4 use base qw( FS::Commission_Mixin FS::m2m_Common FS::Record );
6 use Business::CreditCard 0.28;
7 use FS::Record qw( dbh qsearch qsearchs );
12 use FS::payment_gateway;
18 FS::agent - Object methods for agent records
24 $record = new FS::agent \%hash;
25 $record = new FS::agent { 'column' => 'value' };
27 $error = $record->insert;
29 $error = $new_record->replace($old_record);
31 $error = $record->delete;
33 $error = $record->check;
35 $agent_type = $record->agent_type;
37 $hashref = $record->pkgpart_hashref;
38 #may purchase $pkgpart if $hashref->{$pkgpart};
42 An FS::agent object represents an agent. Every customer has an agent. Agents
43 can be used to track things like resellers or salespeople. FS::agent inherits
44 from FS::Record. The following fields are currently supported:
50 primary key (assigned automatically for new agents)
54 Text name of this agent
58 Agent type (see L<FS::agent_type>)
60 =item ticketing_queueid
64 =item invoice_template
70 Optional agent customer (see L<FS::cust_main>)
74 Disabled flag, empty or 'Y'
78 Deprecated (never used)
82 Deprecated (never used)
86 (Deprecated) Username for the Agent interface
90 (Deprecated) Password for the Agent interface
100 Creates a new agent. To add the agent to the database, see L<"insert">.
104 sub table { 'agent'; }
108 Adds this agent to the database. If there is an error, returns the error,
109 otherwise returns false.
113 Deletes this agent from the database. Only agents with no customers can be
114 deleted. If there is an error, returns the error, otherwise returns false.
121 return "Can't delete an agent with customers!"
122 if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
124 $self->SUPER::delete;
127 =item replace OLD_RECORD
129 Replaces OLD_RECORD with this one in the database. If there is an error,
130 returns the error, otherwise returns false.
134 Checks all fields to make sure this is a valid agent. If there is an error,
135 returns the error, otherwise returns false. Called by the insert and replace
144 $self->ut_numbern('agentnum')
145 || $self->ut_text('agent')
146 || $self->ut_number('typenum')
147 || $self->ut_numbern('freq')
148 || $self->ut_textn('prog')
149 || $self->ut_textn('invoice_template')
150 || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
151 || $self->ut_numbern('ticketing_queueid')
153 return $error if $error;
155 if ( $self->dbdef_table->column('disabled') ) {
156 $error = $self->ut_enum('disabled', [ '', 'Y' ] );
157 return $error if $error;
160 if ( $self->dbdef_table->column('username') ) {
161 $error = $self->ut_alphan('username');
162 return $error if $error;
163 if ( length($self->username) ) {
164 my $conflict = qsearchs('agent', { 'username' => $self->username } );
165 return 'duplicate agent username (with '. $conflict->agent. ')'
166 if $conflict && $conflict->agentnum != $self->agentnum;
167 $error = $self->ut_text('password'); # ut_text... arbitrary choice
169 $self->_password('');
173 return "Unknown typenum!"
174 unless $self->agent_type;
181 Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
187 qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
190 =item agent_cust_main
192 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
197 sub agent_cust_main {
199 qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
202 =item pkgpart_hashref
204 Returns a hash reference. The keys of the hash are pkgparts. The value is
205 true if this agent may purchase the specified package definition. See
210 sub pkgpart_hashref {
212 $self->agent_type->pkgpart_hashref;
215 =item ticketing_queue
217 Returns the queue name corresponding with the id from the I<ticketing_queueid>
218 field, or the empty string.
222 sub ticketing_queue {
224 FS::TicketSystem->queue($self->ticketing_queueid);
227 =item payment_gateway [ OPTION => VALUE, ... ]
229 Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
231 Currently available options are I<nofatal>, I<method>, I<thirdparty> and I<conf>.
233 If I<nofatal> is set, and no gateway is available, then the empty string
234 will be returned instead of throwing a fatal exception.
236 The I<method> option can be used to influence the choice
237 as well. Presently only CHEK/ECHECK and PAYPAL methods are meaningful.
239 If I<method> is CHEK/ECHECK and the default gateway is being returned,
240 the business-onlinepayment-ach gateway will be returned if available.
242 If I<thirdparty> is set and the I<method> is PAYPAL, the defined paypal
243 gateway will be returned.
245 Exisisting I<$conf> may be passed for efficiency.
249 # opts invnum/payinfo for cardtype/taxclass overrides no longer supported
250 # any future overrides added here need to be reconciled with the tokenization process
252 sub payment_gateway {
253 my ( $self, %options ) = @_;
255 $options{'conf'} ||= new FS::Conf;
256 my $conf = $options{'conf'};
258 if ( $options{thirdparty} ) {
260 # allows PayPal to coexist with credit card gateways
261 my $is_paypal = { op => '!=', value => 'PayPal' };
262 if ( uc($options{method}) eq 'PAYPAL' ) {
263 $is_paypal = 'PayPal';
266 my $gateway = qsearchs({
267 table => 'payment_gateway',
268 addl_from => ' JOIN agent_payment_gateway USING (gatewaynum) ',
270 gateway_namespace => 'Business::OnlineThirdPartyPayment',
271 gateway_module => $is_paypal,
274 extra_sql => ' AND agentnum = '.$self->agentnum,
279 } elsif ( $options{'nofatal'} ) {
282 die "no third-party gateway configured\n";
286 my $override = qsearchs('agent_payment_gateway', { agentnum => $self->agentnum } );
288 my $payment_gateway = FS::payment_gateway->by_key_or_default(
289 gatewaynum => $override ? $override->gatewaynum : '',
298 Returns all L<FS::invoice_mode> objects that are valid for this agent (i.e.
299 those with this agentnum or null agentnum).
306 table => 'invoice_mode',
307 hashref => { agentnum => $self->agentnum },
308 extra_sql => ' OR agentnum IS NULL',
309 order_by => ' ORDER BY modename',
313 =item num_prospect_cust_main
315 Returns the number of prospects (customers with no packages ever ordered) for
320 sub num_prospect_cust_main {
321 shift->num_sql(FS::cust_main->prospect_sql);
325 my( $self, $sql ) = @_;
326 my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
327 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
328 $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
329 $sth->fetchrow_arrayref->[0];
332 =item prospect_cust_main
334 Returns the prospects (customers with no packages ever ordered) for this agent,
335 as cust_main objects.
339 sub prospect_cust_main {
340 shift->cust_main_sql(FS::cust_main->prospect_sql);
344 my( $self, $sql ) = @_;
345 qsearch( 'cust_main',
346 { 'agentnum' => $self->agentnum },
352 =item num_ordered_cust_main
354 Returns the number of ordered customers for this agent (customers with packages
355 ordered, but not yet billed).
359 sub num_ordered_cust_main {
360 shift->num_sql(FS::cust_main->ordered_sql);
363 =item ordered_cust_main
365 Returns the ordered customers for this agent (customers with packages ordered,
366 but not yet billed), as cust_main objects.
370 sub ordered_cust_main {
371 shift->cust_main_sql(FS::cust_main->ordered_sql);
375 =item num_active_cust_main
377 Returns the number of active customers for this agent (customers with active
382 sub num_active_cust_main {
383 shift->num_sql(FS::cust_main->active_sql);
386 =item active_cust_main
388 Returns the active customers for this agent, as cust_main objects.
392 sub active_cust_main {
393 shift->cust_main_sql(FS::cust_main->active_sql);
396 =item num_inactive_cust_main
398 Returns the number of inactive customers for this agent (customers with no
399 active recurring packages, but otherwise unsuspended/uncancelled).
403 sub num_inactive_cust_main {
404 shift->num_sql(FS::cust_main->inactive_sql);
407 =item inactive_cust_main
409 Returns the inactive customers for this agent, as cust_main objects.
413 sub inactive_cust_main {
414 shift->cust_main_sql(FS::cust_main->inactive_sql);
418 =item num_susp_cust_main
420 Returns the number of suspended customers for this agent.
424 sub num_susp_cust_main {
425 shift->num_sql(FS::cust_main->susp_sql);
430 Returns the suspended customers for this agent, as cust_main objects.
435 shift->cust_main_sql(FS::cust_main->susp_sql);
438 =item num_cancel_cust_main
440 Returns the number of cancelled customer for this agent.
444 sub num_cancel_cust_main {
445 shift->num_sql(FS::cust_main->cancel_sql);
448 =item cancel_cust_main
450 Returns the cancelled customers for this agent, as cust_main objects.
454 sub cancel_cust_main {
455 shift->cust_main_sql(FS::cust_main->cancel_sql);
458 =item num_active_cust_pkg
460 Returns the number of active customer packages for this agent.
464 sub num_active_cust_pkg {
465 shift->num_pkg_sql(FS::cust_pkg->active_sql);
469 my( $self, $sql ) = @_;
471 "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
472 " WHERE agentnum = ? AND $sql";
473 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
474 $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
475 $sth->fetchrow_arrayref->[0];
478 =item num_inactive_cust_pkg
480 Returns the number of inactive customer packages (one-time packages otherwise
481 unsuspended/uncancelled) for this agent.
485 sub num_inactive_cust_pkg {
486 shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
489 =item num_susp_cust_pkg
491 Returns the number of suspended customer packages for this agent.
495 sub num_susp_cust_pkg {
496 shift->num_pkg_sql(FS::cust_pkg->susp_sql);
499 =item num_cancel_cust_pkg
501 Returns the number of cancelled customer packages for this agent.
505 sub num_cancel_cust_pkg {
506 shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
509 =item num_on_hold_cust_pkg
511 Returns the number of inactive customer packages (one-time packages otherwise
512 unsuspended/uncancelled) for this agent.
516 sub num_on_hold_cust_pkg {
517 shift->num_pkg_sql(FS::cust_pkg->on_hold_sql);
520 =item num_not_yet_billed_cust_pkg
522 Returns the number of inactive customer packages (one-time packages otherwise
523 unsuspended/uncancelled) for this agent.
527 sub num_not_yet_billed_cust_pkg {
528 shift->num_pkg_sql(FS::cust_pkg->not_yet_billed_sql);
531 =item generate_reg_codes NUM PKGPART_ARRAYREF
533 Generates the specified number of registration codes, allowing purchase of the
534 specified package definitions. Returns an array reference of the newly
535 generated codes, or a scalar error message.
539 #false laziness w/prepay_credit::generate
540 sub generate_reg_codes {
541 my( $self, $num, $pkgparts ) = @_;
543 my @codeset = ( 'A'..'Z' );
545 local $SIG{HUP} = 'IGNORE';
546 local $SIG{INT} = 'IGNORE';
547 local $SIG{QUIT} = 'IGNORE';
548 local $SIG{TERM} = 'IGNORE';
549 local $SIG{TSTP} = 'IGNORE';
550 local $SIG{PIPE} = 'IGNORE';
552 my $oldAutoCommit = $FS::UID::AutoCommit;
553 local $FS::UID::AutoCommit = 0;
558 my $reg_code = new FS::reg_code {
559 'agentnum' => $self->agentnum,
560 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
562 my $error = $reg_code->insert($pkgparts);
564 $dbh->rollback if $oldAutoCommit;
567 push @codes, $reg_code->code;
570 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
578 Returns the number of unused registration codes for this agent.
584 my $sth = dbh->prepare(
585 "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
586 ) or die dbh->errstr;
587 $sth->execute($self->agentnum) or die $sth->errstr;
588 $sth->fetchrow_arrayref->[0];
591 =item num_prepay_credit
593 Returns the number of unused prepaid cards for this agent.
597 sub num_prepay_credit {
599 my $sth = dbh->prepare(
600 "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
601 ) or die dbh->errstr;
602 $sth->execute($self->agentnum) or die $sth->errstr;
603 $sth->fetchrow_arrayref->[0];
608 Returns the number of non-disabled sales people for this agent.
614 my $sth = dbh->prepare(
615 "SELECT COUNT(*) FROM sales WHERE agentnum = ?
616 AND ( disabled = '' OR disabled IS NULL )"
617 ) or die dbh->errstr;
618 $sth->execute($self->agentnum) or die $sth->errstr;
619 $sth->fetchrow_arrayref->[0];
622 sub commission_where {
624 'cust_credit.commission_agentnum = ' . $self->agentnum;
629 'cust_main.agentnum = ' . $self->agentnum;
638 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
639 schema.html from the base documentation.