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