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