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