#!/usr/bin/perl use strict; use FS::UID qw(adminsuidsetup); use FS::Record qw(qsearch qsearchs); use FS::cust_pkg; use FS::part_pkg; my $user = shift or die &usage; my @pkgparts = @ARGV or die &usage; my $dbh = adminsuidsetup $user; $FS::UID::AutoCommit = 0; my %stats = ( mainpkgs => 0, created => 0, linked => 0, errors => 0, ); my %pkg_freq; # cache foreach my $pkgpart (@pkgparts) { my $part_pkg = FS::part_pkg->by_key($pkgpart) or die "pkgpart $pkgpart not found.\n"; $pkg_freq{$pkgpart} = $part_pkg->freq; my @links = $part_pkg->supp_part_pkg_link or die "pkgpart $pkgpart has no supplemental packages.\n"; CUST_PKG: foreach my $cust_pkg ( qsearch('cust_pkg', { 'pkgpart' => $pkgpart, 'cancel' => '', }) ) { my $cust_main = $cust_pkg->cust_main; my @existing = $cust_pkg->supplemental_pkgs; my @active = grep { !$_->main_pkgnum } $cust_main->ncancelled_pkgs; LINK: foreach my $link (@links) { # yeah, it's expensive # see if there's an existing package with this link identity foreach (@existing) { if ($_->pkglinknum == $link->pkglinknum) { next LINK; } } # no? then is there one with this pkgpart? my $i = 0; foreach (@active) { if ( $_->pkgpart == $link->dst_pkgpart ) { set_link($cust_pkg, $link, $_); splice(@active, $i, 1); # delete it so we don't reuse it next LINK; } } # no? then create one create_linked($cust_pkg, $link); } #foreach $link $stats{mainpkgs}++; } #foreach $cust_pkg } #foreach $pkgpart print " Main packages: $stats{mainpkgs} Supplemental packages linked: $stats{linked} Supplemental packages ordered: $stats{created} Errors: $stats{errors} "; $dbh->commit or die $dbh->errstr; sub set_link { my ($main_pkg, $part_pkg_link, $supp_pkg) = @_; my $task = "linking package ".$supp_pkg->pkgnum. " to package ".$main_pkg->pkgnum; $supp_pkg->set('main_pkgnum', $main_pkg->pkgnum); $supp_pkg->set('pkglinknum', $part_pkg_link->pkglinknum); # Set the next bill date of the supplemental package to the nearest one in # the future that lines up with the main package. If the main package # hasn't started billing yet, use its future start date. my $new_bill = $main_pkg->get('bill') || $main_pkg->get('start_date'); if ( $new_bill ) { my $old_bill = $supp_pkg->get('bill'); my $diff = $new_bill - $old_bill; my $main_freq = $pkg_freq{$main_pkg->pkgpart}; my $prev_bill = 0; while ($diff < 0) { # this will exit once $new_bill has overtaken the existing bill date. # if there is no existing bill date, then this will exit right away # and set bill to the bill date of the main package, which is correct. $prev_bill = $new_bill; $new_bill = FS::part_pkg->add_freq($new_bill, $main_freq); $diff = $new_bill - $old_bill; } # then, of $new_bill and $prev_bill, pick the one that's closer to $old_bill if ( $prev_bill > 0 and $new_bill - $old_bill > $old_bill - $prev_bill ) { $supp_pkg->set('bill', $prev_bill); } else { $supp_pkg->set('bill', $new_bill); } } else { # otherwise the main package hasn't been billed yet and has no # start date, so we can't sync the supplemental to it yet. # but we can still link them. warn "$task: main package has no next bill date.\n"; } my $error = $supp_pkg->replace; if ( $error ) { warn "$task:\n $error\n"; $stats{errors}++; } else { $stats{linked}++; } return; } sub create_linked { my ($main_pkg, $part_pkg_link) = @_; my $task = "creating pkgpart ".$part_pkg_link->dst_pkgpart. " supplemental to package ".$main_pkg->pkgnum; my $supp_pkg = FS::cust_pkg->new({ 'pkgpart' => $part_pkg_link->dst_pkgpart, 'pkglinknum' => $part_pkg_link->pkglinknum, 'custnum' => $main_pkg->custnum, 'main_pkgnum' => $main_pkg->pkgnum, 'locationnum' => $main_pkg->locationnum, 'start_date' => $main_pkg->start_date, 'order_date' => $main_pkg->order_date, 'expire' => $main_pkg->expire, 'adjourn' => $main_pkg->adjourn, 'contract_end' => $main_pkg->contract_end, 'susp' => $main_pkg->susp, 'bill' => $main_pkg->bill, 'refnum' => $main_pkg->refnum, 'discountnum' => $main_pkg->discountnum, 'waive_setup' => $main_pkg->waive_setup, }); my $error = $supp_pkg->insert; if ( $error ) { warn "$task:\n $error\n"; $stats{errors}++; } else { $stats{created}++; } return; } sub usage { die "Usage:\n fs-migrate-supplemental user main_pkgpart\n"; }