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