6 use Business::CreditCard 0.28;
7 use FS::Record qw( dbh qsearch qsearchs );
15 @ISA = qw( FS::m2m_Common FS::Record );
19 FS::agent - Object methods for agent records
25 $record = new FS::agent \%hash;
26 $record = new FS::agent { 'column' => 'value' };
28 $error = $record->insert;
30 $error = $new_record->replace($old_record);
32 $error = $record->delete;
34 $error = $record->check;
36 $agent_type = $record->agent_type;
38 $hashref = $record->pkgpart_hashref;
39 #may purchase $pkgpart if $hashref->{$pkgpart};
43 An FS::agent object represents an agent. Every customer has an agent. Agents
44 can be used to track things like resellers or salespeople. FS::agent inherits
45 from FS::Record. The following fields are currently supported:
49 =item agentnum - primary key (assigned automatically for new agents)
51 =item agent - Text name of this agent
53 =item typenum - Agent type (see L<FS::agent_type>)
55 =item ticketing_queueid - Ticketing Queue
57 =item invoice_template - Invoice template name
59 =item agent_custnum - Optional agent customer (see L<FS::cust_main>)
61 =item disabled - Disabled flag, empty or 'Y'
63 =item prog - Deprecated (never used)
65 =item freq - Deprecated (never used)
67 =item username - (Deprecated) Username for the Agent interface
69 =item _password - (Deprecated) Password for the Agent interface
79 Creates a new agent. To add the agent to the database, see L<"insert">.
83 sub table { 'agent'; }
87 Adds this agent to the database. If there is an error, returns the error,
88 otherwise returns false.
92 Deletes this agent from the database. Only agents with no customers can be
93 deleted. If there is an error, returns the error, otherwise returns false.
100 return "Can't delete an agent with customers!"
101 if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
103 $self->SUPER::delete;
106 =item replace OLD_RECORD
108 Replaces OLD_RECORD with this one in the database. If there is an error,
109 returns the error, otherwise returns false.
113 Checks all fields to make sure this is a valid agent. If there is an error,
114 returns the error, otherwise returns false. Called by the insert and replace
123 $self->ut_numbern('agentnum')
124 || $self->ut_text('agent')
125 || $self->ut_number('typenum')
126 || $self->ut_numbern('freq')
127 || $self->ut_textn('prog')
128 || $self->ut_textn('invoice_template')
129 || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
131 return $error if $error;
133 if ( $self->dbdef_table->column('disabled') ) {
134 $error = $self->ut_enum('disabled', [ '', 'Y' ] );
135 return $error if $error;
138 if ( $self->dbdef_table->column('username') ) {
139 $error = $self->ut_alphan('username');
140 return $error if $error;
141 if ( length($self->username) ) {
142 my $conflict = qsearchs('agent', { 'username' => $self->username } );
143 return 'duplicate agent username (with '. $conflict->agent. ')'
144 if $conflict && $conflict->agentnum != $self->agentnum;
145 $error = $self->ut_text('password'); # ut_text... arbitrary choice
147 $self->_password('');
151 return "Unknown typenum!"
152 unless $self->agent_type;
159 Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
165 qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
168 =item agent_cust_main
170 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
175 sub agent_cust_main {
177 qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
180 =item pkgpart_hashref
182 Returns a hash reference. The keys of the hash are pkgparts. The value is
183 true if this agent may purchase the specified package definition. See
188 sub pkgpart_hashref {
190 $self->agent_type->pkgpart_hashref;
193 =item ticketing_queue
195 Returns the queue name corresponding with the id from the I<ticketing_queueid>
196 field, or the empty string.
200 sub ticketing_queue {
202 FS::TicketSystem->queue($self->ticketing_queueid);
205 =item payment_gateway [ OPTION => VALUE, ... ]
207 Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
209 Currently available options are I<nofatal>, I<invnum>, I<method>, and I<payinfo>.
211 If I<nofatal> is set, and no gateway is available, then the empty string
212 will be returned instead of throwing a fatal exception.
214 If I<invnum> is set to the number of an invoice (see L<FS::cust_bill>) then
215 an attempt will be made to select a gateway suited for the taxes paid on
218 The I<method> and I<payinfo> options can be used to influence the choice
219 as well. Presently only 'CC' and 'ECHECK' methods are meaningful.
221 When the I<method> is 'CC' then the card number in I<payinfo> can direct
222 this routine to route to a gateway suited for that type of card.
226 sub payment_gateway {
227 my ( $self, %options ) = @_;
230 if ( $options{invnum} ) {
232 my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{invnum} } );
233 die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
239 $cust_bill->cust_bill_pkg;
241 my @taxclasses = map $_->taxclass, @part_pkg;
243 $taxclass = $taxclasses[0]
244 unless grep { $taxclasses[0] ne $_ } @taxclasses; #unless there are
245 #different taxclasses
248 #look for an agent gateway override first
250 if ( $options{method} && $options{method} eq 'CC' && $options{payinfo} ) {
251 $cardtype = cardtype($options{payinfo});
252 } elsif ( $options{method} && $options{method} eq 'ECHECK' ) {
255 $cardtype = $options{method} || '';
259 qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
260 cardtype => $cardtype,
261 taxclass => $taxclass, } )
262 || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
264 taxclass => $taxclass, } )
265 || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
266 cardtype => $cardtype,
268 || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
273 my $conf = new FS::Conf;
274 if ( $override ) { #use a payment gateway override
276 $payment_gateway = $override->payment_gateway;
278 $payment_gateway->gateway_namespace('Business::OnlinePayment')
279 unless $payment_gateway->gateway_namespace;
281 } else { #use the standard settings from the config
283 # the standard settings from the config could be moved to a null agent
284 # agent_payment_gateway referenced payment_gateway
286 unless ( $conf->exists('business-onlinepayment') ) {
287 if ( $options{'nofatal'} ) {
290 die "Real-time processing not enabled\n";
295 my $bop_config = 'business-onlinepayment';
296 $bop_config .= '-ach'
297 if ( $options{method}
298 && $options{method} =~ /^(ECHECK|CHEK)$/
299 && $conf->exists($bop_config. '-ach')
301 my ( $processor, $login, $password, $action, @bop_options ) =
302 $conf->config($bop_config);
303 $action ||= 'normal authorization';
304 pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
305 die "No real-time processor is enabled - ".
306 "did you set the business-onlinepayment configuration value?\n"
309 $payment_gateway = new FS::payment_gateway;
311 $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') ||
312 'Business::OnlinePayment');
313 $payment_gateway->gateway_module($processor);
314 $payment_gateway->gateway_username($login);
315 $payment_gateway->gateway_password($password);
316 $payment_gateway->gateway_action($action);
317 $payment_gateway->set('options', [ @bop_options ]);
321 unless ( $payment_gateway->gateway_namespace ) {
322 $payment_gateway->gateway_namespace(
323 scalar($conf->config('business-onlinepayment-namespace'))
324 || 'Business::OnlinePayment'
331 =item num_prospect_cust_main
333 Returns the number of prospects (customers with no packages ever ordered) for
338 sub num_prospect_cust_main {
339 shift->num_sql(FS::cust_main->prospect_sql);
343 my( $self, $sql ) = @_;
344 my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
345 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
346 $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
347 $sth->fetchrow_arrayref->[0];
350 =item prospect_cust_main
352 Returns the prospects (customers with no packages ever ordered) for this agent,
353 as cust_main objects.
357 sub prospect_cust_main {
358 shift->cust_main_sql(FS::cust_main->prospect_sql);
362 my( $self, $sql ) = @_;
363 qsearch( 'cust_main',
364 { 'agentnum' => $self->agentnum },
370 =item num_active_cust_main
372 Returns the number of active customers for this agent (customers with active
377 sub num_active_cust_main {
378 shift->num_sql(FS::cust_main->active_sql);
381 =item active_cust_main
383 Returns the active customers for this agent, as cust_main objects.
387 sub active_cust_main {
388 shift->cust_main_sql(FS::cust_main->active_sql);
391 =item num_inactive_cust_main
393 Returns the number of inactive customers for this agent (customers with no
394 active recurring packages, but otherwise unsuspended/uncancelled).
398 sub num_inactive_cust_main {
399 shift->num_sql(FS::cust_main->inactive_sql);
402 =item inactive_cust_main
404 Returns the inactive customers for this agent, as cust_main objects.
408 sub inactive_cust_main {
409 shift->cust_main_sql(FS::cust_main->inactive_sql);
413 =item num_susp_cust_main
415 Returns the number of suspended customers for this agent.
419 sub num_susp_cust_main {
420 shift->num_sql(FS::cust_main->susp_sql);
425 Returns the suspended customers for this agent, as cust_main objects.
430 shift->cust_main_sql(FS::cust_main->susp_sql);
433 =item num_cancel_cust_main
435 Returns the number of cancelled customer for this agent.
439 sub num_cancel_cust_main {
440 shift->num_sql(FS::cust_main->cancel_sql);
443 =item cancel_cust_main
445 Returns the cancelled customers for this agent, as cust_main objects.
449 sub cancel_cust_main {
450 shift->cust_main_sql(FS::cust_main->cancel_sql);
453 =item num_active_cust_pkg
455 Returns the number of active customer packages for this agent.
459 sub num_active_cust_pkg {
460 shift->num_pkg_sql(FS::cust_pkg->active_sql);
464 my( $self, $sql ) = @_;
466 "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
467 " WHERE agentnum = ? AND $sql";
468 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
469 $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
470 $sth->fetchrow_arrayref->[0];
473 =item num_inactive_cust_pkg
475 Returns the number of inactive customer packages (one-time packages otherwise
476 unsuspended/uncancelled) for this agent.
480 sub num_inactive_cust_pkg {
481 shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
484 =item num_susp_cust_pkg
486 Returns the number of suspended customer packages for this agent.
490 sub num_susp_cust_pkg {
491 shift->num_pkg_sql(FS::cust_pkg->susp_sql);
494 =item num_cancel_cust_pkg
496 Returns the number of cancelled customer packages for this agent.
500 sub num_cancel_cust_pkg {
501 shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
504 =item generate_reg_codes NUM PKGPART_ARRAYREF
506 Generates the specified number of registration codes, allowing purchase of the
507 specified package definitions. Returns an array reference of the newly
508 generated codes, or a scalar error message.
512 #false laziness w/prepay_credit::generate
513 sub generate_reg_codes {
514 my( $self, $num, $pkgparts ) = @_;
516 my @codeset = ( 'A'..'Z' );
518 local $SIG{HUP} = 'IGNORE';
519 local $SIG{INT} = 'IGNORE';
520 local $SIG{QUIT} = 'IGNORE';
521 local $SIG{TERM} = 'IGNORE';
522 local $SIG{TSTP} = 'IGNORE';
523 local $SIG{PIPE} = 'IGNORE';
525 my $oldAutoCommit = $FS::UID::AutoCommit;
526 local $FS::UID::AutoCommit = 0;
531 my $reg_code = new FS::reg_code {
532 'agentnum' => $self->agentnum,
533 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
535 my $error = $reg_code->insert($pkgparts);
537 $dbh->rollback if $oldAutoCommit;
540 push @codes, $reg_code->code;
543 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
551 Returns the number of unused registration codes for this agent.
557 my $sth = dbh->prepare(
558 "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
559 ) or die dbh->errstr;
560 $sth->execute($self->agentnum) or die $sth->errstr;
561 $sth->fetchrow_arrayref->[0];
564 =item num_prepay_credit
566 Returns the number of unused prepaid cards for this agent.
570 sub num_prepay_credit {
572 my $sth = dbh->prepare(
573 "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
574 ) or die dbh->errstr;
575 $sth->execute($self->agentnum) or die $sth->errstr;
576 $sth->fetchrow_arrayref->[0];
586 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
587 schema.html from the base documentation.