2 use base qw( FS::m2m_Common FS::m2name_Common FS::Record );
6 use Business::CreditCard 0.28;
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<invnum>, I<method>,
242 I<payinfo>, and I<thirdparty>.
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 If I<invnum> is set to the number of an invoice (see L<FS::cust_bill>) then
248 an attempt will be made to select a gateway suited for the taxes paid on
251 The I<method> and I<payinfo> options can be used to influence the choice
252 as well. Presently only 'CC', 'ECHECK', and 'PAYPAL' methods are meaningful.
254 When the I<method> is 'CC' then the card number in I<payinfo> can direct
255 this routine to route to a gateway suited for that type of card.
257 If I<thirdparty> is set, the defined self-service payment gateway will
262 sub payment_gateway {
263 my ( $self, %options ) = @_;
265 my $conf = new FS::Conf;
267 if ( $options{thirdparty} ) {
268 # still a kludge, but it gets the job done
269 # and the 'cardtype' semantics don't really apply to thirdparty
270 # gateways because we have to choose a gateway without ever
271 # seeing the card number
273 $conf->config('selfservice-payment_gateway', $self->agentnum);
275 $gateway = FS::payment_gateway->by_key($gatewaynum) if $gatewaynum;
276 return $gateway if $gateway;
278 # a little less kludgey than the above, and allows PayPal to coexist
279 # with credit card gateways
280 my $is_paypal = { op => '!=', value => 'PayPal' };
281 if ( uc($options{method}) eq 'PAYPAL' ) {
282 $is_paypal = 'PayPal';
285 $gateway = qsearchs({
286 table => 'payment_gateway',
287 addl_from => ' JOIN agent_payment_gateway USING (gatewaynum) ',
289 gateway_namespace => 'Business::OnlineThirdPartyPayment',
290 gateway_module => $is_paypal,
293 extra_sql => ' AND agentnum = '.$self->agentnum,
298 } elsif ( $options{'nofatal'} ) {
301 die "no third-party gateway configured\n";
306 if ( $options{invnum} ) {
308 my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{invnum} } );
309 die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
315 $cust_bill->cust_bill_pkg;
317 my @taxclasses = map $_->taxclass, @part_pkg;
319 $taxclass = $taxclasses[0]
320 unless grep { $taxclasses[0] ne $_ } @taxclasses; #unless there are
321 #different taxclasses
324 #look for an agent gateway override first
326 if ( $options{method} ) {
327 if ( $options{method} eq 'CC' && $options{payinfo} ) {
328 $cardtype = cardtype($options{payinfo});
329 } elsif ( $options{method} eq 'ECHECK' ) {
332 $cardtype = $options{method}
337 qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
338 cardtype => $cardtype,
339 taxclass => $taxclass, } )
340 || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
342 taxclass => $taxclass, } )
343 || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
344 cardtype => $cardtype,
346 || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
351 if ( $override ) { #use a payment gateway override
353 $payment_gateway = $override->payment_gateway;
355 $payment_gateway->gateway_namespace('Business::OnlinePayment')
356 unless $payment_gateway->gateway_namespace;
358 } else { #use the standard settings from the config
360 # the standard settings from the config could be moved to a null agent
361 # agent_payment_gateway referenced payment_gateway
363 unless ( $conf->exists('business-onlinepayment') ) {
364 if ( $options{'nofatal'} ) {
367 die "Real-time processing not enabled\n";
372 my $bop_config = 'business-onlinepayment';
373 $bop_config .= '-ach'
374 if ( $options{method}
375 && $options{method} =~ /^(ECHECK|CHEK)$/
376 && $conf->exists($bop_config. '-ach')
378 my ( $processor, $login, $password, $action, @bop_options ) =
379 $conf->config($bop_config);
380 $action ||= 'normal authorization';
381 pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
382 die "No real-time processor is enabled - ".
383 "did you set the business-onlinepayment configuration value?\n"
386 $payment_gateway = new FS::payment_gateway;
388 $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') ||
389 'Business::OnlinePayment');
390 $payment_gateway->gateway_module($processor);
391 $payment_gateway->gateway_username($login);
392 $payment_gateway->gateway_password($password);
393 $payment_gateway->gateway_action($action);
394 $payment_gateway->set('options', [ @bop_options ]);
398 unless ( $payment_gateway->gateway_namespace ) {
399 $payment_gateway->gateway_namespace(
400 scalar($conf->config('business-onlinepayment-namespace'))
401 || 'Business::OnlinePayment'
410 Returns all L<FS::invoice_mode> objects that are valid for this agent (i.e.
411 those with this agentnum or null agentnum).
418 table => 'invoice_mode',
419 hashref => { agentnum => $self->agentnum },
420 extra_sql => ' OR agentnum IS NULL',
421 order_by => ' ORDER BY modename',
425 =item num_prospect_cust_main
427 Returns the number of prospects (customers with no packages ever ordered) for
432 sub num_prospect_cust_main {
433 shift->num_sql(FS::cust_main->prospect_sql);
437 my( $self, $sql ) = @_;
438 my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
439 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
440 $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
441 $sth->fetchrow_arrayref->[0];
444 =item prospect_cust_main
446 Returns the prospects (customers with no packages ever ordered) for this agent,
447 as cust_main objects.
451 sub prospect_cust_main {
452 shift->cust_main_sql(FS::cust_main->prospect_sql);
456 my( $self, $sql ) = @_;
457 qsearch( 'cust_main',
458 { 'agentnum' => $self->agentnum },
464 =item num_ordered_cust_main
466 Returns the number of ordered customers for this agent (customers with packages
467 ordered, but not yet billed).
471 sub num_ordered_cust_main {
472 shift->num_sql(FS::cust_main->ordered_sql);
475 =item ordered_cust_main
477 Returns the ordered customers for this agent (customers with packages ordered,
478 but not yet billed), as cust_main objects.
482 sub ordered_cust_main {
483 shift->cust_main_sql(FS::cust_main->ordered_sql);
487 =item num_active_cust_main
489 Returns the number of active customers for this agent (customers with active
494 sub num_active_cust_main {
495 shift->num_sql(FS::cust_main->active_sql);
498 =item active_cust_main
500 Returns the active customers for this agent, as cust_main objects.
504 sub active_cust_main {
505 shift->cust_main_sql(FS::cust_main->active_sql);
508 =item num_inactive_cust_main
510 Returns the number of inactive customers for this agent (customers with no
511 active recurring packages, but otherwise unsuspended/uncancelled).
515 sub num_inactive_cust_main {
516 shift->num_sql(FS::cust_main->inactive_sql);
519 =item inactive_cust_main
521 Returns the inactive customers for this agent, as cust_main objects.
525 sub inactive_cust_main {
526 shift->cust_main_sql(FS::cust_main->inactive_sql);
530 =item num_susp_cust_main
532 Returns the number of suspended customers for this agent.
536 sub num_susp_cust_main {
537 shift->num_sql(FS::cust_main->susp_sql);
542 Returns the suspended customers for this agent, as cust_main objects.
547 shift->cust_main_sql(FS::cust_main->susp_sql);
550 =item num_cancel_cust_main
552 Returns the number of cancelled customer for this agent.
556 sub num_cancel_cust_main {
557 shift->num_sql(FS::cust_main->cancel_sql);
560 =item cancel_cust_main
562 Returns the cancelled customers for this agent, as cust_main objects.
566 sub cancel_cust_main {
567 shift->cust_main_sql(FS::cust_main->cancel_sql);
570 =item num_active_cust_pkg
572 Returns the number of active customer packages for this agent.
576 sub num_active_cust_pkg {
577 shift->num_pkg_sql(FS::cust_pkg->active_sql);
581 my( $self, $sql ) = @_;
583 "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
584 " WHERE agentnum = ? AND $sql";
585 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
586 $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
587 $sth->fetchrow_arrayref->[0];
590 =item num_inactive_cust_pkg
592 Returns the number of inactive customer packages (one-time packages otherwise
593 unsuspended/uncancelled) for this agent.
597 sub num_inactive_cust_pkg {
598 shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
601 =item num_susp_cust_pkg
603 Returns the number of suspended customer packages for this agent.
607 sub num_susp_cust_pkg {
608 shift->num_pkg_sql(FS::cust_pkg->susp_sql);
611 =item num_cancel_cust_pkg
613 Returns the number of cancelled customer packages for this agent.
617 sub num_cancel_cust_pkg {
618 shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
621 =item num_on_hold_cust_pkg
623 Returns the number of inactive customer packages (one-time packages otherwise
624 unsuspended/uncancelled) for this agent.
628 sub num_on_hold_cust_pkg {
629 shift->num_pkg_sql(FS::cust_pkg->on_hold_sql);
632 =item num_not_yet_billed_cust_pkg
634 Returns the number of inactive customer packages (one-time packages otherwise
635 unsuspended/uncancelled) for this agent.
639 sub num_not_yet_billed_cust_pkg {
640 shift->num_pkg_sql(FS::cust_pkg->not_yet_billed_sql);
643 =item generate_reg_codes NUM PKGPART_ARRAYREF
645 Generates the specified number of registration codes, allowing purchase of the
646 specified package definitions. Returns an array reference of the newly
647 generated codes, or a scalar error message.
651 #false laziness w/prepay_credit::generate
652 sub generate_reg_codes {
653 my( $self, $num, $pkgparts ) = @_;
655 my @codeset = ( 'A'..'Z' );
657 local $SIG{HUP} = 'IGNORE';
658 local $SIG{INT} = 'IGNORE';
659 local $SIG{QUIT} = 'IGNORE';
660 local $SIG{TERM} = 'IGNORE';
661 local $SIG{TSTP} = 'IGNORE';
662 local $SIG{PIPE} = 'IGNORE';
664 my $oldAutoCommit = $FS::UID::AutoCommit;
665 local $FS::UID::AutoCommit = 0;
670 my $reg_code = new FS::reg_code {
671 'agentnum' => $self->agentnum,
672 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
674 my $error = $reg_code->insert($pkgparts);
676 $dbh->rollback if $oldAutoCommit;
679 push @codes, $reg_code->code;
682 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
690 Returns the number of unused registration codes for this agent.
696 my $sth = dbh->prepare(
697 "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
698 ) or die dbh->errstr;
699 $sth->execute($self->agentnum) or die $sth->errstr;
700 $sth->fetchrow_arrayref->[0];
703 =item num_prepay_credit
705 Returns the number of unused prepaid cards for this agent.
709 sub num_prepay_credit {
711 my $sth = dbh->prepare(
712 "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
713 ) or die dbh->errstr;
714 $sth->execute($self->agentnum) or die $sth->errstr;
715 $sth->fetchrow_arrayref->[0];
720 Returns the number of non-disabled sales people for this agent.
726 my $sth = dbh->prepare(
727 "SELECT COUNT(*) FROM sales WHERE agentnum = ?
728 AND ( disabled = '' OR disabled IS NULL )"
729 ) or die dbh->errstr;
730 $sth->execute($self->agentnum) or die $sth->errstr;
731 $sth->fetchrow_arrayref->[0];
740 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
741 schema.html from the base documentation.