Virtual field merge
[freeside.git] / FS / FS / svc_Common.pm
1 package FS::svc_Common;
2
3 use strict;
4 use vars qw( @ISA $noexport_hack );
5 use FS::Record qw( qsearch qsearchs fields dbh );
6 use FS::cust_svc;
7 use FS::part_svc;
8 use FS::queue;
9
10 @ISA = qw( FS::Record );
11
12 =head1 NAME
13
14 FS::svc_Common - Object method for all svc_ records
15
16 =head1 SYNOPSIS
17
18 use FS::svc_Common;
19
20 @ISA = qw( FS::svc_Common );
21
22 =head1 DESCRIPTION
23
24 FS::svc_Common is intended as a base class for table-specific classes to
25 inherit from, i.e. FS::svc_acct.  FS::svc_Common inherits from FS::Record.
26
27 =head1 METHODS
28
29 =over 4
30
31 =cut
32
33 sub virtual_fields {
34
35   # This restricts the fields based on part_svc_column and the svcpart of 
36   # the service.  There are four possible cases:
37   # 1.  svcpart passed as part of the svc_x hash.
38   # 2.  svcpart fetched via cust_svc based on svcnum.
39   # 3.  No svcnum or svcpart.  In this case, return ALL the fields with 
40   #     dbtable eq $self->table.
41   # 4.  Called via "fields('svc_acct')" or something similar.  In this case
42   #     there is no $self object.
43
44   my $self = shift;
45   my $svcpart;
46   my @vfields = $self->SUPER::virtual_fields;
47
48   return @vfields unless (ref $self); # Case 4
49
50   if ($self->svcpart) { # Case 1
51     $svcpart = $self->svcpart;
52   } elsif (my $cust_svc = $self->cust_svc) { # Case 2
53     $svcpart = $cust_svc->svcpart;
54   } else { # Case 3
55     $svcpart = '';
56   }
57
58   if ($svcpart) { #Cases 1 and 2
59     my %flags = map { $_->columnname, $_->columnflag } (
60         qsearch ('part_svc_column', { svcpart => $svcpart } )
61       );
62     return grep { not ($flags{$_} eq 'X') } @vfields;
63   } else { # Case 3
64     return @vfields;
65   } 
66   return ();
67 }
68
69 =item check
70
71 Checks the validity of fields in this record.
72
73 At present, this does nothing but call FS::Record::check (which, in turn, 
74 does nothing but run virtual field checks).
75
76 =cut
77
78 sub check {
79   my $self = shift;
80   $self->SUPER::check;
81 }
82
83 =item insert [ JOBNUM_ARRAYREF ]
84
85 Adds this record to the database.  If there is an error, returns the error,
86 otherwise returns false.
87
88 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
89 defined.  An FS::cust_svc record will be created and inserted.
90
91 If an arrayref is passed as parameter, the B<jobnum>s of any export jobs will
92 be added to the array.
93
94 =cut
95
96 sub insert {
97   my $self = shift;
98   local $FS::queue::jobnums = shift if @_;
99   my $error;
100
101   local $SIG{HUP} = 'IGNORE';
102   local $SIG{INT} = 'IGNORE';
103   local $SIG{QUIT} = 'IGNORE';
104   local $SIG{TERM} = 'IGNORE';
105   local $SIG{TSTP} = 'IGNORE';
106   local $SIG{PIPE} = 'IGNORE';
107
108   my $oldAutoCommit = $FS::UID::AutoCommit;
109   local $FS::UID::AutoCommit = 0;
110   my $dbh = dbh;
111
112   $error = $self->check;
113   return $error if $error;
114
115   my $svcnum = $self->svcnum;
116   my $cust_svc;
117   unless ( $svcnum ) {
118     $cust_svc = new FS::cust_svc ( {
119       #hua?# 'svcnum'  => $svcnum,
120       'pkgnum'  => $self->pkgnum,
121       'svcpart' => $self->svcpart,
122     } );
123     $error = $cust_svc->insert;
124     if ( $error ) {
125       $dbh->rollback if $oldAutoCommit;
126       return $error;
127     }
128     $svcnum = $self->svcnum($cust_svc->svcnum);
129   } else {
130     $cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
131     unless ( $cust_svc ) {
132       $dbh->rollback if $oldAutoCommit;
133       return "no cust_svc record found for svcnum ". $self->svcnum;
134     }
135     $self->pkgnum($cust_svc->pkgnum);
136     $self->svcpart($cust_svc->svcpart);
137   }
138
139   $error = $self->SUPER::insert;
140   if ( $error ) {
141     $dbh->rollback if $oldAutoCommit;
142     return $error;
143   }
144
145   #new-style exports!
146   unless ( $noexport_hack ) {
147     foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
148       my $error = $part_export->export_insert($self);
149       if ( $error ) {
150         $dbh->rollback if $oldAutoCommit;
151         return "exporting to ". $part_export->exporttype.
152                " (transaction rolled back): $error";
153       }
154     }
155   }
156
157   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
158
159   '';
160 }
161
162 =item delete
163
164 Deletes this account from the database.  If there is an error, returns the
165 error, otherwise returns false.
166
167 The corresponding FS::cust_svc record will be deleted as well.
168
169 =cut
170
171 sub delete {
172   my $self = shift;
173   my $error;
174
175   local $SIG{HUP} = 'IGNORE';
176   local $SIG{INT} = 'IGNORE';
177   local $SIG{QUIT} = 'IGNORE';
178   local $SIG{TERM} = 'IGNORE';
179   local $SIG{TSTP} = 'IGNORE';
180   local $SIG{PIPE} = 'IGNORE';
181
182   my $svcnum = $self->svcnum;
183
184   my $oldAutoCommit = $FS::UID::AutoCommit;
185   local $FS::UID::AutoCommit = 0;
186   my $dbh = dbh;
187
188   $error = $self->SUPER::delete;
189   return $error if $error;
190
191   #new-style exports!
192   unless ( $noexport_hack ) {
193     foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
194       my $error = $part_export->export_delete($self);
195       if ( $error ) {
196         $dbh->rollback if $oldAutoCommit;
197         return "exporting to ". $part_export->exporttype.
198                " (transaction rolled back): $error";
199       }
200     }
201   }
202
203   return $error if $error;
204
205   my $cust_svc = $self->cust_svc;
206   $error = $cust_svc->delete;
207   return $error if $error;
208
209   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
210
211   '';
212 }
213
214 =item replace OLD_RECORD
215
216 Replaces OLD_RECORD with this one.  If there is an error, returns the error,
217 otherwise returns false.
218
219 =cut
220
221 sub replace {
222   my ($new, $old) = (shift, shift);
223
224   local $SIG{HUP} = 'IGNORE';
225   local $SIG{INT} = 'IGNORE';
226   local $SIG{QUIT} = 'IGNORE';
227   local $SIG{TERM} = 'IGNORE';
228   local $SIG{TSTP} = 'IGNORE';
229   local $SIG{PIPE} = 'IGNORE';
230
231   my $oldAutoCommit = $FS::UID::AutoCommit;
232   local $FS::UID::AutoCommit = 0;
233   my $dbh = dbh;
234
235   my $error = $new->SUPER::replace($old);
236   if ($error) {
237     $dbh->rollback if $oldAutoCommit;
238     return $error;
239   }
240
241   #new-style exports!
242   unless ( $noexport_hack ) {
243     foreach my $part_export ( $new->cust_svc->part_svc->part_export ) {
244       my $error = $part_export->export_replace($new,$old);
245       if ( $error ) {
246         $dbh->rollback if $oldAutoCommit;
247         return "error exporting to ". $part_export->exporttype.
248                " (transaction rolled back): $error";
249       }
250     }
251   }
252
253   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
254   '';
255 }
256
257
258 =item setfixed
259
260 Sets any fixed fields for this service (see L<FS::part_svc>).  If there is an
261 error, returns the error, otherwise returns the FS::part_svc object (use ref()
262 to test the return).  Usually called by the check method.
263
264 =cut
265
266 sub setfixed {
267   my $self = shift;
268   $self->setx('F');
269 }
270
271 =item setdefault
272
273 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
274 current values.  If there is an error, returns the error, otherwise returns
275 the FS::part_svc object (use ref() to test the return).
276
277 =cut
278
279 sub setdefault {
280   my $self = shift;
281   $self->setx('D');
282 }
283
284 sub setx {
285   my $self = shift;
286   my $x = shift;
287
288   my $error;
289
290   $error =
291     $self->ut_numbern('svcnum')
292   ;
293   return $error if $error;
294
295   #get part_svc
296   my $svcpart;
297   if ( $self->svcnum ) {
298     my $cust_svc = $self->cust_svc;
299     return "Unknown svcnum" unless $cust_svc; 
300     $svcpart = $cust_svc->svcpart;
301   } else {
302     $svcpart = $self->getfield('svcpart');
303   }
304   my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
305   return "Unkonwn svcpart" unless $part_svc;
306
307   #set default/fixed/whatever fields from part_svc
308   my $table = $self->table;
309   foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
310     my $part_svc_column = $part_svc->part_svc_column($field);
311     if ( $part_svc_column->columnflag eq $x ) {
312       $self->setfield( $field, $part_svc_column->columnvalue );
313     }
314   }
315
316  $part_svc;
317
318 }
319
320 =item cust_svc
321
322 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
323 object (see L<FS::cust_svc>).
324
325 =cut
326
327 sub cust_svc {
328   my $self = shift;
329   qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
330 }
331
332 =item suspend
333
334 Runs export_suspend callbacks.
335
336 =cut
337
338 sub suspend {
339   my $self = shift;
340
341   local $SIG{HUP} = 'IGNORE';
342   local $SIG{INT} = 'IGNORE';
343   local $SIG{QUIT} = 'IGNORE';
344   local $SIG{TERM} = 'IGNORE';
345   local $SIG{TSTP} = 'IGNORE';
346   local $SIG{PIPE} = 'IGNORE';
347
348   my $oldAutoCommit = $FS::UID::AutoCommit;
349   local $FS::UID::AutoCommit = 0;
350   my $dbh = dbh;
351
352   #new-style exports!
353   unless ( $noexport_hack ) {
354     foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
355       my $error = $part_export->export_suspend($self);
356       if ( $error ) {
357         $dbh->rollback if $oldAutoCommit;
358         return "error exporting to ". $part_export->exporttype.
359                " (transaction rolled back): $error";
360       }
361     }
362   }
363
364   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
365   '';
366
367 }
368
369 =item unsuspend
370
371 Runs export_unsuspend callbacks.
372
373 =cut
374
375 sub unsuspend {
376   my $self = shift;
377
378   local $SIG{HUP} = 'IGNORE';
379   local $SIG{INT} = 'IGNORE';
380   local $SIG{QUIT} = 'IGNORE';
381   local $SIG{TERM} = 'IGNORE';
382   local $SIG{TSTP} = 'IGNORE';
383   local $SIG{PIPE} = 'IGNORE';
384
385   my $oldAutoCommit = $FS::UID::AutoCommit;
386   local $FS::UID::AutoCommit = 0;
387   my $dbh = dbh;
388
389   #new-style exports!
390   unless ( $noexport_hack ) {
391     foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
392       my $error = $part_export->export_unsuspend($self);
393       if ( $error ) {
394         $dbh->rollback if $oldAutoCommit;
395         return "error exporting to ". $part_export->exporttype.
396                " (transaction rolled back): $error";
397       }
398     }
399   }
400
401   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
402   '';
403
404 }
405
406 =item cancel
407
408 Stub - returns false (no error) so derived classes don't need to define these
409 methods.  Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
410
411 =cut
412
413 sub cancel { ''; }
414
415 =back
416
417 =head1 VERSION
418
419 $Id: svc_Common.pm,v 1.13 2003-08-05 00:20:47 khoff Exp $
420
421 =head1 BUGS
422
423 The setfixed method return value.
424
425 =head1 SEE ALSO
426
427 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
428 from the base documentation.
429
430 =cut
431
432 1;
433