1 package FS::svc_Common;
4 use vars qw( @ISA $noexport_hack $DEBUG );
6 use FS::Record qw( qsearch qsearchs fields dbh );
7 use FS::cust_main_Mixin;
12 use FS::inventory_item;
13 use FS::inventory_class;
15 @ISA = qw( FS::cust_main_Mixin FS::Record );
21 FS::svc_Common - Object method for all svc_ records
27 @ISA = qw( FS::svc_Common );
31 FS::svc_Common is intended as a base class for table-specific classes to
32 inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
42 my $class = ref($proto) || $proto;
44 bless ($self, $class);
46 unless ( defined ( $self->table ) ) {
47 $self->{'Table'} = shift;
48 carp "warning: FS::Record::new called with table name ". $self->{'Table'};
51 #$self->{'Hash'} = shift;
53 $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
55 $self->{'Hash'}{$_} = $newhash->{$_}
56 foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
59 foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
60 $self->{'Hash'}{$field}='';
63 $self->_rebless if $self->can('_rebless');
65 $self->{'modified'} = 0;
67 $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
74 # This restricts the fields based on part_svc_column and the svcpart of
75 # the service. There are four possible cases:
76 # 1. svcpart passed as part of the svc_x hash.
77 # 2. svcpart fetched via cust_svc based on svcnum.
78 # 3. No svcnum or svcpart. In this case, return ALL the fields with
79 # dbtable eq $self->table.
80 # 4. Called via "fields('svc_acct')" or something similar. In this case
81 # there is no $self object.
85 my @vfields = $self->SUPER::virtual_fields;
87 return @vfields unless (ref $self); # Case 4
89 if ($self->svcpart) { # Case 1
90 $svcpart = $self->svcpart;
91 } elsif ( $self->svcnum
92 && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} )
94 $svcpart = $self->cust_svc->svcpart;
99 if ($svcpart) { #Cases 1 and 2
100 my %flags = map { $_->columnname, $_->columnflag } (
101 qsearch ('part_svc_column', { svcpart => $svcpart } )
103 return grep { not ($flags{$_} eq 'X') } @vfields;
112 Checks the validity of fields in this record.
114 At present, this does nothing but call FS::Record::check (which, in turn,
115 does nothing but run virtual field checks).
124 =item insert [ , OPTION => VALUE ... ]
126 Adds this record to the database. If there is an error, returns the error,
127 otherwise returns false.
129 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
130 defined. An FS::cust_svc record will be created and inserted.
132 Currently available options are: I<jobnums>, I<child_objects> and
135 If I<jobnum> is set to an array reference, the jobnums of any export jobs will
136 be added to the referenced array.
138 If I<child_objects> is set to an array reference of FS::tablename objects (for
139 example, FS::acct_snarf objects), they will have their svcnum field set and
140 will be inserted after this record, but before any exports are run. Each
141 element of the array can also optionally be a two-element array reference
142 containing the child object and the name of an alternate field to be filled in
143 with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
145 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
146 jobnums), all provisioning jobs will have a dependancy on the supplied
147 jobnum(s) (they will not run until the specific job(s) complete(s)).
154 warn "FS::svc_Common::insert called with options ".
155 join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
159 local $FS::queue::jobnums = \@jobnums;
160 warn "FS::svc_Common::insert: set \$FS::queue::jobnums to $FS::queue::jobnums"
162 my $objects = $options{'child_objects'} || [];
163 my $depend_jobnums = $options{'depend_jobnum'} || [];
164 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
167 local $SIG{HUP} = 'IGNORE';
168 local $SIG{INT} = 'IGNORE';
169 local $SIG{QUIT} = 'IGNORE';
170 local $SIG{TERM} = 'IGNORE';
171 local $SIG{TSTP} = 'IGNORE';
172 local $SIG{PIPE} = 'IGNORE';
174 my $oldAutoCommit = $FS::UID::AutoCommit;
175 local $FS::UID::AutoCommit = 0;
178 $error = $self->check;
179 return $error if $error;
181 my $svcnum = $self->svcnum;
182 my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
183 #unless ( $svcnum ) {
184 if ( !$svcnum or !$cust_svc ) {
185 $cust_svc = new FS::cust_svc ( {
186 #hua?# 'svcnum' => $svcnum,
187 'svcnum' => $self->svcnum,
188 'pkgnum' => $self->pkgnum,
189 'svcpart' => $self->svcpart,
191 $error = $cust_svc->insert;
193 $dbh->rollback if $oldAutoCommit;
196 $svcnum = $self->svcnum($cust_svc->svcnum);
198 #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
199 unless ( $cust_svc ) {
200 $dbh->rollback if $oldAutoCommit;
201 return "no cust_svc record found for svcnum ". $self->svcnum;
203 $self->pkgnum($cust_svc->pkgnum);
204 $self->svcpart($cust_svc->svcpart);
207 $error = $self->set_auto_inventory;
209 $dbh->rollback if $oldAutoCommit;
213 $error = $self->SUPER::insert;
215 $dbh->rollback if $oldAutoCommit;
219 foreach my $object ( @$objects ) {
221 if ( ref($object) eq 'ARRAY' ) {
222 ($obj, $field) = @$object;
227 $obj->$field($self->svcnum);
228 $error = $obj->insert;
230 $dbh->rollback if $oldAutoCommit;
236 unless ( $noexport_hack ) {
238 warn "FS::svc_Common::insert: \$FS::queue::jobnums is $FS::queue::jobnums"
241 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
242 my $error = $part_export->export_insert($self);
244 $dbh->rollback if $oldAutoCommit;
245 return "exporting to ". $part_export->exporttype.
246 " (transaction rolled back): $error";
250 foreach my $depend_jobnum ( @$depend_jobnums ) {
251 warn "inserting dependancies on supplied job $depend_jobnum\n"
253 foreach my $jobnum ( @jobnums ) {
254 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
255 warn "inserting dependancy for job $jobnum on $depend_jobnum\n"
257 my $error = $queue->depend_insert($depend_jobnum);
259 $dbh->rollback if $oldAutoCommit;
260 return "error queuing job dependancy: $error";
267 if ( exists $options{'jobnums'} ) {
268 push @{ $options{'jobnums'} }, @jobnums;
271 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
278 Deletes this account from the database. If there is an error, returns the
279 error, otherwise returns false.
281 The corresponding FS::cust_svc record will be deleted as well.
289 local $SIG{HUP} = 'IGNORE';
290 local $SIG{INT} = 'IGNORE';
291 local $SIG{QUIT} = 'IGNORE';
292 local $SIG{TERM} = 'IGNORE';
293 local $SIG{TSTP} = 'IGNORE';
294 local $SIG{PIPE} = 'IGNORE';
296 my $svcnum = $self->svcnum;
298 my $oldAutoCommit = $FS::UID::AutoCommit;
299 local $FS::UID::AutoCommit = 0;
302 $error = $self->SUPER::delete;
303 return $error if $error;
306 unless ( $noexport_hack ) {
307 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
308 $error = $part_export->export_delete($self);
310 $dbh->rollback if $oldAutoCommit;
311 return "exporting to ". $part_export->exporttype.
312 " (transaction rolled back): $error";
317 $error = $self->return_inventory;
319 $dbh->rollback if $oldAutoCommit;
320 return "error returning inventory: $error";
323 my $cust_svc = $self->cust_svc;
324 $error = $cust_svc->delete;
326 $dbh->rollback if $oldAutoCommit;
330 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
335 =item replace OLD_RECORD
337 Replaces OLD_RECORD with this one. If there is an error, returns the error,
338 otherwise returns false.
343 my ($new, $old) = (shift, shift);
345 local $SIG{HUP} = 'IGNORE';
346 local $SIG{INT} = 'IGNORE';
347 local $SIG{QUIT} = 'IGNORE';
348 local $SIG{TERM} = 'IGNORE';
349 local $SIG{TSTP} = 'IGNORE';
350 local $SIG{PIPE} = 'IGNORE';
352 my $oldAutoCommit = $FS::UID::AutoCommit;
353 local $FS::UID::AutoCommit = 0;
356 my $error = $new->set_auto_inventory;
358 $dbh->rollback if $oldAutoCommit;
362 $error = $new->SUPER::replace($old);
364 $dbh->rollback if $oldAutoCommit;
369 unless ( $noexport_hack ) {
371 #not quite false laziness, but same pattern as FS::svc_acct::replace and
372 #FS::part_export::sqlradius::_export_replace. List::Compare or something
373 #would be useful but too much of a pain in the ass to deploy
375 my @old_part_export = $old->cust_svc->part_svc->part_export;
376 my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
377 my @new_part_export =
379 ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
380 : $new->cust_svc->part_svc->part_export;
381 my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
383 foreach my $delete_part_export (
384 grep { ! $new_exportnum{$_->exportnum} } @old_part_export
386 my $error = $delete_part_export->export_delete($old);
388 $dbh->rollback if $oldAutoCommit;
389 return "error deleting, export to ". $delete_part_export->exporttype.
390 " (transaction rolled back): $error";
394 foreach my $replace_part_export (
395 grep { $old_exportnum{$_->exportnum} } @new_part_export
397 my $error = $replace_part_export->export_replace($new,$old);
399 $dbh->rollback if $oldAutoCommit;
400 return "error exporting to ". $replace_part_export->exporttype.
401 " (transaction rolled back): $error";
405 foreach my $insert_part_export (
406 grep { ! $old_exportnum{$_->exportnum} } @new_part_export
408 my $error = $insert_part_export->export_insert($new);
410 $dbh->rollback if $oldAutoCommit;
411 return "error inserting export to ". $insert_part_export->exporttype.
412 " (transaction rolled back): $error";
418 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
425 Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
426 error, returns the error, otherwise returns the FS::part_svc object (use ref()
427 to test the return). Usually called by the check method.
433 $self->setx('F', @_);
438 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
439 current values. If there is an error, returns the error, otherwise returns
440 the FS::part_svc object (use ref() to test the return).
446 $self->setx('D', @_ );
449 =item set_default_and_fixed
453 sub set_default_and_fixed {
455 $self->setx( [ 'D', 'F' ], @_ );
458 =item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
460 Sets fields according to the passed in flag or arrayref of flags.
462 Optionally, a hashref of field names and callback coderefs can be passed.
463 If a coderef exists for a given field name, instead of setting the field,
464 the coderef is called with the column value (part_svc_column.columnvalue)
465 as the single parameter.
472 my @x = ref($x) ? @$x : ($x);
473 my %coderef = @_ ? shift : {};
476 $self->ut_numbern('svcnum')
478 return $error if $error;
480 my $part_svc = $self->part_svc;
481 return "Unkonwn svcpart" unless $part_svc;
483 #set default/fixed/whatever fields from part_svc
485 foreach my $part_svc_column (
486 grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
487 $part_svc->all_part_svc_column
490 my $columnname = $part_svc_column->columnname;
491 my $columnvalue = $part_svc_column->columnvalue;
493 if ( exists( $coderef{columnname} ) ) {
494 &{ $coderef{$columnname} }( $self, $columnvalue);
496 $self->setfield( $columnname, $columnvalue );
510 if ( $self->get('svcpart') ) {
511 $svcpart = $self->get('svcpart');
512 } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
513 my $cust_svc = $self->cust_svc;
514 return "Unknown svcnum" unless $cust_svc;
515 $svcpart = $cust_svc->svcpart;
518 qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
522 =item set_auto_inventory
524 Sets any fields which auto-populate from inventory (see L<FS::part_svc>).
525 If there is an error, returns the error, otherwise returns false.
529 sub set_auto_inventory {
533 $self->ut_numbern('svcnum')
535 return $error if $error;
537 my $part_svc = $self->part_svc;
538 return "Unkonwn svcpart" unless $part_svc;
540 local $SIG{HUP} = 'IGNORE';
541 local $SIG{INT} = 'IGNORE';
542 local $SIG{QUIT} = 'IGNORE';
543 local $SIG{TERM} = 'IGNORE';
544 local $SIG{TSTP} = 'IGNORE';
545 local $SIG{PIPE} = 'IGNORE';
547 my $oldAutoCommit = $FS::UID::AutoCommit;
548 local $FS::UID::AutoCommit = 0;
551 #set default/fixed/whatever fields from part_svc
552 my $table = $self->table;
553 foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
554 my $part_svc_column = $part_svc->part_svc_column($field);
555 if ( $part_svc_column->columnflag eq 'A' && $self->$field() eq '' ) {
557 my $classnum = $part_svc_column->columnvalue;
558 my $inventory_item = qsearchs({
559 'table' => 'inventory_item',
560 'hashref' => { 'classnum' => $classnum,
563 'extra_sql' => 'LIMIT 1 FOR UPDATE',
566 unless ( $inventory_item ) {
567 $dbh->rollback if $oldAutoCommit;
568 my $inventory_class =
569 qsearchs('inventory_class', { 'classnum' => $classnum } );
570 return "Can't find inventory_class.classnum $classnum"
571 unless $inventory_class;
572 return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
576 $inventory_item->svcnum( $self->svcnum );
577 my $ierror = $inventory_item->replace();
579 $dbh->rollback if $oldAutoCommit;
580 return "Error provisioning inventory: $ierror";
584 $self->setfield( $field, $inventory_item->item );
589 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
595 =item return_inventory
599 sub return_inventory {
602 local $SIG{HUP} = 'IGNORE';
603 local $SIG{INT} = 'IGNORE';
604 local $SIG{QUIT} = 'IGNORE';
605 local $SIG{TERM} = 'IGNORE';
606 local $SIG{TSTP} = 'IGNORE';
607 local $SIG{PIPE} = 'IGNORE';
609 my $oldAutoCommit = $FS::UID::AutoCommit;
610 local $FS::UID::AutoCommit = 0;
613 foreach my $inventory_item ( $self->inventory_item ) {
614 $inventory_item->svcnum('');
615 my $error = $inventory_item->replace();
617 $dbh->rollback if $oldAutoCommit;
618 return "Error returning inventory: $error";
622 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
629 Returns the inventory items associated with this svc_ record, as
630 FS::inventory_item objects (see L<FS::inventory_item>.
637 'table' => 'inventory_item',
638 'hashref' => { 'svcnum' => $self->svcnum, },
644 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
645 object (see L<FS::cust_svc>).
651 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
656 Runs export_suspend callbacks.
663 local $SIG{HUP} = 'IGNORE';
664 local $SIG{INT} = 'IGNORE';
665 local $SIG{QUIT} = 'IGNORE';
666 local $SIG{TERM} = 'IGNORE';
667 local $SIG{TSTP} = 'IGNORE';
668 local $SIG{PIPE} = 'IGNORE';
670 my $oldAutoCommit = $FS::UID::AutoCommit;
671 local $FS::UID::AutoCommit = 0;
675 unless ( $noexport_hack ) {
676 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
677 my $error = $part_export->export_suspend($self);
679 $dbh->rollback if $oldAutoCommit;
680 return "error exporting to ". $part_export->exporttype.
681 " (transaction rolled back): $error";
686 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
693 Runs export_unsuspend callbacks.
700 local $SIG{HUP} = 'IGNORE';
701 local $SIG{INT} = 'IGNORE';
702 local $SIG{QUIT} = 'IGNORE';
703 local $SIG{TERM} = 'IGNORE';
704 local $SIG{TSTP} = 'IGNORE';
705 local $SIG{PIPE} = 'IGNORE';
707 my $oldAutoCommit = $FS::UID::AutoCommit;
708 local $FS::UID::AutoCommit = 0;
712 unless ( $noexport_hack ) {
713 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
714 my $error = $part_export->export_unsuspend($self);
716 $dbh->rollback if $oldAutoCommit;
717 return "error exporting to ". $part_export->exporttype.
718 " (transaction rolled back): $error";
723 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
730 Stub - returns false (no error) so derived classes don't need to define these
731 methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
737 =item clone_suspended
739 Constructor used by FS::part_export::_export_suspend fallback. Stub returning
740 same object for svc_ classes which don't implement a suspension fallback
741 (everything except svc_acct at the moment). Document better.
745 sub clone_suspended {
749 =item clone_kludge_unsuspend
751 Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
752 same object for svc_ classes which don't implement a suspension fallback
753 (everything except svc_acct at the moment). Document better.
757 sub clone_kludge_unsuspend {
765 The setfixed method return value.
769 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
770 from the base documentation.