4166a7cac36939eaa287e952b4c24c4549019570
[freeside.git] / FS / FS / agent.pm
1 package FS::agent;
2
3 use strict;
4 use vars qw( @ISA );
5 #use Crypt::YAPassGen;
6 use FS::Record qw( dbh qsearch qsearchs );
7 use FS::cust_main;
8 use FS::cust_pkg;
9 use FS::agent_type;
10 use FS::reg_code;
11 use FS::TicketSystem;
12
13 @ISA = qw( FS::Record );
14
15 =head1 NAME
16
17 FS::agent - Object methods for agent records
18
19 =head1 SYNOPSIS
20
21   use FS::agent;
22
23   $record = new FS::agent \%hash;
24   $record = new FS::agent { 'column' => 'value' };
25
26   $error = $record->insert;
27
28   $error = $new_record->replace($old_record);
29
30   $error = $record->delete;
31
32   $error = $record->check;
33
34   $agent_type = $record->agent_type;
35
36   $hashref = $record->pkgpart_hashref;
37   #may purchase $pkgpart if $hashref->{$pkgpart};
38
39 =head1 DESCRIPTION
40
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:
44
45 =over 4
46
47 =item agentnum - primary key (assigned automatically for new agents)
48
49 =item agent - Text name of this agent
50
51 =item typenum - Agent type (see L<FS::agent_type>)
52
53 =item ticketing_queueid - Ticketing Queue
54
55 =item invoice_template - Invoice template name
56
57 =item agent_custnum - Optional agent customer (see L<FS::cust_main>)
58
59 =item disabled - Disabled flag, empty or 'Y'
60
61 =item prog - Deprecated (never used)
62
63 =item freq - Deprecated (never used)
64
65 =item username - (Deprecated) Username for the Agent interface
66
67 =item _password - (Deprecated) Password for the Agent interface
68
69 =back
70
71 =head1 METHODS
72
73 =over 4
74
75 =item new HASHREF
76
77 Creates a new agent.  To add the agent to the database, see L<"insert">.
78
79 =cut
80
81 sub table { 'agent'; }
82
83 =item insert
84
85 Adds this agent to the database.  If there is an error, returns the error,
86 otherwise returns false.
87
88 =item delete
89
90 Deletes this agent from the database.  Only agents with no customers can be
91 deleted.  If there is an error, returns the error, otherwise returns false.
92
93 =cut
94
95 sub delete {
96   my $self = shift;
97
98   return "Can't delete an agent with customers!"
99     if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
100
101   $self->SUPER::delete;
102 }
103
104 =item replace OLD_RECORD
105
106 Replaces OLD_RECORD with this one in the database.  If there is an error,
107 returns the error, otherwise returns false.
108
109 =item check
110
111 Checks all fields to make sure this is a valid agent.  If there is an error,
112 returns the error, otherwise returns false.  Called by the insert and replace
113 methods.
114
115 =cut
116
117 sub check {
118   my $self = shift;
119
120   my $error =
121     $self->ut_numbern('agentnum')
122       || $self->ut_text('agent')
123       || $self->ut_number('typenum')
124       || $self->ut_numbern('freq')
125       || $self->ut_textn('prog')
126       || $self->ut_textn('invoice_template')
127       || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
128   ;
129   return $error if $error;
130
131   if ( $self->dbdef_table->column('disabled') ) {
132     $error = $self->ut_enum('disabled', [ '', 'Y' ] );
133     return $error if $error;
134   }
135
136   if ( $self->dbdef_table->column('username') ) {
137     $error = $self->ut_alphan('username');
138     return $error if $error;
139     if ( length($self->username) ) {
140       my $conflict = qsearchs('agent', { 'username' => $self->username } );
141       return 'duplicate agent username (with '. $conflict->agent. ')'
142         if $conflict && $conflict->agentnum != $self->agentnum;
143       $error = $self->ut_text('password'); # ut_text... arbitrary choice
144     } else {
145       $self->_password('');
146     }
147   }
148
149   return "Unknown typenum!"
150     unless $self->agent_type;
151
152   $self->SUPER::check;
153 }
154
155 =item agent_type
156
157 Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
158
159 =cut
160
161 sub agent_type {
162   my $self = shift;
163   qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
164 }
165
166 =item agent_cust_main
167
168 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
169 agent.
170
171 =cut
172
173 sub agent_cust_main {
174   my $self = shift;
175   qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
176 }
177
178 =item pkgpart_hashref
179
180 Returns a hash reference.  The keys of the hash are pkgparts.  The value is
181 true if this agent may purchase the specified package definition.  See
182 L<FS::part_pkg>.
183
184 =cut
185
186 sub pkgpart_hashref {
187   my $self = shift;
188   $self->agent_type->pkgpart_hashref;
189 }
190
191 =item ticketing_queue
192
193 Returns the queue name corresponding with the id from the I<ticketing_queueid>
194 field, or the empty string.
195
196 =cut
197
198 sub ticketing_queue {
199   my $self = shift;
200   FS::TicketSystem->queue($self->ticketing_queueid);
201 };
202
203 =item num_prospect_cust_main
204
205 Returns the number of prospects (customers with no packages ever ordered) for
206 this agent.
207
208 =cut
209
210 sub num_prospect_cust_main {
211   shift->num_sql(FS::cust_main->prospect_sql);
212 }
213
214 sub num_sql {
215   my( $self, $sql ) = @_;
216   my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
217   my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
218   $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
219   $sth->fetchrow_arrayref->[0];
220 }
221
222 =item prospect_cust_main
223
224 Returns the prospects (customers with no packages ever ordered) for this agent,
225 as cust_main objects.
226
227 =cut
228
229 sub prospect_cust_main {
230   shift->cust_main_sql(FS::cust_main->prospect_sql);
231 }
232
233 sub cust_main_sql {
234   my( $self, $sql ) = @_;
235   qsearch( 'cust_main',
236            { 'agentnum' => $self->agentnum },
237            '',
238            " AND $sql"
239   );
240 }
241
242 =item num_active_cust_main
243
244 Returns the number of active customers for this agent (customers with active
245 recurring packages).
246
247 =cut
248
249 sub num_active_cust_main {
250   shift->num_sql(FS::cust_main->active_sql);
251 }
252
253 =item active_cust_main
254
255 Returns the active customers for this agent, as cust_main objects.
256
257 =cut
258
259 sub active_cust_main {
260   shift->cust_main_sql(FS::cust_main->active_sql);
261 }
262
263 =item num_inactive_cust_main
264
265 Returns the number of inactive customers for this agent (customers with no
266 active recurring packages, but otherwise unsuspended/uncancelled).
267
268 =cut
269
270 sub num_inactive_cust_main {
271   shift->num_sql(FS::cust_main->inactive_sql);
272 }
273
274 =item inactive_cust_main
275
276 Returns the inactive customers for this agent, as cust_main objects.
277
278 =cut
279
280 sub inactive_cust_main {
281   shift->cust_main_sql(FS::cust_main->inactive_sql);
282 }
283
284
285 =item num_susp_cust_main
286
287 Returns the number of suspended customers for this agent.
288
289 =cut
290
291 sub num_susp_cust_main {
292   shift->num_sql(FS::cust_main->susp_sql);
293 }
294
295 =item susp_cust_main
296
297 Returns the suspended customers for this agent, as cust_main objects.
298
299 =cut
300
301 sub susp_cust_main {
302   shift->cust_main_sql(FS::cust_main->susp_sql);
303 }
304
305 =item num_cancel_cust_main
306
307 Returns the number of cancelled customer for this agent.
308
309 =cut
310
311 sub num_cancel_cust_main {
312   shift->num_sql(FS::cust_main->cancel_sql);
313 }
314
315 =item cancel_cust_main
316
317 Returns the cancelled customers for this agent, as cust_main objects.
318
319 =cut
320
321 sub cancel_cust_main {
322   shift->cust_main_sql(FS::cust_main->cancel_sql);
323 }
324
325 =item num_active_cust_pkg
326
327 Returns the number of active customer packages for this agent.
328
329 =cut
330
331 sub num_active_cust_pkg {
332   shift->num_pkg_sql(FS::cust_pkg->active_sql);
333 }
334
335 sub num_pkg_sql {
336   my( $self, $sql ) = @_;
337   my $statement = 
338     "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
339     " WHERE agentnum = ? AND $sql";
340   my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
341   $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
342   $sth->fetchrow_arrayref->[0];
343 }
344
345 =item num_inactive_cust_pkg
346
347 Returns the number of inactive customer packages (one-time packages otherwise
348 unsuspended/uncancelled) for this agent.
349
350 =cut
351
352 sub num_inactive_cust_pkg {
353   shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
354 }
355
356 =item num_susp_cust_pkg
357
358 Returns the number of suspended customer packages for this agent.
359
360 =cut
361
362 sub num_susp_cust_pkg {
363   shift->num_pkg_sql(FS::cust_pkg->susp_sql);
364 }
365
366 =item num_cancel_cust_pkg
367
368 Returns the number of cancelled customer packages for this agent.
369
370 =cut
371
372 sub num_cancel_cust_pkg {
373   shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
374 }
375
376 =item generate_reg_codes NUM PKGPART_ARRAYREF
377
378 Generates the specified number of registration codes, allowing purchase of the
379 specified package definitions.  Returns an array reference of the newly
380 generated codes, or a scalar error message.
381
382 =cut
383
384 #false laziness w/prepay_credit::generate
385 sub generate_reg_codes {
386   my( $self, $num, $pkgparts ) = @_;
387
388   my @codeset = ( 'A'..'Z' );
389
390   local $SIG{HUP} = 'IGNORE';
391   local $SIG{INT} = 'IGNORE';
392   local $SIG{QUIT} = 'IGNORE';
393   local $SIG{TERM} = 'IGNORE';
394   local $SIG{TSTP} = 'IGNORE';
395   local $SIG{PIPE} = 'IGNORE';
396
397   my $oldAutoCommit = $FS::UID::AutoCommit;
398   local $FS::UID::AutoCommit = 0;
399   my $dbh = dbh;
400
401   my @codes = ();
402   for ( 1 ... $num ) {
403     my $reg_code = new FS::reg_code {
404       'agentnum' => $self->agentnum,
405       'code'     => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
406     };
407     my $error = $reg_code->insert($pkgparts);
408     if ( $error ) {
409       $dbh->rollback if $oldAutoCommit;
410       return $error;
411     }
412     push @codes, $reg_code->code;
413   }
414
415   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
416
417   \@codes;
418
419 }
420
421 =item num_reg_code
422
423 Returns the number of unused registration codes for this agent.
424
425 =cut
426
427 sub num_reg_code {
428   my $self = shift;
429   my $sth = dbh->prepare(
430     "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
431   ) or die dbh->errstr;
432   $sth->execute($self->agentnum) or die $sth->errstr;
433   $sth->fetchrow_arrayref->[0];
434 }
435
436 =item num_prepay_credit
437
438 Returns the number of unused prepaid cards for this agent.
439
440 =cut
441
442 sub num_prepay_credit {
443   my $self = shift;
444   my $sth = dbh->prepare(
445     "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
446   ) or die dbh->errstr;
447   $sth->execute($self->agentnum) or die $sth->errstr;
448   $sth->fetchrow_arrayref->[0];
449 }
450
451
452 =back
453
454 =head1 BUGS
455
456 =head1 SEE ALSO
457
458 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>, 
459 schema.html from the base documentation.
460
461 =cut
462
463 1;
464