summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2013-01-12 12:03:16 -0800
committerMark Wells <mark@freeside.biz>2013-01-12 12:07:06 -0800
commitb70b0d8c6f571a68ffb60c5ca728a230926abee4 (patch)
tree27e6d37c746c8eb5a4a9e257297d98d569bd5a1b
parentdd825e780ad1e7d520f5c2d7f99c0f67fe892781 (diff)
supplemental packages, #20689
-rw-r--r--FS/FS/Schema.pm2
-rw-r--r--FS/FS/cust_main/Billing.pm25
-rw-r--r--FS/FS/cust_main/Packages.pm31
-rw-r--r--FS/FS/cust_pkg.pm173
-rw-r--r--FS/FS/part_pkg.pm11
-rw-r--r--FS/FS/part_pkg_link.pm22
-rwxr-xr-xhttemplate/browse/part_pkg.cgi13
-rwxr-xr-xhttemplate/edit/REAL_cust_pkg.cgi87
-rwxr-xr-xhttemplate/edit/cust_pkg.cgi36
-rwxr-xr-xhttemplate/edit/part_pkg.cgi14
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi57
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi9
-rwxr-xr-xhttemplate/misc/confirm-cust_pkg-edit_dates.html283
-rw-r--r--httemplate/search/elements/search-html.html12
-rw-r--r--httemplate/search/elements/search.html5
-rwxr-xr-xhttemplate/view/cust_main/packages.html25
-rw-r--r--httemplate/view/cust_main/packages/package.html80
-rwxr-xr-xhttemplate/view/cust_main/packages/section.html52
-rw-r--r--httemplate/view/cust_main/packages/status.html144
19 files changed, 877 insertions, 204 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 69e21da..5ac2b5f 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1738,6 +1738,8 @@ sub tables_hashref {
'change_pkgnum', 'int', 'NULL', '', '', '',
'change_pkgpart', 'int', 'NULL', '', '', '',
'change_locationnum', 'int', 'NULL', '', '', '',
+ 'main_pkgnum', 'int', 'NULL', '', '', '',
+ 'pkglinknum', 'int', 'NULL', '', '', '',
'manual_flag', 'char', 'NULL', 1, '', '',
'no_auto', 'char', 'NULL', 1, '', '',
'quantity', 'int', 'NULL', '', '', '',
diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm
index cd46c73..deb5e84 100644
--- a/FS/FS/cust_main/Billing.pm
+++ b/FS/FS/cust_main/Billing.pm
@@ -410,6 +410,7 @@ sub bill {
my @precommit_hooks = ();
$options{'pkg_list'} ||= [ $self->ncancelled_pkgs ]; #param checks?
+
foreach my $cust_pkg ( @{ $options{'pkg_list'} } ) {
next if $options{'not_pkgpart'}->{$cust_pkg->pkgpart};
@@ -1031,9 +1032,31 @@ sub _make_lines {
if ( $increment_next_bill ) {
- my $next_bill = $part_pkg->add_freq($sdate, $options{freq_override} || 0);
+ my $next_bill;
+
+ if ( my $main_pkg = $cust_pkg->main_pkg ) {
+ # supplemental package
+ # to keep in sync with the main package, simulate billing at
+ # its frequency
+ my $main_pkg_freq = $main_pkg->part_pkg->freq;
+ my $supp_pkg_freq = $part_pkg->freq;
+ my $ratio = $supp_pkg_freq / $main_pkg_freq;
+ if ( $ratio != int($ratio) ) {
+ # the UI should prevent setting up packages like this, but just
+ # in case
+ return "supplemental package period is not an integer multiple of main package period";
+ }
+ $next_bill = $sdate;
+ for (1..$ratio) {
+ $next_bill = $part_pkg->add_freq( $next_bill, $main_pkg_freq );
+ }
+
+ } else {
+ # the normal case
+ $next_bill = $part_pkg->add_freq($sdate, $options{freq_override} || 0);
return "unparsable frequency: ". $part_pkg->freq
if $next_bill == -1;
+ }
#pro-rating magic - if $recur_prog fiddled $sdate, want to use that
# only for figuring next bill date, nothing else, so, reset $sdate again
diff --git a/FS/FS/cust_main/Packages.pm b/FS/FS/cust_main/Packages.pm
index 395cce7..ee38f04 100644
--- a/FS/FS/cust_main/Packages.pm
+++ b/FS/FS/cust_main/Packages.pm
@@ -29,6 +29,9 @@ These methods are available on FS::cust_main objects;
Orders a single package.
+Note that if the package definition has supplemental packages, those will
+be ordered as well.
+
Options may be passed as a list of key/value pairs or as a hash reference.
Options are:
@@ -141,6 +144,34 @@ sub order_pkg {
}
}
+ # add supplemental packages, if any are needed
+ my $part_pkg = FS::part_pkg->by_key($cust_pkg->pkgpart);
+ foreach my $link ($part_pkg->supp_part_pkg_link) {
+ warn "inserting supplemental package ".$link->dst_pkgpart;
+ my $pkg = FS::cust_pkg->new({
+ 'pkgpart' => $link->dst_pkgpart,
+ 'pkglinknum' => $link->pkglinknum,
+ 'custnum' => $self->custnum,
+ 'main_pkgnum' => $cust_pkg->pkgnum,
+ 'locationnum' => $cust_pkg->locationnum,
+ # try to prevent as many surprises as possible
+ 'pkgbatch' => $cust_pkg->pkgbatch,
+ 'start_date' => $cust_pkg->start_date,
+ 'order_date' => $cust_pkg->order_date,
+ 'expire' => $cust_pkg->expire,
+ 'adjourn' => $cust_pkg->adjourn,
+ 'contract_end' => $cust_pkg->contract_end,
+ 'refnum' => $cust_pkg->refnum,
+ 'discountnum' => $cust_pkg->discountnum,
+ 'waive_setup' => $cust_pkg->waive_setup,
+ });
+ $error = $self->order_pkg('cust_pkg' => $pkg);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting supplemental package: $error";
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no error
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 22a7b2c..6d85a11 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -197,6 +197,15 @@ Previous locationnum
=item waive_setup
+=item main_pkgnum
+
+The pkgnum of the package that this package is supplemental to, if any.
+
+=item pkglinknum
+
+The package link (L<FS::part_pkg_link>) that defines this supplemental
+package, if it is one.
+
=back
Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date
@@ -616,6 +625,8 @@ sub check {
|| $self->ut_numbern('agent_pkgid')
|| $self->ut_enum('recur_show_zero', [ '', 'Y', 'N', ])
|| $self->ut_enum('setup_show_zero', [ '', 'Y', 'N', ])
+ || $self->ut_foreign_keyn('main_pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_foreign_keyn('pkglinknum', 'part_pkg_link', 'pkglinknum')
;
return $error if $error;
@@ -730,6 +741,11 @@ sub cancel {
my( $self, %options ) = @_;
my $error;
+ # pass all suspend/cancel actions to the main package
+ if ( $self->main_pkgnum and !$options{'from_main'} ) {
+ return $self->main_pkg->cancel(%options);
+ }
+
my $conf = new FS::Conf;
warn "cust_pkg::cancel called with options".
@@ -835,6 +851,14 @@ sub cancel {
return $error;
}
+ foreach my $supp_pkg ( $self->supplemental_pkgs ) {
+ $error = $supp_pkg->cancel(%options, 'from_main' => 1);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "canceling supplemental pkg#".$supp_pkg->pkgnum.": $error";
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
return '' if $date; #no errors
@@ -894,6 +918,9 @@ svc_fatal: service provisioning errors are fatal
svc_errors: pass an array reference, will be filled in with any provisioning errors
+main_pkgnum: link the package as a supplemental package of this one. For
+internal use only.
+
=cut
sub uncancel {
@@ -902,6 +929,10 @@ sub uncancel {
#in case you try do do $uncancel-date = $cust_pkg->uncacel
return '' unless $self->get('cancel');
+ if ( $self->main_pkgnum and !$options{'main_pkgnum'} ) {
+ return $self->main_pkg->uncancel(%options);
+ }
+
##
# Transaction-alize
##
@@ -926,6 +957,7 @@ sub uncancel {
bill => ( $options{'bill'} || $self->get('bill') ),
uncancel => time,
uncancel_pkgnum => $self->pkgnum,
+ main_pkgnum => ($options{'main_pkgnum'} || ''),
map { $_ => $self->get($_) } qw(
custnum pkgpart locationnum
setup
@@ -1023,6 +1055,20 @@ sub uncancel {
}
##
+ # Uncancel any supplemental packages, and make them supplemental to the
+ # new one.
+ ##
+
+ foreach my $supp_pkg ( $self->supplemental_pkgs ) {
+ my $new_pkg;
+ $error = $supp_pkg->uncancel(%options, 'main_pkgnum' => $cust_pkg->pkgnum);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "canceling supplemental pkg#".$supp_pkg->pkgnum.": $error";
+ }
+ }
+
+ ##
# Finish
##
@@ -1111,6 +1157,9 @@ of final invoices or unused-time credits
unsuspended. This may be more convenient than calling C<unsuspend()>
separately.
+=item from_main - allows a supplemental package to be suspended, rather
+than redirecting the method call to its main package. For internal use.
+
=back
If there is an error, returns the error, otherwise returns false.
@@ -1121,6 +1170,11 @@ sub suspend {
my( $self, %options ) = @_;
my $error;
+ # pass all suspend/cancel actions to the main package
+ if ( $self->main_pkgnum and !$options{'from_main'} ) {
+ return $self->main_pkg->suspend(%options);
+ }
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -1271,6 +1325,14 @@ sub suspend {
}
+ foreach my $supp_pkg ( $self->supplemental_pkgs ) {
+ $error = $supp_pkg->suspend(%options, 'from_main' => 1);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "suspending supplemental pkg#".$supp_pkg->pkgnum.": $error";
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no errors
@@ -1353,6 +1415,11 @@ sub unsuspend {
my( $self, %opt ) = @_;
my $error;
+ # pass all suspend/cancel actions to the main package
+ if ( $self->main_pkgnum and !$opt{'from_main'} ) {
+ return $self->main_pkg->unsuspend(%opt);
+ }
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -1511,6 +1578,14 @@ sub unsuspend {
}
+ foreach my $supp_pkg ( $self->supplemental_pkgs ) {
+ $error = $supp_pkg->unsuspend(%opt, 'from_main' => 1);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "unsuspending supplemental pkg#".$supp_pkg->pkgnum.": $error";
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no errors
@@ -1700,7 +1775,6 @@ sub change {
locationnum => ( $opt->{'locationnum'} ),
%hash,
};
-
$error = $cust_pkg->insert( 'change' => 1 );
if ($error) {
$dbh->rollback if $oldAutoCommit;
@@ -1749,11 +1823,63 @@ sub change {
}
}
+ # Order any supplemental packages.
+ my $part_pkg = $cust_pkg->part_pkg;
+ my @old_supp_pkgs = $self->supplemental_pkgs;
+ my @new_supp_pkgs;
+ foreach my $link ($part_pkg->supp_part_pkg_link) {
+ my $old;
+ foreach (@old_supp_pkgs) {
+ if ($_->pkgpart == $link->dst_pkgpart) {
+ $old = $_;
+ $_->pkgpart(0); # so that it can't match more than once
+ }
+ last if $old;
+ }
+ # false laziness with FS::cust_main::Packages::order_pkg
+ my $new = FS::cust_pkg->new({
+ pkgpart => $link->dst_pkgpart,
+ pkglinknum => $link->pkglinknum,
+ custnum => $self->custnum,
+ main_pkgnum => $cust_pkg->pkgnum,
+ locationnum => $cust_pkg->locationnum,
+ start_date => $cust_pkg->start_date,
+ order_date => $cust_pkg->order_date,
+ expire => $cust_pkg->expire,
+ adjourn => $cust_pkg->adjourn,
+ contract_end => $cust_pkg->contract_end,
+ refnum => $cust_pkg->refnum,
+ discountnum => $cust_pkg->discountnum,
+ waive_setup => $cust_pkg->waive_setup
+ });
+ if ( $old and $opt->{'keep_dates'} ) {
+ foreach (qw(setup bill last_bill)) {
+ $new->set($_, $old->get($_));
+ }
+ }
+ $error = $new->insert;
+ # transfer services
+ if ( $old ) {
+ $error ||= $old->transfer($new);
+ }
+ if ( $error and $error > 0 ) {
+ # no reason why this should ever fail, but still...
+ $error = "Unable to transfer all services from supplemental package ".
+ $old->pkgnum;
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ push @new_supp_pkgs, $new;
+ }
+
#Good to go, cancel old package. Notify 'cancel' of whether to credit
#remaining time.
#Don't allow billing the package (preceding period packages and/or
#outstanding usage) if we are keeping dates (i.e. location changing),
#because the new package will be billed for the same date range.
+ #Supplemental packages are also canceled here.
$error = $self->cancel(
quiet => 1,
unused_credit => $unused_credit,
@@ -1766,7 +1892,9 @@ sub change {
if ( $conf->exists('cust_pkg-change_pkgpart-bill_now') ) {
#$self->cust_main
- my $error = $cust_pkg->cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
+ my $error = $cust_pkg->cust_main->bill(
+ 'pkg_list' => [ $cust_pkg, @new_supp_pkgs ]
+ );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -3139,6 +3267,31 @@ sub cust_pkg_discount_active {
=back
+=item supplemental_pkgs
+
+Returns a list of all packages supplemental to this one.
+
+=cut
+
+sub supplemental_pkgs {
+ my $self = shift;
+ qsearch('cust_pkg', { 'main_pkgnum' => $self->pkgnum });
+}
+
+=item main_pkg
+
+Returns the package that this one is supplemental to, if any.
+
+=cut
+
+sub main_pkg {
+ my $self = shift;
+ if ( $self->main_pkgnum ) {
+ return FS::cust_pkg->by_key($self->main_pkgnum);
+ }
+ return;
+}
+
=head1 CLASS METHODS
=over 4
@@ -3951,11 +4104,25 @@ sub order {
%hash,
};
$error = $cust_pkg->insert( 'change' => $change );
+ push @$return_cust_pkg, $cust_pkg;
+
+ foreach my $link ($cust_pkg->part_pkg->supp_part_pkg_link) {
+ my $supp_pkg = FS::cust_pkg->new({
+ custnum => $custnum,
+ pkgpart => $link->dst_pkgpart,
+ refnum => $refnum,
+ main_pkgnum => $cust_pkg->pkgnum,
+ %hash,
+ });
+ $error ||= $supp_pkg->insert( 'change' => $change );
+ push @$return_cust_pkg, $supp_pkg;
+ }
+
if ($error) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
- push @$return_cust_pkg, $cust_pkg;
+
}
# $return_cust_pkg now contains refs to all of the newly
# created packages.
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 6e7f8f8..d4c420f 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -1175,6 +1175,17 @@ sub svc_part_pkg_link {
shift->_part_pkg_link('svc', @_);
}
+=item supp_part_pkg_link
+
+Returns the associated part_pkg_link records of type 'supp' (supplemental
+packages).
+
+=cut
+
+sub supp_part_pkg_link {
+ shift->_part_pkg_link('supp', @_);
+}
+
sub _part_pkg_link {
my( $self, $type ) = @_;
qsearch({ table => 'part_pkg_link',
diff --git a/FS/FS/part_pkg_link.pm b/FS/FS/part_pkg_link.pm
index fb7a8d3..9ce8e6a 100644
--- a/FS/FS/part_pkg_link.pm
+++ b/FS/FS/part_pkg_link.pm
@@ -49,12 +49,13 @@ Destination package (see L<FS::part_pkg>)
=item link_type
Link type - currently, "bill" (source package bills a line item from target
-package), or "svc" (source package includes services from target package).
+package), or "svc" (source package includes services from target package),
+or "supp" (ordering source package creates a target package).
=item hidden
Flag indicating that this subpackage should be felt, but not seen as an invoice
-line item when set to 'Y'
+line item when set to 'Y'. Not allowed for "supp" links.
=back
@@ -119,11 +120,26 @@ sub check {
$self->ut_numbern('pkglinknum')
|| $self->ut_foreign_key('src_pkgpart', 'part_pkg', 'pkgpart')
|| $self->ut_foreign_key('dst_pkgpart', 'part_pkg', 'pkgpart')
- || $self->ut_enum('link_type', [ 'bill', 'svc' ] )
+ || $self->ut_enum('link_type', [ 'bill', 'svc', 'supp' ] )
|| $self->ut_enum('hidden', [ '', 'Y' ] )
;
return $error if $error;
+ if ( $self->link_type eq 'supp' ) {
+ # some sanity checking
+ my $src_pkg = $self->src_pkg;
+ my $dst_pkg = $self->dst_pkg;
+ if ( $src_pkg->freq eq '0' and $dst_pkg->freq ne '0' ) {
+ return "One-time charges can't have supplemental packages."
+ } elsif ( $dst_pkg->freq ne '0' ) {
+ my $ratio = $dst_pkg->freq / $src_pkg->freq;
+ if ($ratio != int($ratio)) {
+ return "Supplemental package period (pkgpart ".$dst_pkg->pkgpart.
+ ") must be an integer multiple of main package period.";
+ }
+ }
+ }
+
$self->SUPER::check;
}
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 57a4297..5b19a30 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -20,6 +20,7 @@
'fields' => \@fields,
'links' => \@links,
'align' => $align,
+ 'link_field' => 'pkgpart',
)
%>
<%init>
@@ -274,6 +275,18 @@ push @fields, sub {
: ()
),
],
+ ( map { my $dst_pkg = $_->dst_pkg;
+ [
+ { data => 'Supplemental: &nbsp;'.
+ '<A HREF="#'. $dst_pkg->pkgpart . '">' .
+ $dst_pkg->pkg . '</A>',
+ align=> 'center',
+ colspan => 2,
+ }
+ ]
+ }
+ $part_pkg->supp_part_pkg_link
+ ),
( map {
my $dst_pkg = $_->dst_pkg;
[
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
index 166a3b7..4bcf55c 100755
--- a/httemplate/edit/REAL_cust_pkg.cgi
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -9,6 +9,29 @@
<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript">
+var submit_fields = [];
+function confirm_changes() {
+ var i;
+ var querystring = 'pkgnum=<%$pkgnum%>';
+ var f = document.forms.formname;
+ for(i = 0; i < submit_fields.length; i++) {
+ querystring += ';'
+ + submit_fields[i]
+ + '='
+ + encodeURIComponent(f.elements[submit_fields[i] + '_text'].value);
+ }
+ overlib(
+ OLiframeContent(
+ '<%$p%>/misc/confirm-cust_pkg-edit_dates.html?' + querystring,
+ 576, 576, 'confirm_popup'
+ ),
+ CAPTION, 'Package date changes', STICKY, AUTOSTATUSCAP, CLOSETEXT, '',
+ MIDX, 0, MIDY, 0, DRAGGABLE, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ TEXTSIZE, 3
+ );
+}
+</SCRIPT>
<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">
<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
@@ -31,6 +54,15 @@
<TD BGCOLOR="#ffffff"><% $part_pkg->pkg %></TD>
</TR>
+% if ( $cust_pkg->main_pkgnum ) {
+% my $main_pkg = $cust_pkg->main_pkg;
+ <TR>
+ <TD ALIGN="right">Supplemental to</TD>
+ <TD BGCOLOR="#ffffff">Package #<% $cust_pkg->main_pkgnum%>:&nbsp;\
+ <% $main_pkg->part_pkg->pkg %></TD>
+ </TR>
+
+% }
<TR>
<TD ALIGN="right">Custom</TD>
<TD BGCOLOR="#ffffff"><% $part_pkg->custom %></TD>
@@ -50,14 +82,14 @@
% if ( $cust_pkg->setup && ! $cust_pkg->start_date ) {
<& .row_display, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
% } else {
- <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start', if_primary=>1 &>
% }
- <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup', if_primary=>1 &>
<& .row_edit, cust_pkg=>$cust_pkg, column=>'last_bill', label=>$last_bill_or_renewed &>
<& .row_edit, cust_pkg=>$cust_pkg, column=>'bill', label=>$next_bill_or_prepaid_until &>
%#if ( $cust_pkg->contract_end or $part_pkg->option('contract_end_months',1) ) {
- <& .row_edit, cust_pkg=>$cust_pkg, column=>'contract_end',label=>'Contract end' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'contract_end',label=>'Contract end', if_primary=>1 &>
%#}
<& .row_display, cust_pkg=>$cust_pkg, column=>'adjourn', label=>'Adjournment', note=>'(will <b>suspend</b> this package when the date is reached)' &>
<& .row_display, cust_pkg=>$cust_pkg, column=>'susp', label=>'Suspension' &>
@@ -73,10 +105,17 @@
$column
$label
$note => ''
+ $if_primary => 0
</%args>
% my $value = $cust_pkg->get($column);
% $value = $value ? time2str($format, $value) : "";
-
+%
+% # if_primary for the dates that can't be edited on supplemental packages
+% if ($if_primary and $cust_pkg->main_pkgnum) {
+ <INPUT TYPE="hidden" ID="<%$column%>_text" VALUE="<% $cust_pkg->get($column) %>">
+ <SCRIPT>submit_fields.push('<%$column%>');</SCRIPT>
+ <& .row_display, %ARGS &>
+% } else {
<TR>
<TD ALIGN="right"><% $label %> date</TD>
<TD>
@@ -104,8 +143,11 @@
button: "<% $column %>_button",
align: "BR"
});
- </SCRIPT>
+ submit_fields.push('<%$column%>');
+
+ </SCRIPT>
+% }
</%def>
<%def .row_display>
@@ -114,6 +156,7 @@
$column
$label
$note => ''
+ $is_primary => 0 #ignored
</%args>
% if ( $cust_pkg->get($column) ) {
<TR>
@@ -130,7 +173,7 @@
</TABLE>
<BR>
-<INPUT TYPE="submit" VALUE="<% mt('Apply changes') |h %>">
+<INPUT TYPE="button" VALUE="<% mt('Apply changes') |h %>" onclick="confirm_changes()">
</FORM>
<% include('/elements/footer.html') %>
@@ -160,38 +203,6 @@ if ( $cgi->param('error') ) {
my @errors = ();
my %errors = map { $_=>1 } split(',', $cgi->param('error'));
$cgi->param('error', '');
-
- if ( $errors{'_bill_areyousure'} ) {
- if ( $cgi->param('bill') =~ /^([\s\d\/\:\-\(\w\)]*)$/ ) {
- my $bill = $1;
- push @errors,
- "You are attempting to set the next bill date to $bill, which is
- in the past. This will charge the customer for the interval
- from $bill until now. Are you sure you want to do this? ".
- '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">';
- }
- }
-
- if ( $errors{'_setup_areyousure'} ) {
- push @errors,
- "You are attempting to remove the setup date. This will re-charge the
- customer for the setup fee. Are you sure you want to do this? ".
- '<INPUT TYPE="checkbox" NAME="setup_areyousure" VALUE="1">';
- }
-
- if ( $errors{'_setupadd_areyousure'} ) {
- push @errors,
- "You are attempting to add a setup date. This will prevent charging the
- customer for the setup fee. Are you sure you want to do this? ".
- '<INPUT TYPE="checkbox" NAME="setupadd_areyousure" VALUE="1">';
- }
-
- if ( $errors{'_start'} ) {
- push @errors,
- "You are attempting to add a start date to a package that has already
- started billing.";
- }
-
$error = join('<BR><BR>', @errors );
}
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
index dd1ed33..88e9254 100755
--- a/httemplate/edit/cust_pkg.cgi
+++ b/httemplate/edit/cust_pkg.cgi
@@ -7,7 +7,6 @@
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
%#current packages
-%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } );
%if (@cust_pkg) {
Current packages - select to remove (services are moved to a new package below)
@@ -18,13 +17,7 @@
</TR>
<BR><BR>
%
-%
-% foreach ( sort { $all_pkg{ $a->getfield('pkgpart') }
-% cmp $all_pkg{ $b->getfield('pkgpart') }
-% }
-% @cust_pkg
-% )
-% {
+% foreach ( @main_pkgs ) {
% my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') );
% my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : '';
%
@@ -36,6 +29,13 @@
<TD ALIGN="right"><% $pkgnum %>:</TD>
<TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD>
</TR>
+% foreach my $supp_pkg ( @{ $supp_pkgs_of{$pkgnum} } ) {
+ <TR>
+ <TD></TD>
+ <TD></TD>
+ <TD>+ <% $all_pkg{$supp_pkg->pkgpart} %> - <% $all_comment{$supp_pkg->pkgpart} %></TD>
+ </TR>
+% }
% }
@@ -147,4 +147,24 @@ if ( $cgi->param('error') ) {
my $p1 = popurl(1);
+my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } );
+my @main_pkgs;
+my %supp_pkgs_of; # main pkgnum => arrayref of cust_pkgs
+
+
+foreach my $cust_pkg
+ ( sort { $all_pkg{ $a->pkgpart } cmp $all_pkg{ $b->getfield('pkgpart') } }
+ @cust_pkg
+ )
+ # XXX does not properly handle recursive supplemental links
+{
+ if ( my $main_pkgnum = $cust_pkg->main_pkgnum ) {
+ $supp_pkgs_of{$main_pkgnum} ||= [];
+ push @{ $supp_pkgs_of{$main_pkgnum} }, $cust_pkg;
+ } else {
+ push @main_pkgs, $cust_pkg;
+ $supp_pkgs_of{$cust_pkg->pkgnum} ||= [];
+ }
+}
+
</%init>
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index c3f4f88..7baf84d 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -53,6 +53,7 @@
'discountnum' => 'Offer discounts for longer terms',
'bill_dst_pkgpart' => 'Include line item(s) from package',
'svc_dst_pkgpart' => 'Include services of package',
+ 'supp_dst_pkgpart' => 'Include complete package',
'report_option' => 'Report classes',
'fcc_ds0s' => 'Voice-grade equivalents',
'fcc_voip_class' => 'Category',
@@ -239,6 +240,19 @@
},
{ 'type' => 'tablebreak-tr-title',
+ 'value' => 'Supplemental packages',
+ 'colspan' => '4',
+ },
+ { 'field' => 'supp_dst_pkgpart',
+ 'type' => 'select-part_pkg',
+ 'm2_label' => 'Include complete package',
+ 'm2m_method' => 'supp_part_pkg_link',
+ 'm2m_dstcol' => 'dst_pkgpart',
+ 'm2_error_callback' =>
+ &{$m2_error_callback_maker}('supp'),
+ },
+
+ { 'type' => 'tablebreak-tr-title',
'value' => 'Pricing add-ons',
'colspan' => 4,
},
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
index 3e0ef59..fd28934 100755
--- a/httemplate/edit/process/REAL_cust_pkg.cgi
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -19,36 +19,41 @@ die "access denied"
my $pkgnum = $cgi->param('pkgnum') or die;
my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
my %hash = $old->hash;
-$hash{$_}= $cgi->param($_) ? parse_datetime($cgi->param($_)) : ''
- foreach qw( start_date setup bill last_bill contract_end );
+foreach ( qw( start_date setup bill last_bill contract_end ) ) {
+ if ( $cgi->param($_) =~ /^(\d+)$/ ) {
+ $hash{$_} = $1;
+ } else {
+ $hash{$_} = '';
+ }
# adjourn, expire, resume not editable this way
-
-my @errors = ();
-
-push @errors, '_bill_areyousure'
- if $hash{'bill'} != $old->bill # if the next bill date was changed
- && $hash{'bill'} < time # to a date in the past
- && ! $cgi->param('bill_areyousure'); # and it wasn't confirmed
-
-push @errors, '_setup_areyousure'
- if ! $hash{'setup'} && $old->setup # if the setup date was removed
- && ! $cgi->param('setup_areyousure'); # and it wasn't confirmed
-
-push @errors, '_setupadd_areyousure'
- if $hash{'setup'} && ! $old->setup # if the setup date was added
- && ! $cgi->param('setupadd_areyousure'); # and it wasn't confirmed
-
-push @errors, '_start'
- if $hash{'start_date'} && !$old->start_date # if a start date was added
- && $hash{'setup'}; # but there's a setup date
+}
my $new;
my $error;
-if ( @errors ) {
- $error = join(',', @errors);
-} else {
- $new = new FS::cust_pkg \%hash;
- $error = $new->replace($old);
+$new = new FS::cust_pkg \%hash;
+$error = $new->replace($old);
+
+if (!$error) {
+ my @supp_pkgs = $old->supplemental_pkgs;
+ foreach $new (@supp_pkgs) {
+ foreach ( qw( start_date setup contract_end ) ) {
+ # propagate these to supplementals
+ $new->set($_, $hash{$_});
+ }
+ if ( $hash{'bill'} ne $old->get('bill') ) {
+ if ( $hash{'bill'} and $old->get('bill') ) {
+ # adjust by the same interval
+ my $diff = $hash{'bill'} - $old->get('bill');
+ $new->set('bill', $new->get('bill') + $diff);
+ } else {
+ # absolute date
+ $new->set('bill', $hash{'bill'});
+ }
+ }
+ $error = $new->replace;
+ $error .= ' (supplemental package '.$new->pkgnum.')' if $error;
+ last if $error;
+ }
}
</%init>
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
index c388676..2ac57f9 100755
--- a/httemplate/edit/process/part_pkg.cgi
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -185,6 +185,15 @@ my @process_m2m = (
grep /^svc_dst_pkgpart/, $cgi->param
],
},
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'supp', 'hidden' => '' },
+ 'params' => [ map $cgi->param($_),
+ grep /^supp_dst_pkgpart/, $cgi->param
+ ],
+ },
map {
my $hidden = $_;
{ 'link_table' => 'part_pkg_link',
diff --git a/httemplate/misc/confirm-cust_pkg-edit_dates.html b/httemplate/misc/confirm-cust_pkg-edit_dates.html
new file mode 100755
index 0000000..27b9a82
--- /dev/null
+++ b/httemplate/misc/confirm-cust_pkg-edit_dates.html
@@ -0,0 +1,283 @@
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit customer package dates');
+
+my %arg = $cgi->Vars;
+
+my $pkgnum = $arg{'pkgnum'};
+$pkgnum =~ /^\d+$/ or die "bad pkgnum '$pkgnum'";
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+my %hash = $cust_pkg->hash;
+foreach (qw( start_date setup bill last_bill contract_end )) {
+ # adjourn, expire, resume not editable this way
+ if( $arg{$_} =~ /^\d+$/ ) {
+ $hash{$_} = $arg{$_};
+ } elsif ( $arg{$_} ) {
+ $hash{$_} = parse_datetime($arg{$_});
+ } else {
+ $hash{$_} = '';
+ }
+}
+
+my (@changes, @confirm, @errors);
+
+my $part_pkg = $cust_pkg->part_pkg;
+my @supp_pkgs = $cust_pkg->supplemental_pkgs;
+my $main_pkg = $cust_pkg->main_pkg;
+
+my $conf = FS::Conf->new;
+my $date_format = $conf->config('date_format') || '%b %o, %Y';
+# Start date
+if ( $hash{'start_date'} != $cust_pkg->get('start_date') and !$hash{'setup'} ) {
+ my $start = '';
+ $start = time2str($date_format, $hash{'start_date'}) if $hash{'start_date'};
+ my $text = 'Set this package';
+ if ( @supp_pkgs ) {
+ $text .= ' and all its supplemental packages';
+ }
+ $text .= ' to start billing';
+ if ( $start ) {
+ $text .= ' on [_1].';
+ push @changes, mt($text, $start);
+ } else {
+ $text .= ' immediately.';
+ push @changes, mt($text);
+ }
+ push @confirm, '';
+}
+
+# Setup date changes
+if ( $hash{'setup'} != $cust_pkg->get('setup') ) {
+ my $setup = time2str($date_format, $hash{'setup'});
+ my $has_setup_fee = grep { $_->part_pkg->option('setup_fee',1) > 0 }
+ $cust_pkg, @supp_pkgs;
+ if ( !$hash{'setup'} ) {
+ my $text = 'Remove the setup date';
+ $text .= ' from this and all its supplemental packages' if @supp_pkgs;
+ $text .= '.';
+ push @changes, mt($text);
+ if ( $has_setup_fee ) {
+ push @confirm, mt('This will re-charge the customer for the setup fee.');
+ } else {
+ push @confirm, '';
+ }
+ } elsif ( $cust_pkg->get('setup') ) {
+ my $text = 'Add a setup date of [_1]';
+ $text .= ' to this and all its supplemental packages' if @supp_pkgs;
+ $text .= '.';
+ push @changes, mt($text, $setup);
+ if ( $has_setup_fee ) {
+ push @confirm, mt('This will prevent charging the setup fee.');
+ } else {
+ push @confirm, '';
+ }
+ } else {
+ my $text = 'Set the setup date to [_1]';
+ $text .= ' on this and all its supplemental packages' if @supp_pkgs;
+ $text .= '.';
+ push @changes, mt($text, $setup);
+ push @confirm, '';
+ }
+}
+
+# Check for start date + setup date
+if ( $hash{'start_date'} and $hash{'setup'} ) {
+ if ( $cust_pkg->get('setup') ) {
+ push @errors, mt('Since the package has already started billing, it '.
+ 'cannot have a start date.');
+ } else {
+ push @errors, mt('You cannot set both a start date and a setup date on '.
+ 'the same package.');
+ }
+}
+
+# Last bill date change
+if ( $hash{'last_bill'} != $cust_pkg->get('last_bill') ) {
+ my $last_bill = time2str($date_format, $hash{'last_bill'});
+ my $name = 'last bill date';
+ $name = 'last renewal date' if $part_pkg->is_prepaid;
+ if ( $hash{'last_bill'} ) {
+ push @changes, mt('Set the [_1] to [_2].', $name, $last_bill);
+ } else {
+ push @changes, mt('Remove the [_1].', $name);
+ }
+ push @confirm, '';
+ # I don't think we want to adjust this on supplemental packages.
+}
+
+# Bill date change
+if ( $hash{'bill'} != $cust_pkg->get('bill') ) {
+ my $bill = time2str($date_format, $hash{'bill'});
+ $bill = 'the current day' if !$hash{'bill'}; # or 'the end of today'?...
+ my $name = 'next bill date';
+ $name = 'end of the prepaid period' if $part_pkg->is_prepaid;
+ push @changes, mt('Set the [_1] to [_2].', $name, $bill);
+
+ if ( $hash{'bill'} < time and $hash{'bill'} ) {
+ push @confirm,
+ mt('The customer will be charged for the interval from [_1] until now.',
+ $bill);
+ } else {
+ push @confirm, '';
+ }
+
+ if ( @supp_pkgs ) {
+ push @changes, '';
+ if ( $cust_pkg->get('bill') and $hash{'bill'} ) {
+ # the package already has a bill date, so adjust the dates
+ # of supplementals by the same interval
+ my $diff = $hash{'bill'} - $cust_pkg->get('bill');
+ my $sign = $diff < 0 ? -1 : 1;
+ $diff = $diff * $sign / 86400;
+ if ( $diff < 1 ) {
+ $diff = mt('[quant,_1,hour]', int($diff * 24));
+ } else {
+ $diff = mt('[quant,_1,day]', int($diff));
+ }
+ push @confirm,
+ mt('[_1] supplemental package will also be billed [_2] [_3].',
+ (@supp_pkgs > 1 ? 'Each' : 'The'),
+ $diff,
+ ($sign > 0 ? 'later' : 'earlier')
+ );
+ } else {
+ # the package hasn't been billed yet, or you've set bill = null
+ push @confirm,
+ mt('[_1] supplemental package will also be billed on [_2].',
+ (@supp_pkgs > 1 ? 'Each' : 'The'),
+ $bill
+ );
+ }
+ } #if @supp_pkgs
+
+ if ( $main_pkg ) {
+ push @changes, '';
+ push @confirm,
+ mt('This package is a supplemental package. The bill date of its '.
+ 'main package will not be adjusted.');
+ }
+}
+
+# Contract end change
+if ( $hash{'contract_end'} != $cust_pkg->get('contract_end') ) {
+ if ( $hash{'contract_end'} ) {
+ my $contract_end = time2str($date_format, $hash{'contract_end'});
+ push @changes,
+ mt('Set this package\'s contract end date to [_1]', $contract_end);
+ } else {
+ push @changes, mt('Remove this package\'s contract end date.');
+ }
+ if ( @supp_pkgs ) {
+ my $text = 'This change will also apply to ' .
+ (@supp_pkgs > 1 ?
+ 'all supplemental packages.':
+ 'the supplemental package.');
+ push @confirm, mt($text);
+ } else {
+ push @confirm, '';
+ }
+}
+
+my $title = '';
+if ( @errors ) {
+ $title = 'Error changing package dates';
+} else {
+ $title = 'Confirm date changes';
+}
+</%init>
+<& /elements/header-popup.html, { title => $title, etc => 'BGCOLOR=""' } &>
+<STYLE TYPE="text/css">
+.error {
+ color: #ff0000;
+ font-weight: bold;
+ text-align: center;
+}
+.confirm { color: #ff0000 }
+.button-container {
+ position: fixed;
+ bottom: 5px;
+ text-align: center;
+ width: 100%
+}
+</STYLE>
+<DIV STYLE="text-align: center; padding:1em">
+<% emt('Package #') %><B><% $pkgnum %></B>: <B><% $cust_pkg->part_pkg->pkg %></B><BR>
+% if ( @changes ) {
+ <% emt('The following changes will be made:') %>
+% } else {
+ <% emt('No changes will be made.') %>
+% }
+</DIV>
+<TABLE WIDTH="100%">
+% if ( @errors ) {
+% foreach my $error ( @errors ) {
+<TR>
+ <TD><IMG SRC="<%$p%>images/cross.png"></TD>
+ <TD CLASS="error"><% $error %></TD>
+</TR>
+% }
+% } else {
+% while (@changes, @confirm) {
+% my $text = shift @changes;
+% if (length $text) {
+<TR>
+ <TD><IMG SRC="<%$p%>images/tick.png"></TD>
+ <TD><% $text %></TD>
+</TR>
+% }
+% $text = shift @confirm;
+% if (length $text) {
+<TR>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="areyousure" VALUE=1 onclick="submit_ready()">
+ </TD>
+ <TD CLASS="confirm"><% $text %></TD>
+</TR>
+% }
+% }
+% }
+</TABLE>
+%# action buttons
+<DIV CLASS="button-container">
+ <BUTTON TYPE="button" STYLE="width:145px" ID="submit_cancel"\
+ onclick="submit_cancel()">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel
+ </BUTTON>
+% if (!@errors ) {
+ <BUTTON TYPE="button" STYLE="width:145px" ID="submit_continue"\
+ onclick="submit_continue()">
+ <IMG SRC="<%$p%>images/tick.png" ALT=""> Continue
+ </BUTTON>
+</DIV>
+% }
+<FORM NAME="DateEditForm" STYLE="display:none" TARGET="_parent" ACTION="<%$p%>edit/process/REAL_cust_pkg.cgi" METHOD="POST">
+% foreach (keys %hash) {
+<INPUT TYPE="hidden" NAME="<%$_%>" VALUE="<% $hash{$_} |h%>">
+% }
+</FORM>
+<SCRIPT>
+function submit_ready() {
+ var ready = true;
+ var checkboxes = document.getElementsByName('areyousure');
+ var i;
+ for (i=0; i < checkboxes.length; i++) {
+ if (! checkboxes[i].checked ) {
+ ready = false;
+ }
+ }
+ document.getElementById('submit_continue').disabled = !ready;
+ return ready;
+}
+function submit_cancel() {
+ parent.nd(1);
+}
+function submit_continue() {
+ if ( submit_ready() ) {
+ document.forms.DateEditForm.submit();
+ }
+}
+submit_ready();
+</SCRIPT>
+<& /elements/footer.html &>
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
index 7ccf356..e760bc5 100644
--- a/httemplate/search/elements/search-html.html
+++ b/httemplate/search/elements/search-html.html
@@ -253,7 +253,17 @@
% $bgcolor = $bgcolor1;
% }
- <TR>
+% my $trid = '';
+% if ( $opt{'link_field' } ) {
+% my $link_field = $opt{'link_field'};
+% if ( ref($link_field) eq 'CODE' ) {
+% $trid = &{$link_field}($row);
+% } else {
+% $trid = $row->$link_field();
+% }
+% }
+ <TR ID="<%$trid |h%>">
+
% if ( $opt{'fields'} ) {
%
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index 5a16a22..68c4888 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -167,6 +167,11 @@ Example:
# miscellany
'download_label' => 'Download this report',
# defaults to 'Download full results'
+ 'link_field' => 'pkgpart'
+ # will create internal links for each row,
+ # with the value of this field as the NAME attribute
+ # If this is a coderef, will evaluate it, passing the
+ # row as an argument, and use the result as the NAME.
&>
</%doc>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 7d79306..7b5b156 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -1,3 +1,22 @@
+<STYLE TYPE="text/css">
+td.package {
+ vertical-align: top;
+ border-width: 0;
+ border-style: solid;
+ border-color: #bbbbff;
+}
+table.package {
+ border: none;
+ padding: 0;
+ border-spacing: 0;
+ width: 100%;
+}
+<!-- even/odd rows -->
+
+.row0 { background-color: #eeeeee; }
+.row1 { background-color: #ffffff; }
+
+</STYLE>
% my $s = 0;
% if ( $curuser->access_right('Qualify service') ) {
@@ -116,7 +135,7 @@ my( $packages, $num_old_packages ) = get_packages($cust_main, $conf);
my $show_location = $conf->exists('cust_pkg-always_show_location')
- || (grep $_->locationnum, @$packages); # ? '1' : '0';
+ || (grep $_->locationnum ne $cust_main->ship_locationnum, @$packages);
my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
#subroutines
@@ -178,6 +197,10 @@ sub get_packages {
}
$num_old_packages -= scalar(@packages);
+
+ # don't include supplemental packages in this list; they'll be found from
+ # their main packages
+ @packages = grep !$_->main_pkgnum, @packages;
( \@packages, $num_old_packages );
}
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
index 5d93ad4..3a362b6 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -1,5 +1,6 @@
-<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top">
- <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+<TD CLASS="inv package" BGCOLOR="<% $bgcolor %>" VALIGN="top"
+ STYLE="border-left-width: <% $supplemental * 30 %>px">
+ <TABLE CLASS="inv package">
<TR>
<TD COLSPAN=2>
<A NAME="cust_pkg<% $cust_pkg->pkgnum %>"
@@ -17,7 +18,7 @@
<B><% $cust_pkg->quantity %></B>
</TD>
</TR>
-% }
+% }
<TR>
<TD COLSPAN=2>
@@ -25,42 +26,51 @@
% unless ( $cust_pkg->get('cancel') ) {
%
-% my $br = 0;
-% if ( $curuser->access_right('Change customer package') ) {
-% $br=1;
- (&nbsp;<%pkg_change_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $supplemental ) {
+% # then only show "Edit dates", "Add invoice details", and "Add
+% # comments".
+% if ( $curuser->access_right('Edit customer package dates') ) {
+ (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
+% }
+% } else {
+% # the usual case
+% my $br = 0;
+% if ( $curuser->access_right('Change customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_change_link($cust_pkg)%>&nbsp;)
+% }
%
-% if ( $curuser->access_right('Edit customer package dates') ) {
-% $br=1;
- (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $curuser->access_right('Edit customer package dates') ) {
+% $br=1;
+ (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
+% }
%
-% if ( $curuser->access_right('Discount customer package')
-% && $part_pkg->can_discount
-% && ! scalar($cust_pkg->cust_pkg_discount_active)
-% && ! scalar($cust_pkg->part_pkg->part_pkg_discount)
-% )
-% {
-% $br=1;
- (&nbsp;<%pkg_discount_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $curuser->access_right('Discount customer package')
+% && $part_pkg->can_discount
+% && ! scalar($cust_pkg->cust_pkg_discount_active)
+% && ! scalar($cust_pkg->part_pkg->part_pkg_discount)
+% )
+% {
+% $br=1;
+ (&nbsp;<%pkg_discount_link($cust_pkg)%>&nbsp;)
+% }
%
-% if ( $curuser->access_right('Customize customer package') ) {
-% $br=1;
- (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
-% }
+% if ( $curuser->access_right('Customize customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
+% }
%
- <% $br ? '<BR>' : '' %>
-% }
+ <% $br ? '<BR>' : '' %>
+% }
-% if ( $cust_pkg->num_cust_event
-% && ( $curuser->access_right('Billing event reports')
-% || $curuser->access_right('View customer billing events')
-% )
-% ) {
- (&nbsp;<%pkg_event_link($cust_pkg)%>&nbsp;)
-% }
+% if ( $cust_pkg->num_cust_event
+% && ( $curuser->access_right('Billing event reports')
+% || $curuser->access_right('View customer billing events')
+% )
+% ) {
+ (&nbsp;<%pkg_event_link($cust_pkg)%>&nbsp;)
+% }
+% } #!$supplemental
</FONT>
</TD>
@@ -170,6 +180,7 @@
</TR>
% if ( $curuser->access_right('Change customer package') and
% !$cust_pkg->get('cancel') and
+% !$supplemental and
% !$opt{'show_location'}) {
<TR>
<TD><FONT SIZE="-1">
@@ -196,6 +207,7 @@ my $countrydefault = $opt{'countrydefault'} || 'US';
my $statedefault = $opt{'statedefault'}
|| ($countrydefault eq 'US' ? 'CA' : '');
+my $supplemental = $opt{'supplemental'} || 0;
#subroutines
#false laziness w/status.html
diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html
index 85f0c79..53bdfa1 100755
--- a/httemplate/view/cust_main/packages/section.html
+++ b/httemplate/view/cust_main/packages/section.html
@@ -1,8 +1,4 @@
% if ( @$packages ) {
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor = '';
-
<TR>
% #my $width = $show_location ? 'WIDTH="25%"' : 'WIDTH="33%"';
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Package') |h %></TH>
@@ -15,39 +11,39 @@
% #$FS::cust_pkg::DEBUG = 2;
% foreach my $cust_pkg (@$packages) {
+ <& .packagerow, $cust_pkg,
+ 'cust_main' => $opt{'cust_main'},
+ %conf_opt
+ &>
+% }
+% } else { # there are no packages
+<BR>
+% }
+<%def .packagerow>
%
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-%
-% my %iopt = (
-% 'bgcolor' => $bgcolor,
-% 'cust_pkg' => $cust_pkg,
-% 'part_pkg' => $cust_pkg->part_pkg,
-% 'cust_main' => $opt{'cust_main'},
-% %conf_opt,
-% );
-%
-
+% my ($cust_pkg, %iopt) = @_;
+% $iopt{'cust_pkg'} = $cust_pkg;
+% $iopt{'part_pkg'} = $cust_pkg->part_pkg;
<!--pkgnum: <% $cust_pkg->pkgnum %>-->
- <TR>
+ <TR CLASS="row<%$row % 2%>">
<& package.html, %iopt &>
<& status.html, %iopt &>
-% if ( $show_location ) {
+% if ( $iopt{'show_location'} ) {
<& location.html, %iopt &>
% }
<& services.html, %iopt &>
</TR>
-
-% } #foreach $cust_pkg
-%# </TABLE>
-% } #if @$packages
-% else {
-<BR>
+% $row++;
+% # include supplemental packages if any
+% $iopt{'supplemental'} = ($iopt{'supplemental'} || 0) + 1;
+% foreach my $supp_pkg ($cust_pkg->supplemental_pkgs) {
+% warn $supp_pkg->pkgnum;
+ <& .packagerow, $supp_pkg, %iopt &>
% }
-
+</%def>
+<%shared>
+my $row = 0;
+</%shared>
<%init>
my %opt = @_;
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index e901774..6be0296 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -3,7 +3,9 @@
%#this should use cust_pkg->status and cust_pkg->statuscolor eventually
-% if ( $cust_pkg->order_date ) {
+% if ( $supplemental ) {
+ <% pkg_status_row_colspan($cust_pkg, emt('Supplemental'), '', 'color' => '7777FF', %opt) %>
+% } elsif ( $cust_pkg->order_date ) {
<% pkg_status_row($cust_pkg, emt('Ordered'), 'order_date', %opt ) %>
% }
@@ -12,30 +14,25 @@
<% pkg_status_row($cust_pkg, emt('Cancelled'), 'cancel', 'color'=>'FF0000', %opt ) %>
- <% pkg_status_row_colspan( $cust_pkg,
- ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
- 'align'=>'right', 'color'=>'ff0000', 'size'=>'-2', 'colspan'=>$colspan,
- %opt
- )
- %>
+ <% pkg_reason_row($cust_pkg, $cpr, color => 'ff0000', %opt) %>
% unless ( $cust_pkg->get('setup') ) {
- <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', 'colspan'=>$colspan, %opt, ) %>
+ <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', %opt, ) %>
% } else {
<% pkg_status_row( $cust_pkg, emt('Setup'), 'setup', %opt ) %>
- <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, emt('Suspended'), 'susp', %opt, curuser=>$curuser ) %>
% }
%
-% if ( $part_pkg->freq ) { #?
+% if ( $part_pkg->freq and !$supplemental ) { #?
<TR>
- <TD COLSPAN=<%$colspan%>>
+ <TD COLSPAN=<%$opt{colspan}%>>
<FONT SIZE=-1>
% if ( $curuser->access_right('Un-cancel customer package') ) {
(&nbsp;<% pkg_uncancel_link($cust_pkg) %>&nbsp;)
@@ -52,26 +49,21 @@
<% pkg_status_row( $cust_pkg, emt('Suspended'), 'susp', 'color'=>'FF9900', %opt ) %>
- <% pkg_status_row_colspan( $cust_pkg,
- ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
- 'align'=>'right', 'color'=>'FF9900', 'size'=>'-2', 'colspan'=>$colspan,
- %opt,
- )
- %>
+ <% pkg_reason_row( $cust_pkg, $cpr, 'color' => 'FF9900', %opt ) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
% unless ( $cust_pkg->get('setup') ) {
- <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', %opt ) %>
% } else {
<% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt ) %>
% }
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
- <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
% if ( $cust_pkg->option('suspend_bill', 1)
% || ( $part_pkg->option('suspend_bill', 1)
@@ -85,31 +77,33 @@
<% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %>
- <TR>
- <TD COLSPAN=<%$colspan%>>
- <FONT SIZE=-1>
-% if ( $curuser->access_right('Unsuspend customer package') ) {
- (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
- (&nbsp;<% pkg_resume_link($cust_pkg) %>&nbsp;)
-% }
-% if ( $curuser->access_right('Cancel customer package immediately') ) {
- (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
-% }
- </FONT>
- </TD>
- </TR>
-
+% if ( !$supplemental ) {
+ <TR>
+ <TD COLSPAN=<%$opt{colspan}%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Unsuspend customer package') ) {
+ (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
+ (&nbsp;<% pkg_resume_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+ </FONT>
+ </TD>
+ </TR>
+% }
+%
% } else { #status: active
%
% unless ( $cust_pkg->get('setup') ) { #not setup
%
% unless ( $part_pkg->freq ) {
- <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', %opt ) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row_if(
$cust_pkg,
@@ -121,8 +115,9 @@
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+% if (!$supplemental) {
<TR>
- <TD COLSPAN=<%$colspan%>>
+ <TD COLSPAN=<%$opt{colspan}%>>
<FONT SIZE=-1>
% if ( $curuser->access_right('Cancel customer package immediately') ) {
(&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
@@ -130,14 +125,15 @@
</FONT>
</TD>
</TR>
+% }
% } else {
- <% pkg_status_row_colspan($cust_pkg, emt("Not yet billed ($billed_or_prepaid [_1])", myfreq($part_pkg) ), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan($cust_pkg, emt("Not yet billed ($billed_or_prepaid [_1])", myfreq($part_pkg) ), '', %opt ) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %>
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
@@ -148,13 +144,13 @@
%
% unless ( $part_pkg->freq ) {
- <% pkg_status_row_colspan($cust_pkg, emt('One-time charge'), '', 'colspan'=>$colspan, %opt ) %>
+ <% pkg_status_row_colspan($cust_pkg, emt('One-time charge'), '', %opt ) %>
<% pkg_status_row($cust_pkg, emt('Billed'), 'setup', %opt) %>
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
@@ -170,7 +166,7 @@
<% pkg_status_row_colspan( $cust_pkg,
emt('Overlimit'),
$billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
- 'color'=>'FFD000', 'colspan'=>$colspan,
+ 'color'=>'FFD000',
%opt
)
%>
@@ -179,15 +175,15 @@
<% pkg_status_row_colspan( $cust_pkg,
emt('Active'),
$billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
- 'color'=>'00CC00', 'colspan'=>$colspan,
+ 'color'=>'00CC00',
%opt
)
%>
% }
- <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
- <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_discount( $cust_pkg, %opt ) %>
<% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %>
@@ -202,7 +198,7 @@
% $cust_pkg->set('autosuspend', $autosuspend) if $autosuspend;
% }
- <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, $next_bill_or_prepaid_until, 'bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if($cust_pkg, emt('Will automatically suspend by'), 'autosuspend', %opt) %>
@@ -212,10 +208,10 @@
<% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %>
-% if ( $part_pkg->freq ) {
+% if ( $part_pkg->freq and !$supplemental ) {
<TR>
- <TD COLSPAN=<%$colspan%>>
+ <TD COLSPAN=<%$opt{colspan}%>>
<FONT SIZE=-1>
% if ( $curuser->access_right('Suspend customer package') ) {
(&nbsp;<% pkg_suspend_link($cust_pkg) %>&nbsp;)
@@ -251,8 +247,10 @@ my $bgcolor = $opt{'bgcolor'};
my $cust_pkg = $opt{'cust_pkg'};
my $part_pkg = $opt{'part_pkg'};
my $curuser = $FS::CurrentUser::CurrentUser;
-my $colspan = $opt{'cust_pkg-display_times'} ? 8 : 4;
my $width = $opt{'cust_pkg-display_times'} ? '38%' : '56%';
+my $supplemental = $opt{'supplemental'};
+
+$opt{colspan} = $opt{'cust_pkg-display_times'} ? 8 : 4;
#false laziness w/edit/REAL_cust_pkg.cgi
my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until );
@@ -285,9 +283,27 @@ sub pkg_link {
sub pkg_status_row {
my( $cust_pkg, $title, $field, %opt ) = @_;
+ if ( $field and $cust_pkg->main_pkgnum ) {
+ # for supplemental packages, we mostly only show these if they're
+ # different from the main package
+ my $main_pkg = $cust_pkg-> main_pkg;
+ if ( $main_pkg->get($field) ne $cust_pkg->get($field)
+ # with some exceptions
+ or $field eq 'bill'
+ or $field eq 'last_bill'
+ or $field eq 'setup'
+ or $field eq 'susp'
+ or $field eq 'cancel'
+ ) {
+ # handle it normally
+ } else {
+ return '';
+ }
+ }
+
my $color = $opt{'color'};
- my $html = qq(<TR><TD WIDTH="<%$width%>" ALIGN="right">);
+ my $html = qq(<TR><TD WIDTH="$width" ALIGN="right">);
$html .= qq(<FONT COLOR="#$color"><B>) if length($color);
$html .= qq($title&nbsp;);
$html .= qq(</B></FONT>) if length($color);
@@ -338,7 +354,6 @@ sub pkg_status_row_changed {
'',
'size' => '-1',
'align' => 'right',
- 'colspan' => $opt{'colspan'},
);
}
@@ -356,9 +371,7 @@ sub pkg_status_row_noauto {
return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/;
my $what = lc(FS::payby->shortname($cust_main->payby));
- pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), '',
- 'colspan' => $opt{'colspan'},
- );
+ pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), '');
}
sub pkg_status_row_discount {
@@ -382,15 +395,24 @@ sub pkg_status_row_discount {
$cust_pkg_discount->pkgdiscountnum.
'">'.emt('remove discount').'</A>)</FONT>';
- $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
- 'colspan' => $opt{'colspan'},
- );
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '', %opt );
}
$html;
}
+sub pkg_reason_row {
+ my ($cust_pkg, $cpr, %opt) = @_;
+ return '' if $cust_pkg->main_pkgnum;
+
+ my $reasontext = '';
+ $reasontext = $cpr->reasontext . ' by ' . $cpr->otaker if $cpr;
+ pkg_status_row_colspan( $cust_pkg, $reasontext, '',
+ 'align'=>'right', 'size'=>'-2', %opt
+ );
+}
+
sub pkg_status_row_colspan {
my($cust_pkg, $title, $addl, %opt) = @_;