use FS::cust_svc;
use FS::part_pkg;
use FS::cust_main;
+use FS::cust_location;
use FS::type_pkgs;
use FS::pkg_svc;
use FS::cust_bill_pkg;
+use FS::cust_pkg_detail;
use FS::cust_event;
use FS::h_cust_svc;
use FS::reg_code;
=over 4
-=item pkgnum - primary key (assigned automatically for new billing items)
+=item pkgnum
-=item custnum - Customer (see L<FS::cust_main>)
+Primary key (assigned automatically for new billing items)
-=item pkgpart - Billing item definition (see L<FS::part_pkg>)
+=item custnum
-=item setup - date
+Customer (see L<FS::cust_main>)
-=item bill - date (next bill date)
+=item pkgpart
+
+Billing item definition (see L<FS::part_pkg>)
+
+=item locationnum
+
+Optional link to package location (see L<FS::location>)
+
+=item setup
+
+date
+
+=item bill
+
+date (next bill date)
+
+=item last_bill
+
+last bill date
+
+=item adjourn
+
+date
+
+=item susp
+
+date
-=item last_bill - last bill date
+=item expire
+
+date
+
+=item cancel
+
+date
-=item adjourn - date
+=item otaker
+
+order taker (assigned automatically if null, see L<FS::UID>)
+
+=item manual_flag
+
+If this field is set to 1, disables the automatic
+unsuspension of this package when using the B<unsuspendauto> config option.
+
+=item quantity
-=item susp - date
+If not set, defaults to 1
-=item expire - date
+=item change_date
-=item cancel - date
+Date of change from previous package
-=item otaker - order taker (assigned automatically if null, see L<FS::UID>)
+=item change_pkgnum
-=item manual_flag - If this field is set to 1, disables the automatic
-unsuspension of this package when using the B<unsuspendauto> config file.
+Previous pkgnum
-=item quantity - If not set, defaults to 1
+=item change_pkgpart
+
+Previous pkgpart
=back
-Note: setup, bill, adjourn, susp, expire and cancel are specified as UNIX timestamps;
-see L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for
-conversion functions.
+Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date
+are specified as UNIX timestamps; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
=head1 METHODS
refnums as keys. If no I<refnum> is defined, a default FS::pkg_referral
record will be created corresponding to cust_main.refnum.
-The following options are available: I<change>
+The following options are available:
+
+=over 4
+
+=item change
-I<change>, if set true, supresses any referral credit to a referring customer.
+If set true, supresses any referral credit to a referring customer.
+
+=item options
+
+cust_pkg_option records will be created
+
+=back
=cut
#}
my $conf = new FS::Conf;
- my $cust_main = $self->cust_main;
- my $part_pkg = $self->part_pkg;
- if ( $conf->exists('referral_credit')
- && $cust_main->referral_custnum
- && ! $options{'change'}
- && $part_pkg->freq !~ /^0\D?$/
- )
- {
- my $referring_cust_main = $cust_main->referring_cust_main;
- if ( $referring_cust_main->status ne 'cancelled' ) {
- my $error;
- if ( $part_pkg->freq !~ /^\d+$/ ) {
- warn 'WARNING: Not crediting customer '. $cust_main->referral_custnum.
- ' for package '. $self->pkgnum.
- ' ( customer '. $self->custnum. ')'.
- ' - One-time referral credits not (yet) available for '.
- ' packages with '. $part_pkg->freq_pretty. ' frequency';
- } else {
-
- my $amount = sprintf( "%.2f", $part_pkg->base_recur / $part_pkg->freq );
- my $error =
- $referring_cust_main->
- credit( $amount,
- 'Referral credit for '.$cust_main->name,
- 'reason_type' => $conf->config('referral_credit_type')
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error crediting customer ". $cust_main->referral_custnum.
- " for referral: $error";
- }
-
- }
-
- }
- }
if ($conf->config('welcome_letter') && $self->cust_main->num_pkgs == 1) {
my $queue = new FS::queue {
# return "Can't delete cust_pkg records!";
#}
-=item replace OLD_RECORD
+=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ]
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
cancel is normally updated by the cancel method (and also the order subroutine
in some cases).
-Calls
+Available options are:
+
+=over 4
+
+=item reason
+
+can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item reason_otaker
+
+the access_user (see L<FS::access_user>) providing the reason
+
+=item options
+
+hashref of keys and values - cust_pkg_option records will be created, updated or removed as appopriate
+
+=back
=cut
foreach my $method ( qw(adjourn expire) ) { # How many reasons?
if ($options->{'reason'} && $new->$method && $old->$method ne $new->$method) {
- my $error = $new->insert_reason( 'reason' => $options->{'reason'},
- 'date' => $new->$method,
- 'action' => $method,
- 'reason_otaker' => $options{'reason_otaker'},
- );
+ my $error = $new->insert_reason(
+ 'reason' => $options->{'reason'},
+ 'date' => $new->$method,
+ 'action' => $method,
+ 'reason_otaker' => $options->{'reason_otaker'},
+ );
if ( $error ) {
dbh->rollback if $oldAutoCommit;
return "Error inserting cust_pkg_reason: $error";
$self->ut_numbern('pkgnum')
|| $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
|| $self->ut_numbern('pkgpart')
+ || $self->ut_foreign_keyn('locationnum', 'location', 'locationnum')
|| $self->ut_numbern('setup')
|| $self->ut_numbern('bill')
|| $self->ut_numbern('susp')
if ( $options{'reason'} ) {
$error = $self->insert_reason( 'reason' => $options{'reason'},
'action' => $date ? 'expire' : 'cancel',
+ 'date' => $date ? $date : $cancel_time,
'reason_otaker' => $options{'reason_otaker'},
);
if ( $error ) {
return "Package $pkgnum expires before it would be suspended.";
}
+ my $suspend_time = $options{'time'} || time;
+
if ( $options{'reason'} ) {
$error = $self->insert_reason( 'reason' => $options{'reason'},
'action' => $date ? 'adjourn' : 'suspend',
+ 'date' => $date ? $date : $suspend_time,
'reason_otaker' => $options{'reason_otaker'},
);
if ( $error ) {
}
unless ( $date ) {
+
+ my @labels = ();
+
foreach my $cust_svc (
qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
+ my( $label, $value ) = $cust_svc->label;
+ push @labels, "$label: $value";
+ }
+ }
+
+ my $conf = new FS::Conf;
+ if ( $conf->config('suspend_email_admin') ) {
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'), #??? well as good as any
+ 'to' => $conf->config('suspend_email_admin'),
+ 'subject' => 'FREESIDE NOTIFICATION: Customer package suspended',
+ 'body' => [
+ "This is an automatic message from your Freeside installation\n",
+ "informing you that the following customer package has been suspended:\n",
+ "\n",
+ 'Customer: #'. $self->custnum. ' '. $self->cust_main->name. "\n",
+ 'Package : #'. $self->pkgnum. " (". $self->part_pkg->pkg_comment. ")\n",
+ ( map { "Service : $_\n" } @labels ),
+ ],
+ );
+
+ if ( $error ) {
+ warn "WARNING: can't send suspension admin email (suspending anyway): ".
+ "$error\n";
}
+
}
+
}
my %hash = $self->hash;
- $date ? ($hash{'adjourn'} = $date) : ($hash{'susp'} = time);
+ if ( $date ) {
+ $hash{'adjourn'} = $date;
+ } else {
+ $hash{'susp'} = $suspend_time;
+ }
my $new = new FS::cust_pkg ( \%hash );
$error = $new->replace( $self, options => { $self->options } );
if ( $error ) {
package, then unsuspends the package itself (clears the susp field and the
adjourn field if it is in the past).
-Available options are: I<adjust_next_bill>.
+Available options are:
+
+=over 4
+
+=item adjust_next_bill
-I<adjust_next_bill> can be set true to adjust the next bill date forward by
+Can be set true to adjust the next bill date forward by
the amount of time the account was inactive. This was set true by default
since 1.4.2 and 1.5.0pre6; however, starting with 1.7.0 this needs to be
explicitly requested. Price plans for which this makes sense (anniversary-date
based than prorate or subscription) could have an option to enable this
behaviour?
+=back
+
If there is an error, returns the error, otherwise returns false.
=cut
qsearch( 'cust_bill_pkg', { 'pkgnum' => $self->pkgnum } );
}
+=item cust_pkg_detail [ DETAILTYPE ]
+
+Returns any customer package details for this package (see
+L<FS::cust_pkg_detail>).
+
+DETAILTYPE can be set to "I" for invoice details or "C" for comments.
+
+=cut
+
+sub cust_pkg_detail {
+ my $self = shift;
+ my %hash = ( 'pkgnum' => $self->pkgnum );
+ $hash{detailtype} = shift if @_;
+ qsearch({
+ 'table' => 'cust_pkg_detail',
+ 'hashref' => \%hash,
+ 'order_by' => 'ORDER BY weight, pkgdetailnum',
+ });
+}
+
+=item set_cust_pkg_detail DETAILTYPE [ DETAIL, DETAIL, ... ]
+
+Sets customer package details for this package (see L<FS::cust_pkg_detail>).
+
+DETAILTYPE can be set to "I" for invoice details or "C" for comments.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub set_cust_pkg_detail {
+ my( $self, $detailtype, @details ) = @_;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $current ( $self->cust_pkg_detail($detailtype) ) {
+ my $error = $current->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error removing old detail: $error";
+ }
+ }
+
+ foreach my $detail ( @details ) {
+ my $cust_pkg_detail = new FS::cust_pkg_detail {
+ 'pkgnum' => $self->pkgnum,
+ 'detailtype' => $detailtype,
+ 'detail' => $detail,
+ };
+ my $error = $cust_pkg_detail->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error adding new detail: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
=item cust_event
Returns the new-style customer billing events (see L<FS::cust_event>) for this invoice.
=item h_labels_short END_TIMESTAMP [ START_TIMESTAMP ]
-Like h_labels, except returns a simple flat list, and shortens long
-(currently >5) lists of identical services to one line that lists the service
-label and the number of individual services rather than individual items.
+Like h_labels, except returns a simple flat list, and shortens long
+(currently >5 or the cust_bill-max_same_services configuration value) lists of
+identical services to one line that lists the service label and the number of
+individual services rather than individual items.
=cut
sub h_labels_short {
my $self = shift;
+ my $conf = new FS::Conf;
+ my $max_same_services = $conf->config('cust_bill-max_same_services') || 5;
+
my %labels;
#tie %labels, 'Tie::IxHash';
push @{ $labels{$_->[0]} }, $_->[1]
foreach $self->h_labels(@_);
my @labels;
foreach my $label ( keys %labels ) {
- my @values = @{ $labels{$label} };
+ my %seen = ();
+ my @values = grep { ! $seen{$_}++ } @{ $labels{$label} };
my $num = scalar(@values);
- if ( $num > 5 ) {
+ if ( $num > $max_same_services ) {
push @labels, "$label ($num)";
} else {
push @labels, map { "$label: $_" } @values;
qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
}
+=item cust_location
+
+Returns the location object, if any (see L<FS::cust_location>).
+
+=cut
+
+sub cust_location {
+ my $self = shift;
+ return '' unless $self->locationnum;
+ qsearchs( 'cust_main', { 'locationnum' => $self->locationnum } );
+}
+
+=item cust_location_or_main
+
+If this package is associated with a location, returns the locaiton (see
+L<FS::cust_location>), otherwise returns the customer (see L<FS::cust_main>).
+
+=cut
+
+sub cust_location_or_main {
+ my $self = shift;
+ $self->cust_location || $self->cust_main;
+}
+
=item seconds_since TIMESTAMP
Returns the number of seconds all accounts (see L<FS::svc_acct>) in this
=over 4
-=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+=item reason
+
+can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item reason_otaker
+
+the access_user (see L<FS::access_user>) providing the reason
+
+=item date
-=item otaker_reason - the access_user (see L<FS::access_user>) providing the reason
+a unix timestamp
-=item date - a unix timestamp
+=item action
-=item action - the action (cancel, susp, adjourn, expire) associated with the reason
+the action (cancel, susp, adjourn, expire) associated with the reason
=back