4158341e489009afe7a2c7d7035dad704550db8e
[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_susp_cust_pkg
326
327 Returns the number of suspended customer packages for this agent.
328
329 =cut
330
331 sub num_susp_cust_pkg {
332   shift->num_pkg_sql(FS::cust_pkg->susp_sql);
333 }
334
335 =item num_cancel_cust_pkg
336
337 Returns the number of cancelled customer packages for this agent.
338
339 =cut
340
341 sub num_cancel_cust_pkg {
342   shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
343 }
344
345 =item generate_reg_codes NUM PKGPART_ARRAYREF
346
347 Generates the specified number of registration codes, allowing purchase of the
348 specified package definitions.  Returns an array reference of the newly
349 generated codes, or a scalar error message.
350
351 =cut
352
353 #false laziness w/prepay_credit::generate
354 sub generate_reg_codes {
355   my( $self, $num, $pkgparts ) = @_;
356
357   my @codeset = ( 'A'..'Z' );
358
359   local $SIG{HUP} = 'IGNORE';
360   local $SIG{INT} = 'IGNORE';
361   local $SIG{QUIT} = 'IGNORE';
362   local $SIG{TERM} = 'IGNORE';
363   local $SIG{TSTP} = 'IGNORE';
364   local $SIG{PIPE} = 'IGNORE';
365
366   my $oldAutoCommit = $FS::UID::AutoCommit;
367   local $FS::UID::AutoCommit = 0;
368   my $dbh = dbh;
369
370   my @codes = ();
371   for ( 1 ... $num ) {
372     my $reg_code = new FS::reg_code {
373       'agentnum' => $self->agentnum,
374       'code'     => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
375     };
376     my $error = $reg_code->insert($pkgparts);
377     if ( $error ) {
378       $dbh->rollback if $oldAutoCommit;
379       return $error;
380     }
381     push @codes, $reg_code->code;
382   }
383
384   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
385
386   \@codes;
387
388 }
389
390 =item num_reg_code
391
392 Returns the number of unused registration codes for this agent.
393
394 =cut
395
396 sub num_reg_code {
397   my $self = shift;
398   my $sth = dbh->prepare(
399     "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
400   ) or die dbh->errstr;
401   $sth->execute($self->agentnum) or die $sth->errstr;
402   $sth->fetchrow_arrayref->[0];
403 }
404
405 =item num_prepay_credit
406
407 Returns the number of unused prepaid cards for this agent.
408
409 =cut
410
411 sub num_prepay_credit {
412   my $self = shift;
413   my $sth = dbh->prepare(
414     "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
415   ) or die dbh->errstr;
416   $sth->execute($self->agentnum) or die $sth->errstr;
417   $sth->fetchrow_arrayref->[0];
418 }
419
420
421 =back
422
423 =head1 BUGS
424
425 =head1 SEE ALSO
426
427 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>, 
428 schema.html from the base documentation.
429
430 =cut
431
432 1;
433