Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / export_svc.pm
1 package FS::export_svc;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::part_export;
7 use FS::part_svc;
8 use FS::svc_export_machine;
9
10 @ISA = qw(FS::Record);
11
12 =head1 NAME
13
14 FS::export_svc - Object methods for export_svc records
15
16 =head1 SYNOPSIS
17
18   use FS::export_svc;
19
20   $record = new FS::export_svc \%hash;
21   $record = new FS::export_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::export_svc object links a service definition (see L<FS::part_svc>) to
34 an export (see L<FS::part_export>).  FS::export_svc inherits from FS::Record.
35 The following fields are currently supported:
36
37 =over 4
38
39 =item exportsvcnum - primary key
40
41 =item exportnum - export (see L<FS::part_export>)
42
43 =item svcpart - service definition (see L<FS::part_svc>)
44
45 =back
46
47 =head1 METHODS
48
49 =over 4
50
51 =item new HASHREF
52
53 Creates a new record.  To add the record to the database, see L<"insert">.
54
55 Note that this stores the hash reference, not a distinct copy of the hash it
56 points to.  You can ask the object for a copy with the I<hash> method.
57
58 =cut
59
60 # the new method can be inherited from FS::Record, if a table method is defined
61
62 sub table { 'export_svc'; }
63
64 =item insert [ JOB, OFFSET, MULTIPLIER ]
65
66 Adds this record to the database.  If there is an error, returns the error,
67 otherwise returns false.
68
69 TODOC: JOB, OFFSET, MULTIPLIER
70
71 =cut
72
73 sub insert {
74   my $self = shift;
75   my( $job, $offset, $mult ) = ( '', 0, 100);
76   $job = shift if @_;
77   $offset = shift if @_;
78   $mult = shift if @_;
79
80   local $SIG{HUP} = 'IGNORE';
81   local $SIG{INT} = 'IGNORE';
82   local $SIG{QUIT} = 'IGNORE';
83   local $SIG{TERM} = 'IGNORE';
84   local $SIG{TSTP} = 'IGNORE';
85   local $SIG{PIPE} = 'IGNORE';
86
87   my $oldAutoCommit = $FS::UID::AutoCommit;
88   local $FS::UID::AutoCommit = 0;
89   my $dbh = dbh;
90
91   my $error = $self->check;
92   return $error if $error;
93
94   #check for duplicates!
95   my @checks = ();
96   my $svcdb = $self->part_svc->svcdb;
97   if ( $svcdb eq 'svc_acct' ) {
98
99     if ( $self->part_export->nodomain =~ /^Y/i ) {
100       push @checks, {
101         label  => 'usernames',
102         method => 'username',
103         sortby => sub { $a cmp $b },
104       };
105     } else {
106       push @checks, {
107         label  => 'username@domain',
108         method => 'email',
109         sortby => sub {
110                         my($auser, $adomain) = split('@', $a);
111                         my($buser, $bdomain) = split('@', $b);
112                         $adomain cmp $bdomain || $auser cmp $buser;
113                       },
114       };
115     }
116
117     unless ( $self->part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
118       push @checks, {
119         label  => 'uids',
120         method => 'uid',
121         sortby => sub { $a <=> $b },
122       };
123     }
124
125   } elsif ( $svcdb eq 'svc_domain' ) {
126     push @checks, {
127       label  => 'domains',
128       method => 'domain',
129       sortby => sub { $a cmp $b },
130     };
131   } else {
132     warn "WARNING: No duplicate checking done on merge of $svcdb exports";
133   }
134
135   if ( @checks ) {
136   
137     my $done = 0;
138     my $percheck = $mult / scalar(@checks);
139
140     foreach my $check ( @checks ) {
141   
142       if ( $job ) {
143         $error = $job->update_statustext(int( $offset + ($done+.33) *$percheck ));
144         if ( $error ) {
145           $dbh->rollback if $oldAutoCommit;
146           return $error;
147         }
148       }
149   
150       my @current_svc = $self->part_export->svc_x;
151       #warn "current: ". scalar(@current_svc). " $current_svc[0]\n";
152   
153       if ( $job ) {
154         $error = $job->update_statustext(int( $offset + ($done+.67) *$percheck ));
155         if ( $error ) {
156           $dbh->rollback if $oldAutoCommit;
157           return $error;
158         }
159       }
160   
161       my @new_svc = $self->part_svc->svc_x;
162       #warn "new: ". scalar(@new_svc). " $new_svc[0]\n";
163   
164       if ( $job ) {
165         $error = $job->update_statustext(int( $offset + ($done+1) *$percheck ));
166         if ( $error ) {
167           $dbh->rollback if $oldAutoCommit;
168           return $error;
169         }
170       }
171   
172       my $method = $check->{'method'};
173       my %cur_svc = map { $_->$method() => $_ } @current_svc;
174       my @dup_svc = grep { $cur_svc{$_->$method()} } @new_svc;
175       #my @diff_customer = grep { 
176       #                           $_->cust_pkg->custnum != $cur_svc{$_->$method()}->cust_pkg->custnum
177       #                         } @dup_svc;
178   
179   
180   
181       if ( @dup_svc ) { #aye, that's the rub
182         #error out for now, eventually accept different options of adjustments
183         # to make to allow us to continue forward
184         $dbh->rollback if $oldAutoCommit;
185   
186         my @diff_customer_svc = grep {
187           my $cust_pkg = $_->cust_svc->cust_pkg;
188           my $custnum = $cust_pkg ? $cust_pkg->custnum : 0;
189           my $other_cust_pkg = $cur_svc{$_->$method()}->cust_svc->cust_pkg;
190           my $other_custnum = $other_cust_pkg ? $other_cust_pkg->custnum : 0;
191           $custnum != $other_custnum;
192         } @dup_svc;
193   
194         my $label = $check->{'label'};
195         my $sortby = $check->{'sortby'};
196         return "Can't export ".
197                $self->part_svc->svcpart.':'.$self->part_svc->svc. " service to ".
198                $self->part_export->exportnum.':'.$self->part_export->exporttype.
199                  ' on '. $self->part_export->machine.
200                ' : '. scalar(@dup_svc). " duplicate $label".
201                ' ('. scalar(@diff_customer_svc). " from different customers)".
202                ": ". join(', ', sort $sortby map { $_->$method() } @dup_svc )
203                #": ". join(', ', sort $sortby map { $_->$method() } @diff_customer_svc )
204                ;
205       }
206   
207       $done++;
208     }
209
210   } #end of duplicate check, whew
211
212   $error = $self->SUPER::insert;
213
214   my $part_export = $self->part_export;
215   if ( !$error and $part_export->default_machine ) {
216     foreach my $cust_svc ( $self->part_svc->cust_svc ) {
217       my $svc_export_machine = FS::svc_export_machine->new({
218           'exportnum'   => $self->exportnum,
219           'svcnum'      => $cust_svc->svcnum,
220           'machinenum'  => $part_export->default_machine,
221       });
222       $error ||= $svc_export_machine->insert;
223     }
224   }
225
226   if ( $error ) {
227     $dbh->rollback if $oldAutoCommit;
228     return $error;
229   }
230
231 #  if ( $self->part_svc->svcdb eq 'svc_acct' ) {
232 #
233 #    if ( $self->part_export->nodomain =~ /^Y/i ) {
234 #
235 #      select username from svc_acct where svcpart = $svcpart
236 #        group by username having count(*) > 1;
237 #
238 #    } else {
239 #
240 #      select username, domain
241 #        from   svc_acct
242 #          join svc_domain on ( svc_acct.domsvc = svc_domain.svcnum )
243 #        group by username, domain having count(*) > 1;
244 #
245 #    }
246 #
247 #  } elsif ( $self->part_svc->svcdb eq 'svc_domain' ) {
248 #
249 #    #similar but easier domain checking one
250 #
251 #  } #etc.?
252 #
253 #  my @services =
254 #    map  { $_->part_svc }
255 #    grep { $_->svcpart != $self->svcpart }
256 #         $self->part_export->export_svc;
257
258   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
259   ''; #no error
260 }
261
262 =item delete
263
264 Delete this record from the database.
265
266 =cut
267
268 sub delete {
269   my $self = shift;
270   my $dbh = dbh;
271   my $oldAutoCommit = $FS::UID::AutoCommit;
272   local $FS::UID::AutoCommit = 0;
273
274   my $error = $self->SUPER::delete;
275   foreach ($self->svc_export_machine) {
276     $error ||= $_->delete;
277   }
278   if ( $error ) {
279     $dbh->rollback if $oldAutoCommit;
280     return $error;
281   }
282   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
283 }
284
285
286 =item replace OLD_RECORD
287
288 Replaces the OLD_RECORD with this one in the database.  If there is an error,
289 returns the error, otherwise returns false.
290
291 =cut
292
293 # the replace method can be inherited from FS::Record
294
295 =item check
296
297 Checks all fields to make sure this is a valid record.  If there is
298 an error, returns the error, otherwise returns false.  Called by the insert
299 and replace methods.
300
301 =cut
302
303 # the check method should currently be supplied - FS::Record contains some
304 # data checking routines
305
306 sub check {
307   my $self = shift;
308
309   $self->ut_numbern('exportsvcnum')
310     || $self->ut_number('exportnum')
311     || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
312     || $self->ut_number('svcpart')
313     || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
314     || $self->SUPER::check
315   ;
316 }
317
318 =item part_export
319
320 Returns the FS::part_export object (see L<FS::part_export>).
321
322 =cut
323
324 sub part_export {
325   my $self = shift;
326   qsearchs( 'part_export', { 'exportnum' => $self->exportnum } );
327 }
328
329 =item part_svc
330
331 Returns the FS::part_svc object (see L<FS::part_svc>).
332
333 =cut
334
335 sub part_svc {
336   my $self = shift;
337   qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
338 }
339
340 =item svc_export_machine
341
342 Returns all export hostname records (L<FS::svc_export_machine>) for this
343 combination of svcpart and exportnum.
344
345 =cut
346
347 sub svc_export_machine {
348   my $self = shift;
349   qsearch({
350     'table'     => 'svc_export_machine',
351     'select'    => 'svc_export_machine.*',
352     'addl_from' => 'JOIN cust_svc USING (svcnum)',
353     'hashref'   => { 'exportnum' => $self->exportnum },
354     'extra_sql' => ' AND cust_svc.svcpart = '.$self->svcpart,
355   });
356 }
357
358 =back
359
360 =head1 BUGS
361
362 =head1 SEE ALSO
363
364 L<FS::part_export>, L<FS::part_svc>, L<FS::Record>, schema.html from the base
365 documentation.
366
367 =cut
368
369 1;
370