add space in error msg
[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.
225
226 =cut
227
228 sub num_active_cust_main {
229   shift->num_sql(FS::cust_main->active_sql);
230 }
231
232 =item active_cust_main
233
234 Returns the active customers for this agent, as cust_main objects.
235
236 =cut
237
238 sub active_cust_main {
239   shift->cust_main_sql(FS::cust_main->active_sql);
240 }
241
242 =item num_susp_cust_main
243
244 Returns the number of suspended customers for this agent.
245
246 =cut
247
248 sub num_susp_cust_main {
249   shift->num_sql(FS::cust_main->susp_sql);
250 }
251
252 =item susp_cust_main
253
254 Returns the suspended customers for this agent, as cust_main objects.
255
256 =cut
257
258 sub susp_cust_main {
259   shift->cust_main_sql(FS::cust_main->susp_sql);
260 }
261
262 =item num_cancel_cust_main
263
264 Returns the number of cancelled customer for this agent.
265
266 =cut
267
268 sub num_cancel_cust_main {
269   shift->num_sql(FS::cust_main->cancel_sql);
270 }
271
272 =item cancel_cust_main
273
274 Returns the cancelled customers for this agent, as cust_main objects.
275
276 =cut
277
278 sub cancel_cust_main {
279   shift->cust_main_sql(FS::cust_main->cancel_sql);
280 }
281
282 =item num_active_cust_pkg
283
284 Returns the number of active customer packages for this agent.
285
286 =cut
287
288 sub num_active_cust_pkg {
289   shift->num_pkg_sql(FS::cust_pkg->active_sql);
290 }
291
292 sub num_pkg_sql {
293   my( $self, $sql ) = @_;
294   my $statement = 
295     "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
296     " WHERE agentnum = ? AND $sql";
297   my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
298   $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
299   $sth->fetchrow_arrayref->[0];
300 }
301
302 =item num_susp_cust_pkg
303
304 Returns the number of suspended customer packages for this agent.
305
306 =cut
307
308 sub num_susp_cust_pkg {
309   shift->num_pkg_sql(FS::cust_pkg->susp_sql);
310 }
311
312 =item num_cancel_cust_pkg
313
314 Returns the number of cancelled customer packages for this agent.
315
316 =cut
317
318 sub num_cancel_cust_pkg {
319   shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
320 }
321
322 =item generate_reg_codes NUM PKGPART_ARRAYREF
323
324 Generates the specified number of registration codes, allowing purchase of the
325 specified package definitions.  Returns an array reference of the newly
326 generated codes, or a scalar error message.
327
328 =cut
329
330 #false laziness w/prepay_credit::generate
331 sub generate_reg_codes {
332   my( $self, $num, $pkgparts ) = @_;
333
334   my @codeset = ( 'A'..'Z' );
335
336   local $SIG{HUP} = 'IGNORE';
337   local $SIG{INT} = 'IGNORE';
338   local $SIG{QUIT} = 'IGNORE';
339   local $SIG{TERM} = 'IGNORE';
340   local $SIG{TSTP} = 'IGNORE';
341   local $SIG{PIPE} = 'IGNORE';
342
343   my $oldAutoCommit = $FS::UID::AutoCommit;
344   local $FS::UID::AutoCommit = 0;
345   my $dbh = dbh;
346
347   my @codes = ();
348   for ( 1 ... $num ) {
349     my $reg_code = new FS::reg_code {
350       'agentnum' => $self->agentnum,
351       'code'     => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
352     };
353     my $error = $reg_code->insert($pkgparts);
354     if ( $error ) {
355       $dbh->rollback if $oldAutoCommit;
356       return $error;
357     }
358     push @codes, $reg_code->code;
359   }
360
361   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
362
363   \@codes;
364
365 }
366
367 =item num_reg_code
368
369 Returns the number of unused registration codes for this agent.
370
371 =cut
372
373 sub num_reg_code {
374   my $self = shift;
375   my $sth = dbh->prepare(
376     "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
377   ) or die dbh->errstr;
378   $sth->execute($self->agentnum) or die $sth->errstr;
379   $sth->fetchrow_arrayref->[0];
380 }
381
382 =item num_prepay_credit
383
384 Returns the number of unused prepaid cards for this agent.
385
386 =cut
387
388 sub num_prepay_credit {
389   my $self = shift;
390   my $sth = dbh->prepare(
391     "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
392   ) or die dbh->errstr;
393   $sth->execute($self->agentnum) or die $sth->errstr;
394   $sth->fetchrow_arrayref->[0];
395 }
396
397
398 =back
399
400 =head1 BUGS
401
402 =head1 SEE ALSO
403
404 L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>, 
405 schema.html from the base documentation.
406
407 =cut
408
409 1;
410