=item svcpart - Service definition (see L<FS::part_svc>)
+=item agent_svcid - Optional legacy service ID
+
=item overlimit - date the service exceeded its usage limit
=back
error, otherwise returns false. Note that this only removes the cust_svc
record - you should probably use the B<cancel> method instead.
+=cut
+
+sub delete {
+ my $self = shift;
+ my $error = $self->SUPER::delete;
+ return $error if $error;
+
+ if ( FS::Conf->new->config('ticket_system') eq 'RT_Internal' ) {
+ FS::TicketSystem->init;
+ my $session = FS::TicketSystem->session;
+ my $links = RT::Links->new($session->{CurrentUser});
+ my $svcnum = $self->svcnum;
+ $links->Limit(FIELD => 'Target',
+ VALUE => 'freeside://freeside/cust_svc/'.$svcnum);
+ while ( my $l = $links->Next ) {
+ my ($val, $msg) = $l->Delete;
+ # can't do anything useful on error
+ warn "error unlinking ticket $svcnum: $msg\n" if !$val;
+ }
+ }
+}
+
=item cancel
Cancels the relevant service by calling the B<cancel> method of the associated
=cut
sub cancel {
- my $self = shift;
+ my($self,%opt) = @_;
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
my $svc = $self->svc_x;
if ($svc) {
-
- my $error = $svc->cancel;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error canceling service: $error";
- }
- $error = $svc->delete; #this deletes this cust_svc record as well
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error deleting service: $error";
+ if ( %opt && $opt{'date'} ) {
+ my $error = $svc->expire($opt{'date'});
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error expiring service: $error";
+ }
+ } else {
+ my $error = $svc->cancel;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error canceling service: $error";
+ }
+ $error = $svc->delete; #this deletes this cust_svc record as well
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error deleting service: $error";
+ }
}
- } else {
+ } elsif ( !%opt ) {
#huh?
warn "WARNING: no svc_ record found for svcnum ". $self->svcnum.
$self->ut_numbern('svcnum')
|| $self->ut_numbern('pkgnum')
|| $self->ut_number('svcpart')
+ || $self->ut_numbern('agent_svcid')
|| $self->ut_numbern('overlimit')
;
return $error if $error;
if ( $self->pkgnum ) {
my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
return "Unknown pkgnum" unless $cust_pkg;
- my $pkg_svc = qsearchs( 'pkg_svc', {
- 'pkgpart' => $cust_pkg->pkgpart,
- 'svcpart' => $self->svcpart,
- });
- # or new FS::pkg_svc ( { 'pkgpart' => $cust_pkg->pkgpart,
- # 'svcpart' => $self->svcpart,
- # 'quantity' => 0 } );
- my $quantity = $pkg_svc ? $pkg_svc->quantity : 0;
-
- my @cust_svc = qsearch('cust_svc', {
- 'pkgnum' => $self->pkgnum,
- 'svcpart' => $self->svcpart,
- });
- return "Already ". scalar(@cust_svc). " ". $part_svc->svc.
+ ($part_svc) = grep { $_->svcpart == $self->svcpart } $cust_pkg->part_svc;
+ return "No svcpart ". $self->svcpart.
+ " services in pkgpart ". $cust_pkg->pkgpart
+ unless $part_svc || $ignore_quantity;
+ return "Already ". $part_svc->get('num_cust_svc'). " ". $part_svc->svc.
" services for pkgnum ". $self->pkgnum
- if scalar(@cust_svc) >= $quantity && !$ignore_quantity;
+ if !$ignore_quantity && $part_svc->get('num_avail') <= 0 ;
}
$self->SUPER::check;
}
+=item display_svcnum
+
+Returns the displayed service number for this service: agent_svcid if it has a
+value, svcnum otherwise
+
+=cut
+
+sub display_svcnum {
+ my $self = shift;
+ $self->agent_svcid || $self->svcnum;
+}
+
=item part_svc
Returns the definition for this service, as a FS::part_svc object (see
$self->h_date('insert');
}
+=item pkg_cancel_date
+
+Returns the date this service's package was canceled. This normally only
+exists for a service that's been preserved through cancellation with the
+part_pkg.preserve flag.
+
+=cut
+
+sub pkg_cancel_date {
+ my $self = shift;
+ my $cust_pkg = $self->cust_pkg or return;
+ return $cust_pkg->getfield('cancel') || '';
+}
+
=item label
Returns a list consisting of:
=cut
-#note: implementation here, POD in FS::svc_acct
-sub seconds_since {
- my($self, $since) = @_;
- my $dbh = dbh;
- my $sth = $dbh->prepare(' SELECT SUM(logout-login) FROM session
- WHERE svcnum = ?
- AND login >= ?
- AND logout IS NOT NULL'
- ) or die $dbh->errstr;
- $sth->execute($self->svcnum, $since) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
+#internal session db deprecated (or at least on hold)
+sub seconds_since { 'internal session db deprecated'; };
+##note: implementation here, POD in FS::svc_acct
+#sub seconds_since {
+# my($self, $since) = @_;
+# my $dbh = dbh;
+# my $sth = $dbh->prepare(' SELECT SUM(logout-login) FROM session
+# WHERE svcnum = ?
+# AND login >= ?
+# AND logout IS NOT NULL'
+# ) or die $dbh->errstr;
+# $sth->execute($self->svcnum, $since) or die $sth->errstr;
+# $sth->fetchrow_arrayref->[0];
+#}
=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
}
+=item tickets
+
+Returns an array of hashes representing the tickets linked to this service.
+
+=cut
+
+sub tickets {
+ my $self = shift;
+
+ my $conf = FS::Conf->new;
+ my $num = $conf->config('cust_main-max_tickets') || 10;
+ my @tickets = ();
+
+ if ( $conf->config('ticket_system') ) {
+ unless ( $conf->config('ticket_system-custom_priority_field') ) {
+
+ @tickets = @{ FS::TicketSystem->service_tickets($self->svcnum, $num) };
+
+ } else {
+
+ foreach my $priority (
+ $conf->config('ticket_system-custom_priority_field-values'), ''
+ ) {
+ last if scalar(@tickets) >= $num;
+ push @tickets,
+ @{ FS::TicketSystem->service_tickets( $self->svcnum,
+ $num - scalar(@tickets),
+ $priority,
+ )
+ };
+ }
+ }
+ }
+ (@tickets);
+}
+
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item smart_search OPTION => VALUE ...
+
+Accepts the option I<search>, the string to search for. The string will
+be searched for as a username, email address, IP address, MAC address,
+phone number, and hardware serial number. Unlike the I<smart_search> on
+customers, this always requires an exact match.
+
+=cut
+
+# though perhaps it should be fuzzy in some cases?
+
+sub smart_search {
+ my %param = __PACKAGE__->smart_search_param(@_);
+ qsearch(\%param);
+}
+
+sub smart_search_param {
+ my $class = shift;
+ my %opt = @_;
+
+ my $string = $opt{'search'};
+ $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace
+
+ my @or =
+ map { my $table = $_;
+ my $search_sql = "FS::$table"->search_sql($string);
+ " ( svcdb = '$table'
+ AND 0 < ( SELECT COUNT(*) FROM $table
+ WHERE $table.svcnum = cust_svc.svcnum
+ AND $search_sql
+ )
+ ) ";
+ }
+ FS::part_svc->svc_tables;
+
+ if ( $string =~ /^(\d+)$/ ) {
+ unshift @or, " ( agent_svcid IS NOT NULL AND agent_svcid = $1 ) ";
+ }
+
+ my @extra_sql = ' ( '. join(' OR ', @or). ' ) ';
+
+ push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+ my $extra_sql = ' WHERE '.join(' AND ', @extra_sql);
+ #for agentnum
+ my $addl_from = ' LEFT JOIN cust_pkg USING ( pkgnum )'.
+ ' LEFT JOIN cust_main USING ( custnum )'.
+ ' LEFT JOIN part_svc USING ( svcpart )';
+
+ (
+ 'table' => 'cust_svc',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ );
+}
+
=back
=head1 BUGS