+ "SELECT $table.svcnum AS svcnum, '$table' AS svcdb ".
+ "FROM $table $addl_from WHERE $search_sql";
+ }
+ FS::part_svc->svc_tables;
+
+ if ( $string =~ /^(\d+)$/ ) {
+ unshift @or, "SELECT cust_svc.svcnum, NULL as svcdb FROM cust_svc WHERE agent_svcid = $1";
+ }
+
+ my $addl_from = " RIGHT JOIN (\n" . join("\nUNION\n", @or) . "\n) AS svc_all ".
+ " ON (svc_all.svcnum = cust_svc.svcnum) ";
+
+ my @extra_sql;
+
+ push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+ my $extra_sql = ' WHERE '.join(' AND ', @extra_sql);
+ #for agentnum
+ $addl_from .= ' LEFT JOIN cust_pkg USING ( pkgnum )'.
+ FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg').
+ ' LEFT JOIN part_svc USING ( svcpart )';
+
+ (
+ 'table' => 'cust_svc',
+ 'select' => 'svc_all.svcnum AS svcnum, '.
+ 'COALESCE(svc_all.svcdb, part_svc.svcdb) AS svcdb, '.
+ 'cust_svc.*',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ );
+}
+
+# If the associated cust_pkg is 'on hold'
+# and the associated pkg_svc has the provision_hold flag
+# and there are no more available_part_svcs on the cust_pkg similarly flagged,
+# then removes hold from pkg
+# returns $error or '' on success,
+# does not indicate if pkg status was changed
+sub _check_provision_hold {
+ my $self = shift;
+
+ # check status of cust_pkg
+ my $cust_pkg = $self->cust_pkg;
+ return '' unless $cust_pkg && $cust_pkg->status eq 'on hold';
+
+ # check flag on this svc
+ # small false laziness with $self->pkg_svc
+ # to avoid looking up cust_pkg twice
+ my $pkg_svc = qsearchs( 'pkg_svc', {
+ 'svcpart' => $self->svcpart,
+ 'pkgpart' => $cust_pkg->pkgpart,
+ });
+ return '' unless $pkg_svc->provision_hold;
+
+ # check for any others available with that flag
+ return '' if $cust_pkg->available_part_svc( 'provision_hold' => 1 );
+
+ # conditions met, remove hold
+ return $cust_pkg->unsuspend;
+}
+
+sub _upgrade_data {
+ my $class = shift;
+
+ # fix missing (deleted by mistake) svc_x records
+ warn "searching for missing svc_x records...\n";
+ my %search = (
+ 'table' => 'cust_svc',
+ 'select' => 'cust_svc.*',
+ 'addl_from' => ' LEFT JOIN ( ' .
+ join(' UNION ',
+ map { "SELECT svcnum FROM $_" }
+ FS::part_svc->svc_tables
+ ) . ' ) AS svc_all ON cust_svc.svcnum = svc_all.svcnum',
+ 'extra_sql' => ' WHERE svc_all.svcnum IS NULL',
+ );
+ my @svcs = qsearch(\%search);
+ warn "found ".scalar(@svcs)."\n";
+
+ local $FS::Record::nowarn_classload = 1; # for h_svc_
+ local $FS::svc_Common::noexport_hack = 1; # because we're inserting services
+
+ my %h_search = (
+ 'hashref' => { history_action => 'delete' },
+ 'order_by' => ' ORDER BY history_date DESC LIMIT 1',
+ );
+ foreach my $cust_svc (@svcs) {
+ my $svcnum = $cust_svc->svcnum;
+ my $svcdb = $cust_svc->part_svc->svcdb;
+ $h_search{'hashref'}{'svcnum'} = $svcnum;
+ $h_search{'table'} = "h_$svcdb";
+ my $h_svc_x = qsearchs(\%h_search);
+ if ( $h_svc_x ) {
+ my $class = "FS::$svcdb";
+ my $new_svc_x = $class->new({ $h_svc_x->hash });
+ my $error = $new_svc_x->insert;
+ warn "error repairing svcnum $svcnum ($svcdb) from history:\n$error\n"
+ if $error;
+ } else {
+ # can't be fixed, so remove the dangling cust_svc to avoid breaking
+ # stuff
+ my $error = $cust_svc->delete;
+ warn "error cleaning up missing svcnum $svcnum ($svcdb):\n$error\n";
+ }