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;
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.
183 =item agent_cust_main
185 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
190 sub agent_cust_main {
192 qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
197 Returns the FS::agent_currency objects (see L<FS::agent_currency>), if any, for
200 =item agent_currency_hashref
202 Returns a hash references of supported additional currencies for this agent.
206 sub agent_currency_hashref {
208 +{ map { $_->currency => 1 }
209 $self->agent_currency
213 =item pkgpart_hashref
215 Returns a hash reference. The keys of the hash are pkgparts. The value is
216 true if this agent may purchase the specified package definition. See
221 sub pkgpart_hashref {
223 $self->agent_type->pkgpart_hashref;
226 =item ticketing_queue
228 Returns the queue name corresponding with the id from the I<ticketing_queueid>
229 field, or the empty string.
233 sub ticketing_queue {
235 FS::TicketSystem->queue($self->ticketing_queueid);
238 =item payment_gateway [ OPTION => VALUE, ... ]
240 Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
242 Currently available options are I<nofatal>, I<method>, I<thirdparty> and I<conf>.
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 Exisisting I<$conf> may be passed for efficiency.
260 # opts invnum/payinfo for cardtype/taxclass overrides no longer supported
261 # any future overrides added here need to be reconciled with the tokenization process
263 sub payment_gateway {
264 my ( $self, %options ) = @_;
266 $options{'conf'} ||= new FS::Conf;
267 my $conf = $options{'conf'};
269 if ( $options{thirdparty} ) {
271 # allows PayPal to coexist with credit card gateways
272 my $is_paypal = { op => '!=', value => 'PayPal' };
273 if ( uc($options{method}) eq 'PAYPAL' ) {
274 $is_paypal = 'PayPal';
277 my $gateway = qsearchs({
278 table => 'payment_gateway',
279 addl_from => ' JOIN agent_payment_gateway USING (gatewaynum) ',
281 gateway_namespace => 'Business::OnlineThirdPartyPayment',
282 gateway_module => $is_paypal,
285 extra_sql => ' AND agentnum = '.$self->agentnum,
290 } elsif ( $options{'nofatal'} ) {
293 die "no third-party gateway configured\n";
297 my $cardtype_search = "AND ( cardtype IS NULL OR cardtype <> 'ACH')";
298 $cardtype_search = "AND ( cardtype IS NULL OR cardtype = 'ACH' )" if $options{method} eq 'ECHECK';
302 "table" => 'agent_payment_gateway',
303 "hashref" => { agentnum => $self->agentnum, },
304 "extra_sql" => $cardtype_search,
307 my $payment_gateway = FS::payment_gateway->by_key_or_default(
308 gatewaynum => $override ? $override->gatewaynum : '',
317 Returns all L<FS::invoice_mode> objects that are valid for this agent (i.e.
318 those with this agentnum or null agentnum).
325 table => 'invoice_mode',
326 hashref => { agentnum => $self->agentnum },
327 extra_sql => ' OR agentnum IS NULL',
328 order_by => ' ORDER BY modename',
332 =item num_prospect_cust_main
334 Returns the number of prospects (customers with no packages ever ordered) for
339 sub num_prospect_cust_main {
340 shift->num_sql(FS::cust_main->prospect_sql);
344 my( $self, $sql ) = @_;
345 my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
346 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
347 $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
348 $sth->fetchrow_arrayref->[0];
351 =item prospect_cust_main
353 Returns the prospects (customers with no packages ever ordered) for this agent,
354 as cust_main objects.
358 sub prospect_cust_main {
359 shift->cust_main_sql(FS::cust_main->prospect_sql);
363 my( $self, $sql ) = @_;
364 qsearch( 'cust_main',
365 { 'agentnum' => $self->agentnum },
371 =item num_ordered_cust_main
373 Returns the number of ordered customers for this agent (customers with packages
374 ordered, but not yet billed).
378 sub num_ordered_cust_main {
379 shift->num_sql(FS::cust_main->ordered_sql);
382 =item ordered_cust_main
384 Returns the ordered customers for this agent (customers with packages ordered,
385 but not yet billed), as cust_main objects.
389 sub ordered_cust_main {
390 shift->cust_main_sql(FS::cust_main->ordered_sql);
394 =item num_active_cust_main
396 Returns the number of active customers for this agent (customers with active
401 sub num_active_cust_main {
402 shift->num_sql(FS::cust_main->active_sql);
405 =item active_cust_main
407 Returns the active customers for this agent, as cust_main objects.
411 sub active_cust_main {
412 shift->cust_main_sql(FS::cust_main->active_sql);
415 =item num_inactive_cust_main
417 Returns the number of inactive customers for this agent (customers with no
418 active recurring packages, but otherwise unsuspended/uncancelled).
422 sub num_inactive_cust_main {
423 shift->num_sql(FS::cust_main->inactive_sql);
426 =item inactive_cust_main
428 Returns the inactive customers for this agent, as cust_main objects.
432 sub inactive_cust_main {
433 shift->cust_main_sql(FS::cust_main->inactive_sql);
437 =item num_susp_cust_main
439 Returns the number of suspended customers for this agent.
443 sub num_susp_cust_main {
444 shift->num_sql(FS::cust_main->susp_sql);
449 Returns the suspended customers for this agent, as cust_main objects.
454 shift->cust_main_sql(FS::cust_main->susp_sql);
457 =item num_cancel_cust_main
459 Returns the number of cancelled customer for this agent.
463 sub num_cancel_cust_main {
464 shift->num_sql(FS::cust_main->cancel_sql);
467 =item cancel_cust_main
469 Returns the cancelled customers for this agent, as cust_main objects.
473 sub cancel_cust_main {
474 shift->cust_main_sql(FS::cust_main->cancel_sql);
477 =item num_active_cust_pkg
479 Returns the number of active customer packages for this agent.
483 sub num_active_cust_pkg {
484 shift->num_pkg_sql(FS::cust_pkg->active_sql);
488 my( $self, $sql ) = @_;
490 "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
491 " WHERE agentnum = ? AND $sql";
492 my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
493 $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
494 $sth->fetchrow_arrayref->[0];
497 =item num_inactive_cust_pkg
499 Returns the number of inactive customer packages (one-time packages otherwise
500 unsuspended/uncancelled) for this agent.
504 sub num_inactive_cust_pkg {
505 shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
508 =item num_susp_cust_pkg
510 Returns the number of suspended customer packages for this agent.
514 sub num_susp_cust_pkg {
515 shift->num_pkg_sql(FS::cust_pkg->susp_sql);
518 =item num_cancel_cust_pkg
520 Returns the number of cancelled customer packages for this agent.
524 sub num_cancel_cust_pkg {
525 shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
528 =item num_on_hold_cust_pkg
530 Returns the number of inactive customer packages (one-time packages otherwise
531 unsuspended/uncancelled) for this agent.
535 sub num_on_hold_cust_pkg {
536 shift->num_pkg_sql(FS::cust_pkg->on_hold_sql);
539 =item num_not_yet_billed_cust_pkg
541 Returns the number of inactive customer packages (one-time packages otherwise
542 unsuspended/uncancelled) for this agent.
546 sub num_not_yet_billed_cust_pkg {
547 shift->num_pkg_sql(FS::cust_pkg->not_yet_billed_sql);
550 =item generate_reg_codes NUM PKGPART_ARRAYREF
552 Generates the specified number of registration codes, allowing purchase of the
553 specified package definitions. Returns an array reference of the newly
554 generated codes, or a scalar error message.
558 #false laziness w/prepay_credit::generate
559 sub generate_reg_codes {
560 my( $self, $num, $pkgparts ) = @_;
562 my @codeset = ( 'A'..'Z' );
564 local $SIG{HUP} = 'IGNORE';
565 local $SIG{INT} = 'IGNORE';
566 local $SIG{QUIT} = 'IGNORE';
567 local $SIG{TERM} = 'IGNORE';
568 local $SIG{TSTP} = 'IGNORE';
569 local $SIG{PIPE} = 'IGNORE';
571 my $oldAutoCommit = $FS::UID::AutoCommit;
572 local $FS::UID::AutoCommit = 0;
577 my $reg_code = new FS::reg_code {
578 'agentnum' => $self->agentnum,
579 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
581 my $error = $reg_code->insert($pkgparts);
583 $dbh->rollback if $oldAutoCommit;
586 push @codes, $reg_code->code;
589 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
597 Returns the number of unused registration codes for this agent.
603 my $sth = dbh->prepare(
604 "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
605 ) or die dbh->errstr;
606 $sth->execute($self->agentnum) or die $sth->errstr;
607 $sth->fetchrow_arrayref->[0];
610 =item num_prepay_credit
612 Returns the number of unused prepaid cards for this agent.
616 sub num_prepay_credit {
618 my $sth = dbh->prepare(
619 "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
620 ) or die dbh->errstr;
621 $sth->execute($self->agentnum) or die $sth->errstr;
622 $sth->fetchrow_arrayref->[0];
627 Returns the number of non-disabled sales people for this agent.
633 my $sth = dbh->prepare(
634 "SELECT COUNT(*) FROM sales WHERE agentnum = ?
635 AND ( disabled = '' OR disabled IS NULL )"
636 ) or die dbh->errstr;
637 $sth->execute($self->agentnum) or die $sth->errstr;
638 $sth->fetchrow_arrayref->[0];
641 sub commission_where {
643 'cust_credit.commission_agentnum = ' . $self->agentnum;
648 'cust_main.agentnum = ' . $self->agentnum;
657 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
658 schema.html from the base documentation.