63bc2ad1c6846a1e567ed1d4d48b7ef3100d944e
[freeside.git] / FS / FS / part_svc.pm
1 package FS::part_svc;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearch qsearchs fields dbh );
6 use FS::part_svc_column;
7 use FS::part_export;
8 use FS::export_svc;
9
10 @ISA = qw(FS::Record);
11
12 =head1 NAME
13
14 FS::part_svc - Object methods for part_svc objects
15
16 =head1 SYNOPSIS
17
18   use FS::part_svc;
19
20   $record = new FS::part_svc \%hash
21   $record = new FS::part_svc { 'column' => 'value' };
22
23   $error = $record->insert;
24
25   $error = $new_record->replace($old_record);
26
27   $error = $record->delete;
28
29   $error = $record->check;
30
31 =head1 DESCRIPTION
32
33 An FS::part_svc represents a service definition.  FS::part_svc inherits from
34 FS::Record.  The following fields are currently supported:
35
36 =over 4
37
38 =item svcpart - primary key (assigned automatically for new service definitions)
39
40 =item svc - text name of this service definition
41
42 =item svcdb - table used for this service.  See L<FS::svc_acct>,
43 L<FS::svc_domain>, and L<FS::svc_forward>, among others.
44
45 =item disabled - Disabled flag, empty or `Y'
46
47 =back
48
49 =head1 METHODS
50
51 =over 4
52
53 =item new HASHREF
54
55 Creates a new service definition.  To add the service definition to the
56 database, see L<"insert">.
57
58 =cut
59
60 sub table { 'part_svc'; }
61
62 =item insert EXTRA_FIELDS_ARRAYREF
63
64 Adds this service definition to the database.  If there is an error, returns
65 the error, otherwise returns false.
66
67 TODOC:
68
69 =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
70
71 =item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null, `D' for default, or `F' for fixed
72
73 TODOC: EXTRA_FIELDS_ARRAYREF
74
75 =cut
76
77 sub insert {
78   my $self = shift;
79   my @fields = ();
80   @fields = @{shift(@_)} if @_;
81
82   local $SIG{HUP} = 'IGNORE';
83   local $SIG{INT} = 'IGNORE';
84   local $SIG{QUIT} = 'IGNORE';
85   local $SIG{TERM} = 'IGNORE';
86   local $SIG{TSTP} = 'IGNORE';
87   local $SIG{PIPE} = 'IGNORE';
88
89   my $oldAutoCommit = $FS::UID::AutoCommit;
90   local $FS::UID::AutoCommit = 0;
91   my $dbh = dbh;
92
93   my $error = $self->SUPER::insert;
94   if ( $error ) {
95     $dbh->rollback if $oldAutoCommit;
96     return $error;
97   }
98
99   my $svcdb = $self->svcdb;
100 #  my @rows = map { /^${svcdb}__(.*)$/; $1 }
101 #    grep ! /_flag$/,
102 #      grep /^${svcdb}__/,
103 #        fields('part_svc');
104   foreach my $field (
105     grep { $_ ne 'svcnum'
106            && defined( $self->getfield($svcdb.'__'.$_.'_flag') )
107          } (fields($svcdb), @fields)
108   ) {
109     my $part_svc_column = $self->part_svc_column($field);
110     my $previous = qsearchs('part_svc_column', {
111       'svcpart'    => $self->svcpart,
112       'columnname' => $field,
113     } );
114
115     my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
116     if ( uc($flag) =~ /^([DF])$/ ) {
117       $part_svc_column->setfield('columnflag', $1);
118       $part_svc_column->setfield('columnvalue',
119         $self->getfield($svcdb.'__'.$field)
120       );
121       if ( $previous ) {
122         $error = $part_svc_column->replace($previous);
123       } else {
124         $error = $part_svc_column->insert;
125       }
126     } else {
127       $error = $previous ? $previous->delete : '';
128     }
129     if ( $error ) {
130       $dbh->rollback if $oldAutoCommit;
131       return $error;
132     }
133
134   }
135
136   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
137
138   '';
139 }
140
141 =item delete
142
143 Currently unimplemented.
144
145 =cut
146
147 sub delete {
148   return "Can't (yet?) delete service definitions.";
149 # check & make sure the svcpart isn't in cust_svc or pkg_svc (in any packages)?
150 }
151
152 =item replace OLD_RECORD [ '1.3-COMPAT' [ , EXTRA_FIELDS_ARRAYREF ] ]
153
154 Replaces OLD_RECORD with this one in the database.  If there is an error,
155 returns the error, otherwise returns false.
156
157 TODOC: 1.3-COMPAT
158
159 TODOC: EXTRA_FIELDS_ARRAYREF
160
161 =cut
162
163 sub replace {
164   my ( $new, $old ) = ( shift, shift );
165
166   return "Can't change svcdb for an existing service definition!"
167     unless $old->svcdb eq $new->svcdb;
168
169   local $SIG{HUP} = 'IGNORE';
170   local $SIG{INT} = 'IGNORE';
171   local $SIG{QUIT} = 'IGNORE';
172   local $SIG{TERM} = 'IGNORE';
173   local $SIG{TSTP} = 'IGNORE';
174   local $SIG{PIPE} = 'IGNORE';
175
176   my $oldAutoCommit = $FS::UID::AutoCommit;
177   local $FS::UID::AutoCommit = 0;
178   my $dbh = dbh;
179
180   my $error = $new->SUPER::replace( $old );
181   if ( $error ) {
182     $dbh->rollback if $oldAutoCommit;
183     return $error;
184   }
185
186   if ( @_ && $_[0] eq '1.3-COMPAT' ) {
187     shift;
188     my @fields = ();
189     @fields = @{shift(@_)} if @_;
190
191     my $svcdb = $new->svcdb;
192     foreach my $field (
193       grep { $_ ne 'svcnum'
194              && defined( $new->getfield($svcdb.'__'.$_.'_flag') )
195            } (fields($svcdb),@fields)
196     ) {
197       my $part_svc_column = $new->part_svc_column($field);
198       my $previous = qsearchs('part_svc_column', {
199         'svcpart'    => $new->svcpart,
200         'columnname' => $field,
201       } );
202
203       my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
204       if ( uc($flag) =~ /^([DF])$/ ) {
205         $part_svc_column->setfield('columnflag', $1);
206         $part_svc_column->setfield('columnvalue',
207           $new->getfield($svcdb.'__'.$field)
208         );
209         if ( $previous ) {
210           $error = $part_svc_column->replace($previous);
211         } else {
212           $error = $part_svc_column->insert;
213         }
214       } else {
215         $error = $previous ? $previous->delete : '';
216       }
217       if ( $error ) {
218         $dbh->rollback if $oldAutoCommit;
219         return $error;
220       }
221     }
222   } else {
223     $dbh->rollback if $oldAutoCommit;
224     return 'non-1.3-COMPAT interface not yet written';
225     #not yet implemented
226   }
227
228   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
229
230   '';
231 }
232
233 =item check
234
235 Checks all fields to make sure this is a valid service definition.  If there is
236 an error, returns the error, otherwise returns false.  Called by the insert
237 and replace methods.
238
239 =cut
240
241 sub check {
242   my $self = shift;
243   my $recref = $self->hashref;
244
245   my $error;
246   $error=
247     $self->ut_numbern('svcpart')
248     || $self->ut_text('svc')
249     || $self->ut_alpha('svcdb')
250     || $self->ut_enum('disabled', [ '', 'Y' ] )
251   ;
252   return $error if $error;
253
254   my @fields = eval { fields( $recref->{svcdb} ) }; #might die
255   return "Unknown svcdb!" unless @fields;
256
257   ''; #no error
258 }
259
260 =item part_svc_column COLUMNNAME
261
262 Returns the part_svc_column object (see L<FS::part_svc_column>) for the given
263 COLUMNNAME, or a new part_svc_column object if none exists.
264
265 =cut
266
267 sub part_svc_column {
268   my( $self, $columnname) = @_;
269   $self->svcpart &&
270     qsearchs('part_svc_column',  {
271                                    'svcpart'    => $self->svcpart,
272                                    'columnname' => $columnname,
273                                  }
274   ) or new FS::part_svc_column {
275                                  'svcpart'    => $self->svcpart,
276                                  'columnname' => $columnname,
277                                };
278 }
279
280 =item all_part_svc_column
281
282 =cut
283
284 sub all_part_svc_column {
285   my $self = shift;
286   qsearch('part_svc_column', { 'svcpart' => $self->svcpart } );
287 }
288
289 =item part_export [ EXPORTTYPE ]
290
291 Returns all exports (see L<FS::part_export>) for this service, or, if an
292 export type is specified, only returns exports of the given type.
293
294 =cut
295
296 sub part_export {
297   my $self = shift;
298   my %search;
299   $search{'exporttype'} = shift if @_;
300   map { qsearchs('part_export', { 'exportnum' => $_->exportnum, %search } ) }
301     qsearch('export_svc', { 'svcpart' => $self->svcpart } );
302 }
303
304 =back
305
306 =head1 BUGS
307
308 Delete is unimplemented.
309
310 The list of svc_* tables is hardcoded.  When svc_acct_pop is renamed, this
311 should be fixed.
312
313 all_part_svc_column method should be documented
314
315 =head1 SEE ALSO
316
317 L<FS::Record>, L<FS::part_svc_column>, L<FS::part_pkg>, L<FS::pkg_svc>,
318 L<FS::cust_svc>, L<FS::svc_acct>, L<FS::svc_forward>, L<FS::svc_domain>,
319 schema.html from the base documentation.
320
321 =cut
322
323 1;
324