summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rw-r--r--httemplate/browse/part_fee.html71
-rw-r--r--httemplate/edit/credit-cust_bill_pkg.html3
-rw-r--r--httemplate/edit/part_fee.html141
-rwxr-xr-xhttemplate/edit/process/part_fee.html20
-rw-r--r--httemplate/elements/menu.html4
-rw-r--r--httemplate/misc/xmlhttp-calculate_taxes.html9
-rw-r--r--httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html15
-rw-r--r--httemplate/pref/pref-process.html1
-rw-r--r--httemplate/pref/pref.html16
-rw-r--r--httemplate/search/cust_bill_pkg.cgi21
-rwxr-xr-xhttemplate/view/cust_main/packages.html232
-rw-r--r--httemplate/view/cust_main/packages/hidden.html55
-rwxr-xr-xhttemplate/view/cust_main/packages/section.html19
13 files changed, 496 insertions, 111 deletions
diff --git a/httemplate/browse/part_fee.html b/httemplate/browse/part_fee.html
new file mode 100644
index 000000000..482c692d7
--- /dev/null
+++ b/httemplate/browse/part_fee.html
@@ -0,0 +1,71 @@
+<& elements/browse.html,
+ title => 'Fee definitions',
+ name_singular => 'fee definition',
+ query => $query,
+ count_query => $count_query,
+ header => [ '#',
+ 'Description',
+ 'Comment',
+ 'Class',
+ 'Amount',
+ 'Tax status',
+ ],
+ fields => [ 'feepart',
+ 'itemdesc',
+ 'comment',
+ 'classname',
+ $sub_amount,
+ $sub_tax,
+ ],
+ disableable => 1,
+ disabled_statuspos => 3,
+ agent_pos => 6,
+ agent_virt => 1,
+ agent_null_right=> 'Edit global fee definitions',
+ links => [ '',
+ $link,
+ $link,
+ ],
+ align => 'cllccc',
+ menubar => \@menubar,
+&>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $acl_edit = $curuser->access_right('Edit fee definitions');
+my $acl_edit_global = $curuser->access_right('Edit global fee definitions');
+die "access denied"
+ unless $acl_edit or $acl_edit_global;
+
+my $query = {
+ 'select' => 'part_fee.*,'.
+ '(select classname from pkg_class '.
+ 'where pkg_class.classnum = part_fee.classnum) AS classname',
+ 'table' => 'part_fee',
+};
+my $count_query = "SELECT COUNT(*) FROM part_fee";
+
+my $sub_amount = sub {
+ my $obj = shift;
+ my $string = $obj->explanation;
+ $string =~ s/\n/<br>/sg;
+ $string;
+};
+
+my $sub_tax = sub {
+ my $obj = shift;
+ if ( $obj->taxable ) {
+ return $obj->taxclass || 'taxable';
+ } elsif ( $obj->taxproductnum ) {
+ return join('<br>',
+ split(/\s*:\s*/, $obj->part_pkg_taxproduct->description)
+ );
+ } else {
+ return 'exempt';
+ }
+};
+
+my $link = [ $p.'edit/part_fee.html?', 'feepart' ];
+
+my @menubar = ( 'Add a new fee definition',
+ $p.'edit/part_fee.html' );
+</%init>
diff --git a/httemplate/edit/credit-cust_bill_pkg.html b/httemplate/edit/credit-cust_bill_pkg.html
index a5ecb69e3..40faddc46 100644
--- a/httemplate/edit/credit-cust_bill_pkg.html
+++ b/httemplate/edit/credit-cust_bill_pkg.html
@@ -269,7 +269,8 @@ my @cust_bill_pkg = qsearch({
'select' => 'cust_bill_pkg.*',
'table' => 'cust_bill_pkg',
'addl_from' => 'LEFT JOIN cust_bill USING (invnum)',
- 'extra_sql' => "WHERE custnum = $custnum AND pkgnum != 0",
+ 'extra_sql' => "WHERE custnum = $custnum ".
+ "AND (pkgnum != 0 or feepart IS NOT NULL)",
'order_by' => 'ORDER BY invnum ASC, billpkgnum ASC',
});
diff --git a/httemplate/edit/part_fee.html b/httemplate/edit/part_fee.html
new file mode 100644
index 000000000..dada23360
--- /dev/null
+++ b/httemplate/edit/part_fee.html
@@ -0,0 +1,141 @@
+<& elements/edit.html,
+ 'name_singular' => 'fee definition',
+ 'table' => 'part_fee',
+ 'labels' => {
+ 'feepart' => 'Fee definition',
+ 'itemdesc' => 'Description',
+ 'comment' => 'Comment (customer-hidden)',
+ 'classnum' => 'Package class',
+ 'taxable' => 'This fee is taxable',
+ 'disabled' => 'Disable this fee',
+ 'taxclass' => 'Tax class name',
+ 'taxproductnum' => 'Tax product',
+ 'pay_weight' => 'Payment weight',
+ 'credit_weight' => 'Credit weight',
+ 'agentnum' => 'Agent',
+ 'amount' => 'Flat fee amount',
+ 'percent' => 'Percentage of invoice amount',
+ 'basis' => 'Based on',
+ 'setuprecur' => 'Report this fee as',
+ 'minimum' => 'Minimum fee',
+ 'maximum' => 'Maximum fee',
+ 'limit_credit' => 'Limit to customer credit balance',
+ %locale_labels
+ },
+ 'fields' => \@fields,
+ 'edit_callback' => $edit_callback,
+ 'error_callback' => $error_callback,
+&>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $acl_edit = $curuser->access_right('Edit fee definitions');
+my $acl_edit_global = $curuser->access_right('Edit global fee definitions');
+die "access denied"
+ unless $acl_edit or $acl_edit_global;
+
+my $conf = FS::Conf->new;
+my @tax_fields;
+if ( $conf->exists('enable_taxproducts') ) {
+ @tax_fields = (
+ { field => 'taxproductnum', type => 'select-taxproduct' }
+ );
+} else {
+ @tax_fields = (
+ { field => 'taxable', type => 'checkbox', value => 'Y' },
+ );
+ push (
+ { field => 'taxclass', type => 'select-taxclass' },
+ ) if $conf->exists('enable_taxclasses');
+}
+
+my $default_locale = $conf->config('locale') || 'en_US';
+my @locales = grep {$_ ne $default_locale} $conf->config('available-locales');
+# duplicates edit/part_pkg.cgi, yuck
+my $n = 0;
+my (@locale_fields, %locale_labels);
+foreach (@locales) {
+ push @locale_fields,
+ { field => 'feepartmsgnum'. $n, type => 'hidden' },
+ { field => 'feepartmsgnum'. $n. '_locale', type => 'hidden' },
+ { field => 'feepartmsgnum'. $n. '_itemdesc', type => 'text', size => 40 },
+ ;
+ $locale_labels{ 'feepartmsgnum'.$n.'_itemdesc' } =
+ 'Description&mdash;' . FS::Locales->description($_);
+ $n++;
+}
+
+my @fields = (
+
+ { field => 'itemdesc', type => 'text', size => 40, },
+ @locale_fields,
+
+ { field => 'comment', type => 'text', size => 40, },
+
+ { field => 'agentnum',
+ type => 'select-agent',
+ disable_empty => !$acl_edit_global,
+ empty_label => '(global)',
+ },
+
+ { field => 'classnum',
+ type => 'select-pkg_class',
+ },
+
+ { field => 'disabled',
+ type => 'checkbox',
+ value => 'Y',
+ },
+
+ { field => 'setuprecur',
+ type => 'select',
+ options => [ 'setup', 'recur' ],
+ labels => { 'setup' => 'a setup fee',
+ 'recur' => 'a recurring charge' },
+ },
+
+ { type => 'justtitle', value => 'Fee calculation' },
+ { field => 'amount', type => 'money', },
+ { field => 'percent', type => 'percentage', },
+
+ { field => 'basis',
+ type => 'select',
+ options => [ 'charged', 'owed' ],
+ labels => { 'charged' => 'amount charged',
+ 'owed' => 'balance due', },
+ },
+
+ { field => 'minimum', type => 'money', },
+ { field => 'maximum', type => 'money', },
+ { field => 'limit_credit',
+ type => 'checkbox',
+ value => 'Y' },
+
+ { type => 'justtitle', value => 'Taxation' },
+
+ @tax_fields,
+);
+
+my $edit_callback = sub {
+ my ($cgi, $obj, $fields, $opt) = @_;
+ my %existing_locales;
+ if ( $obj->feepart ) {
+ %existing_locales = map { $_->locale => $_ } $obj->part_fee_msgcat;
+ }
+ my $n = 0;
+ foreach (@locales) {
+ $obj->set('feepartmsgnum'.$n.'_locale', $_);
+ # load the existing itemdescs
+ if ( my $msgcat = $existing_locales{$_} ) {
+ $obj->set('feepartmsgnum'.$n, $msgcat->feepartmsgnum);
+ $obj->set('feepartmsgnum'.$n.'_itemdesc', $msgcat->itemdesc);
+ }
+ # then override that with the CGI param if there is one
+ if ( my $itemdesc = $cgi->param('feepartmsgnum'.$n.'_itemdesc') ) {
+ $obj->set('feepartmsgnum'.$n.'_itemdesc', $itemdesc);
+ }
+ $n++;
+ }
+};
+
+my $error_callback = $edit_callback;
+</%init>
diff --git a/httemplate/edit/process/part_fee.html b/httemplate/edit/process/part_fee.html
new file mode 100755
index 000000000..25656e9b0
--- /dev/null
+++ b/httemplate/edit/process/part_fee.html
@@ -0,0 +1,20 @@
+<& elements/process.html,
+ 'debug' => 1,
+ 'table' => 'part_fee',
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Edit global fee definitions',
+ 'viewall_dir' => 'browse',
+ 'process_o2m' => {
+ 'table' => 'part_fee_msgcat',
+ 'fields' => [ 'locale', 'itemdesc' ],
+ },
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $acl_edit = $curuser->access_right('Edit fee definitions');
+my $acl_edit_global = $curuser->access_right('Edit global fee definitions');
+die "access denied"
+ unless $acl_edit or $acl_edit_global;
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index fb84e7501..cd4fb39ec 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -584,6 +584,10 @@ if ( $curuser->access_right('Configuration') ) {
$config_pkg{'Package report classes'} = [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define optional groups of packages for reporting only.' ];
#eo package grouping sub-menu
+ if ( $curuser->access_right([ 'Edit fee definitions',
+ 'Edit global fee definitions' ]) ) {
+ $config_pkg{'Fees'} = [ $fsurl.'browse/part_fee.html', '' ];
+ }
$config_pkg{'Discounts'} = [ $fsurl.'browse/discount.html', '' ];
$config_pkg{'Discount classes'} = [ $fsurl.'browse/discount_class.html', '' ];
$config_pkg{'Cancel/Suspend Reasons'} = [ \%config_pkg_reason, '' ];
diff --git a/httemplate/misc/xmlhttp-calculate_taxes.html b/httemplate/misc/xmlhttp-calculate_taxes.html
index ed7bd0173..2bb1f4cce 100644
--- a/httemplate/misc/xmlhttp-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-calculate_taxes.html
@@ -62,14 +62,7 @@ if ( $sub eq 'calculate_taxes' ) {
my $taxlisthash = {};
foreach my $cust_bill_pkg (values %cust_bill_pkg) {
- my $part_pkg = $cust_bill_pkg->part_pkg;
- $cust_main->_handle_taxes( $part_pkg,
- $taxlisthash,
- $cust_bill_pkg,
- $cust_bill_pkg->cust_pkg,
- $cust_bill_pkg->cust_bill->_date,
- $cust_bill_pkg->cust_pkg->pkgpart,
- );
+ $cust_main->_handle_taxes( $taxlisthash, $cust_bill_pkg );
}
my $listref_or_error =
$cust_main->calculate_taxes( [ values %cust_bill_pkg ], $taxlisthash, [ values %cust_bill_pkg ]->[0]->cust_bill->_date );
diff --git a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
index c0db3e2c4..4558682bd 100644
--- a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
@@ -62,15 +62,7 @@ if ( $sub eq 'calculate_taxes' ) {
push @cust_bill_pkg, $cust_bill_pkg;
- my $part_pkg = $cust_bill_pkg->part_pkg;
- $cust_main->_handle_taxes( $part_pkg,
- $taxlisthash,
- $cust_bill_pkg,
- $cust_bill_pkg->cust_pkg,
- $cust_bill_pkg->cust_bill->_date,
- $cust_bill_pkg->cust_pkg->pkgpart,
- );
-
+ $cust_main->_handle_taxes( $taxlisthash, $cust_bill_pkg );
}
if ( @cust_bill_pkg ) {
@@ -89,7 +81,10 @@ if ( $sub eq 'calculate_taxes' ) {
foreach my $taxline ( @$listref_or_error ) {
my $amount = $taxline->setup;
my $desc = $taxline->desc;
- foreach my $location ( @{$taxline->cust_bill_pkg_tax_location}, @{$taxline->cust_bill_pkg_tax_rate_location} ) {
+ foreach my $location (
+ @{$taxline->get('cust_bill_pkg_tax_location')},
+ @{$taxline->get('cust_bill_pkg_tax_rate_location')} )
+ {
my $taxlocnum = $location->locationnum || '';
my $taxratelocnum = $location->taxratelocationnum || '';
$location->cust_bill_pkg_desc($taxline->desc); #ugh @ that kludge
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index 7848b72cb..6d4f89a77 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -59,6 +59,7 @@ unless ( $error ) { # if ($access_user) {
snom-ip snom-username snom-password
vonage-fromnumber vonage-username vonage-password
cust_pkg-display_times
+ hide_package_changes
show_pkgnum show_confitem_counts export_getsettings
show_db_profile save_db_profile save_tmp_typesetting
height width availHeight availWidth colorDepth
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index ccfeecd77..eaa7d3281 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -78,6 +78,22 @@ Interface
</TD>
</TR>
+ <TR>
+ <TH ALIGN="right">Hide package changes: </TH>
+ <TD>
+ <& /elements/select.html,
+ field => 'hide_package_changes',
+ options => [ '', 'location', 'all' ],
+ labels => { '' => 'never',
+ 'location' => 'location changes',
+ 'all' => 'all package changes',
+ },
+ curr_value => ($cgi->param('hide_package_changes')
+ || $curuser->option('hide_package_changes')),
+ &>
+ </TD>
+ </TR>
+
% my $history_order = $curuser->option('history_order') || 'oldest';
<TR>
<TH ALIGN="right">Customer history sort order: </TH>
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 6b7a5e6e2..440ab150c 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -137,9 +137,9 @@ Filtering parameters:
- use_override: Apply "classnum" and "taxclass" filtering based on the
override (bundle) pkgpart, rather than always using the true pkgpart.
-- nottax: Limit to items that are not taxes (pkgnum > 0).
+- nottax: Limit to items that are not taxes (pkgnum > 0 or feepart > 0).
-- istax: Limit to items that are taxes (pkgnum == 0).
+- istax: Limit to items that are taxes (pkgnum == 0 and feepart = null).
- taxnum: Limit to items whose tax definition matches this taxnum.
With "nottax" that means items that are subject to that tax;
@@ -305,7 +305,8 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
# we want the package and its definition if available
my $join_pkg =
' LEFT JOIN cust_pkg USING (pkgnum)
- LEFT JOIN part_pkg USING (pkgpart)';
+ LEFT JOIN part_pkg USING (pkgpart)
+ LEFT JOIN part_fee USING (feepart)';
my $part_pkg = 'part_pkg';
# "Separate sub-packages from parents"
@@ -319,12 +320,16 @@ if ( $use_override ) {
$part_pkg = 'override';
}
push @select, "$part_pkg.pkgpart", "$part_pkg.pkg";
-push @select, "$part_pkg.taxclass" if $conf->exists('enable_taxclasses');
+push @select, "COALESCE($part_pkg.taxclass, part_fee.taxclass) AS taxclass"
+ if $conf->exists('enable_taxclasses');
# the non-tax case
if ( $cgi->param('nottax') ) {
- push @where, 'cust_bill_pkg.pkgnum > 0';
+ push @select, "part_fee.itemdesc";
+
+ push @where,
+ '(cust_bill_pkg.pkgnum > 0 OR cust_bill_pkg.feepart IS NOT NULL)';
my @tax_where; # will go into a subquery
my @exempt_where; # will also go into a subquery
@@ -335,7 +340,7 @@ if ( $cgi->param('nottax') ) {
# N: classnum
if ( grep { $_ eq 'classnum' } $cgi->param ) {
my @classnums = grep /^\d*$/, $cgi->param('classnum');
- push @where, "COALESCE($part_pkg.classnum, 0) IN ( ".
+ push @where, "COALESCE(part_fee.classnum, $part_pkg.classnum, 0) IN ( ".
join(',', @classnums ).
' )'
if @classnums;
@@ -360,7 +365,7 @@ if ( $cgi->param('nottax') ) {
# effective taxclass, not the real one
push @tax_where, 'cust_main_county.taxclass IS NULL'
} elsif ( $cgi->param('taxclass') ) {
- push @tax_where, "$part_pkg.taxclass IN (" .
+ push @tax_where, "COALESCE(part_fee.taxclass, $part_pkg.taxclass) IN (" .
join(', ', map {dbh->quote($_)} $cgi->param('taxclass') ).
')';
}
@@ -681,7 +686,7 @@ if ( $cgi->param('salesnum') =~ /^(\d+)$/ ) {
'paid' => ($cgi->param('paid') ? 1 : 0),
'classnum' => scalar($cgi->param('classnum'))
);
- $join_pkg .= " JOIN sales_pkg_class ON ( COALESCE(sales_pkg_class.classnum, 0) = COALESCE( part_pkg.classnum, 0) )";
+ $join_pkg .= " JOIN sales_pkg_class ON ( COALESCE(sales_pkg_class.classnum, 0) = COALESCE( part_fee.classnum, part_pkg.classnum, 0) )";
my $extra_sql = $subsearch->{extra_sql};
$extra_sql =~ s/^WHERE//;
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 566ab2943..746e0c7c5 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -22,8 +22,62 @@ table.usage {
.row0 { background-color: #eeeeee; }
.row1 { background-color: #ffffff; }
-</STYLE>
+table.hiddenrows {
+ width: 80%;
+ margin-left: 100px;
+ border: 1px solid #7E0079;
+ background-color: #cccccc;
+}
+
+.hiddenrows td {
+ text-align: center;
+}
+.rolldown_button {
+ min-width: 80px;
+ margin-left: 100px;
+ min-height: 20px;
+ background-color: #efefef;
+ border: 1px solid #7e0079;
+ z-index: 1;
+ text-align: center;
+}
+</STYLE>
+% # activate rolldown buttons for hidden package blocks
+<SCRIPT TYPE="text/javascript">
+function toggle_rolldown() {
+ var up_arrow = <% decode_entities('&#x2b06') |js_string %>;
+ var dn_arrow = <% decode_entities('&#x2b07') |js_string %>;
+ var pkgnum = this.id.replace('rolldown_', '');
+ var hidden = document.getElementById('cust_pkg'+pkgnum+'_block');
+ if (hidden.style.display == 'none') {
+ hidden.style.display = '';
+ this.textContent = this.textContent.replace(dn_arrow, up_arrow);
+ } else {
+ hidden.style.display = 'none';
+ this.textContent = this.textContent.replace(up_arrow, dn_arrow);
+ }
+}
+<&| /elements/onload.js &>
+var el;
+% if ( $cgi->param('fragment') =~ /^cust_pkg(\d+)$/ ) {
+% # IE-specific hack, but also unhide the row if it's in a hidden block
+el = document.getElementById('cust_pkg<% $1 %>');
+% }
+var all_buttons = document.getElementsByClassName('rolldown_button');
+for (var i = 0; i < all_buttons.length; i++) {
+ all_buttons[i].onclick = toggle_rolldown;
+ var block_id = all_buttons[i].id.replace('rolldown_', '');
+ if ( el && document.getElementById('cust_pkg'+block_id+'_block')
+ .contains(el)
+ ) {
+ // then toggle it now
+ all_buttons[i].click();
+ }
+}
+if ( el ) el.scrollIntoView(true);
+</&>
+</SCRIPT>
% unless ( $opt{no_links} ) {
% my $s = 0;
@@ -124,7 +178,7 @@ table.usage {
% if ( $conf->exists('cust_pkg-group_by_location') ) {
<& locations.html,
'cust_main' => $cust_main,
- 'packages' => $packages,
+ 'packages' => \@packages,
%opt,
&>
% }
@@ -133,7 +187,7 @@ table.usage {
<& /elements/table-grid.html &>
<& packages/section.html,
'cust_main' => $cust_main,
- 'packages' => $packages,
+ 'packages' => \@packages,
%opt,
&>
</TABLE>
@@ -141,15 +195,6 @@ table.usage {
</TD>
</TR>
-% if ( $cgi->param('fragment') =~ /^cust_pkg(\d+)$/ ) {
- <SCRIPT>
- // IE-specific hack. other browsers listen to #fragments
- // is this even working? or is the #target redirection just working cause
- // we set the URL params differently?
- var el = document.getElementById( 'cust_pkg<% $1 %>' );
- if ( el ) el.scrollIntoView(true);
- </SCRIPT>
-% }
</TABLE>
<%init>
@@ -159,94 +204,113 @@ my $conf = new FS::Conf;
my $curuser = $FS::CurrentUser::CurrentUser;
-my( $packages, $num_old_packages ) = get_packages($cust_main, $conf);
-
my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
-#subroutines
-
-sub get_packages {
- my $cust_main = shift or return undef;
- my $conf = shift;
-
- my $method;
- if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
- || ( $conf->exists('hidecancelledpackages')
- && ! $cgi->param('showcancelledpackages') )
- )
- {
- $method = 'ncancelled_pkgs';
- } else {
- $method = 'all_pkgs';
- }
- my $cust_pkg_fields =
- join(', ', map { "cust_pkg.$_ AS $_" } fields('cust_pkg') );
+my $hide_changed = $curuser->option('hide_package_changes');
- my $part_pkg_fields =
- join(', ', map { "part_pkg.$_ AS part_pkg_$_" } fields('part_pkg') );
+my $hide_cancelled = 0;
+if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledpackages')
+ && ! $cgi->param('showcancelledpackages') )
+ )
+{
+ $hide_cancelled = 1;
+}
- my $group_by =
- join(', ', map "cust_pkg.$_", fields('cust_pkg') ). ', '.
- join(', ', map "part_pkg.$_", fields('part_pkg') );
+my $cust_pkg_fields =
+ join(', ', map { "cust_pkg.$_ AS $_" } fields('cust_pkg') );
- my $num_svcs = '( SELECT COUNT(*) FROM cust_svc '.
- ' WHERE cust_svc.pkgnum = cust_pkg.pkgnum ) AS num_svcs';
+my $part_pkg_fields =
+ join(', ', map { "part_pkg.$_ AS part_pkg_$_" } fields('part_pkg') );
- my @packages = $cust_main->$method( {
- 'select' => "$cust_pkg_fields, $part_pkg_fields, $num_svcs",
- 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
- } );
- my $num_old_packages = scalar(@packages);
+my $group_by =
+ join(', ', map "cust_pkg.$_", fields('cust_pkg') ). ', '.
+ join(', ', map "part_pkg.$_", fields('part_pkg') );
- my %change_to_from; # target pkgnum => current cust_pkg, for future changes
+my $num_svcs = '( SELECT COUNT(*) FROM cust_svc '.
+ ' WHERE cust_svc.pkgnum = cust_pkg.pkgnum ) AS num_svcs';
- foreach my $cust_pkg ( @packages ) {
- my %hash = $cust_pkg->hash;
- my %part_pkg = map { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); }
- grep { /^part_pkg_/ } keys %hash;
- $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg;
- if ( $cust_pkg->change_to_pkgnum ) {
- $change_to_from{$cust_pkg->change_to_pkgnum} = $cust_pkg;
- }
+# don't exclude cancelled packages at this stage
+my @packages = $cust_main->all_pkgs( {
+ 'select' => "$cust_pkg_fields, $part_pkg_fields, $num_svcs",
+ 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
+} );
+
+my %change_to_from; # target pkgnum => current cust_pkg, for future changes
+my %changed_from; # old pkgnum => new cust_pkg, for past changes
+
+foreach my $cust_pkg ( @packages ) {
+ my %hash = $cust_pkg->hash;
+ my %part_pkg = map { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); }
+ grep { /^part_pkg_/ } keys %hash;
+ $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg;
+ if ( $cust_pkg->change_to_pkgnum ) {
+ $change_to_from{$cust_pkg->change_to_pkgnum} = $cust_pkg;
}
+ if ( $cust_pkg->change_pkgnum ) {
+ $changed_from{$cust_pkg->change_pkgnum} = $cust_pkg;
+ }
+}
- if ( keys %change_to_from ) {
- my @not_future_packages;
- foreach my $cust_pkg (@packages) {
- if ( exists( $change_to_from{$cust_pkg->pkgnum} ) ) {
- my $change_from = $change_to_from{ $cust_pkg->pkgnum };
- $cust_pkg->set('change_from_pkg', $change_from);
- $change_from->set('change_to_pkg', $cust_pkg);
- } else {
- push @not_future_packages, $cust_pkg;
+# filter out hidden package changes
+if ( keys %change_to_from or keys %changed_from ) {
+ my @displayable_packages;
+ foreach my $cust_pkg (@packages) {
+ if ( exists( $change_to_from{$cust_pkg->pkgnum} ) ) {
+ # $cust_pkg is an ordered, not-yet-active package change target
+ my $change_from = $change_to_from{ $cust_pkg->pkgnum };
+ $cust_pkg->set('change_from_pkg', $change_from);
+ $change_from->set('change_to_pkg', $cust_pkg);
+ } elsif ( exists( $changed_from{$cust_pkg->pkgnum} ) ) {
+ # $cust_pkg is a canceled package changed into another packge
+ my $changed_to = $changed_from{$cust_pkg->pkgnum};
+ if ( ( $hide_changed eq 'all' ) or
+ ( $hide_changed eq 'location'
+ and $changed_to->pkgpart == $cust_pkg->pkgpart
+ and $changed_to->refnum == $cust_pkg->refnum
+ and $changed_to->quantity == $cust_pkg->quantity )
+ ) {
+ # then we're hiding it
+ $cust_pkg->set('changed_to_pkg', $changed_to);
+ $changed_to->set('changed_from_pkg', $cust_pkg);
+ } else { # show it anyway
+ push @displayable_packages, $cust_pkg;
}
+ } else {
+ push @displayable_packages, $cust_pkg;
}
- @packages = @not_future_packages;
}
+ @packages = @displayable_packages;
+}
- unless ( $cgi->param('showoldpackages') ) {
- my $years = $conf->config('cust_main-packages-years') || 2;
- my $then = time - $years * 31556926; #60*60*24*365.2422 is close enough
-
- my %hide = ( 'cancelled' => 'cancel',
- 'one-time charge' => 'setup',
- );
-
- @packages =
- grep { !exists($hide{$_->status}) or $_->get($hide{$_->status}) > $then
- or $_->num_svcs #don't hide packages w/services
- }
- @packages;
- }
+# filter all cancelled packages if the user wants
+if ( $hide_cancelled ) {
+ @packages = grep { !$_->get('cancel') } @packages;
+}
+
+# filter out 'old' packages
+my $num_old_packages = scalar(@packages);
- $num_old_packages -= scalar(@packages);
-
- # don't include supplemental packages in this list; they'll be found from
- # their main packages
- # (as will change-target packages)
- @packages = grep !$_->main_pkgnum, @packages;
+unless ( $cgi->param('showoldpackages') ) {
+ my $years = $conf->config('cust_main-packages-years') || 2;
+ my $then = time - $years * 31556926; #60*60*24*365.2422 is close enough
- ( \@packages, $num_old_packages );
+ my %hide = ( 'cancelled' => 'cancel',
+ 'one-time charge' => 'setup',
+ );
+
+ @packages =
+ grep { !exists($hide{$_->status}) or $_->get($hide{$_->status}) > $then
+ or $_->num_svcs #don't hide packages w/services
+ }
+ @packages;
}
+$num_old_packages -= scalar(@packages);
+
+# don't include supplemental packages in this list; they'll be found from
+# their main packages
+# (as will change-target packages)
+@packages = grep !$_->main_pkgnum, @packages;
+
</%init>
diff --git a/httemplate/view/cust_main/packages/hidden.html b/httemplate/view/cust_main/packages/hidden.html
new file mode 100644
index 000000000..e3bd0fabf
--- /dev/null
+++ b/httemplate/view/cust_main/packages/hidden.html
@@ -0,0 +1,55 @@
+% if (!$iopt{noframe}) {
+% # then start the block here, and assign a suitable ID (cust_pkgX_block)
+<TABLE CLASS="hiddenrows" STYLE="display: none" ID="<% $id %>_block">
+% }
+ <TR ID="<% $id %>">
+ <TD>
+ <A NAME="<% $id %>"/>
+ <% $pkgnum %>
+ </TD>
+ <TD>
+% if ( $pkgpart_change and $location_change ) {
+ Package type and location change
+% } elsif ( $pkgpart_change ) {
+ Package type change
+% } elsif ( $location_change ) {
+ Location change
+% } # or else what?
+ <B><% time2str('%b %o, %Y', $cust_pkg->get('cancel')) %></B>
+ </TD><TD>
+% if ( $pkgpart_change ) {
+ from <B><% $part_pkg->pkg |h %></B></A> - <% $part_pkg->custom_comment |h %>
+% }
+% if ( $pkgpart_change and $location_change ) {
+ <BR>
+% }
+% if ( $location_change ) {
+ from <I><% $cust_pkg->location_label %></I>
+% }
+ </TD>
+ </TR>
+% if ( $cust_pkg->get('changed_from_pkg') ) {
+<& hidden.html, $cust_pkg->get('changed_from_pkg'),
+ %iopt,
+ 'next_pkg' => $cust_pkg,
+ 'noframe' => 1
+&>
+% }
+% if ( !$iopt{noframe} ) {
+</TABLE>
+% }
+<%init>
+my $cust_pkg = shift;
+my $part_pkg = $cust_pkg->part_pkg;
+my %iopt = @_;
+my $next = delete($iopt{'next_pkg'});
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $pkgnum = $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '';
+
+my $id = "cust_pkg".$cust_pkg->pkgnum;
+
+my $pkgpart_change = ($next->pkgpart != $cust_pkg->pkgpart);
+my $location_change = ($next->locationnum != $cust_pkg->locationnum);
+my $both_change = $pkgpart_change && $location_change;
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html
index 152ccaa5d..730bb2cf0 100755
--- a/httemplate/view/cust_main/packages/section.html
+++ b/httemplate/view/cust_main/packages/section.html
@@ -35,6 +35,25 @@
</TD>
<& services.html, %iopt &>
</TR>
+% # insert hidden predecessors to this package, if any
+% # and a rolldown button to show them
+% # (we'll make it do something later)
+% if ( $cust_pkg->get('changed_from_pkg') ) {
+ <TR CLASS="row<% $row % 2 %>">
+ <TD COLSPAN=4>
+ <BUTTON CLASS="rolldown_button"
+ ID="rolldown_<% $cust_pkg->change_pkgnum %>">
+ History &#x2b07;
+ </BUTTON>
+% # the hidden block here has ID="cust_pkgX" where X is the first pkgnum
+% # it contains.
+ <& hidden.html, $cust_pkg->get('changed_from_pkg'),
+ %iopt,
+ 'next_pkg' => $cust_pkg,
+ &>
+ </TD>
+ </TR>
+% }
% $row++;
% # show the change target, if there is one
% if ( $cust_pkg->change_to_pkg ) {