From 87a986f35b347affed5f1e1ff5c5c7c59ccd4ad1 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 20 Jul 2009 14:26:12 +0000 Subject: [PATCH] bundle bill linked packages into top line total when desired #5724 --- FS/FS/Schema.pm | 14 ++-- FS/FS/cust_bill.pm | 93 +++++++++++++++++++++------ FS/FS/cust_bill_pkg.pm | 3 + FS/FS/cust_main.pm | 34 +++++++--- FS/FS/part_pkg.pm | 26 +++++--- FS/FS/part_pkg_link.pm | 8 ++- httemplate/edit/elements/edit.html | 117 +++++++++++++++++++++++++++++++++- httemplate/edit/part_pkg.cgi | 42 ++++++++---- httemplate/edit/process/part_pkg.cgi | 30 ++++++--- httemplate/elements/checkbox.html | 19 ++++++ httemplate/elements/tr-checkbox.html | 8 +-- httemplate/elements/tr-justtitle.html | 2 +- httemplate/elements/tr-title.html | 7 +- 13 files changed, 327 insertions(+), 76 deletions(-) create mode 100644 httemplate/elements/checkbox.html diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 8b5318421..419e9ac20 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -518,6 +518,7 @@ sub tables_hashref { 'quantity', 'int', 'NULL', '', '', '', 'unitsetup', @money_typen, '', '', 'unitrecur', @money_typen, '', '', + 'hidden', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'billpkgnum', 'unique' => [], @@ -1240,15 +1241,18 @@ sub tables_hashref { 'part_pkg_link' => { 'columns' => [ - 'pkglinknum', 'serial', '', '', '', '', - 'src_pkgpart', 'int', '', '', '', '', - 'dst_pkgpart', 'int', '', '', '', '', - 'link_type', 'varchar', '', $char_d, '', '', + 'pkglinknum', 'serial', '', '', '', '', + 'src_pkgpart', 'int', '', '', '', '', + 'dst_pkgpart', 'int', '', '', '', '', + 'link_type', 'varchar', '', $char_d, '', '', + 'hidden', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'pkglinknum', - 'unique' => [ [ 'src_pkgpart', 'dst_pkgpart', 'link_type' ] ], + 'unique' => [ [ 'src_pkgpart', 'dst_pkgpart', 'link_type', 'hidden' ] ], 'index' => [ [ 'src_pkgpart' ] ], }, + # XXX somewhat borked unique: we don't really want a hidden and unhidden + # it turns out we'd prefer to use svc, bill, and invisibill (or something) 'part_pkg_taxclass' => { 'columns' => [ diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 17b88c540..ac0280842 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2793,8 +2793,19 @@ sub _items_cust_bill_pkg { my $section = $opt{section}->{description} if $opt{section}; my @b = (); + my ($s, $r, $u) = ( undef, undef, undef ); foreach my $cust_bill_pkg ( @$cust_bill_pkg ) { + + foreach ( $s, $r, $u ) { + if ( $_ && !$cust_bill_pkg->hidden ) { + $_->{amount} = sprintf( "%.2f", $_->{amount} ), + $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ), + push @b, { %$_ }; + $_ = undef; + } + } + foreach my $display ( grep { defined($section) ? $_->section eq $section : 1 @@ -2826,18 +2837,25 @@ sub _items_cust_bill_pkg { my @d = (); push @d, map &{$escape_function}($_), $cust_pkg->h_labels_short($self->_date) - unless $cust_pkg->part_pkg->hide_svc_detail; + unless $cust_pkg->part_pkg->hide_svc_detail + || $cust_bill_pkg->hidden; push @d, $cust_bill_pkg->details(%details_opt) if $cust_bill_pkg->recur == 0; - push @b, { - description => $description, - #pkgpart => $part_pkg->pkgpart, - pkgnum => $cust_bill_pkg->pkgnum, - amount => sprintf("%.2f", $cust_bill_pkg->setup), - unit_amount => sprintf("%.2f", $cust_bill_pkg->unitsetup), - quantity => $cust_bill_pkg->quantity, - ext_description => \@d, + if ( $cust_bill_pkg->hidden ) { + $s->{amount} += $cust_bill_pkg->setup; + $s->{unit_amount} += $cust_bill_pkg->unitsetup; + push @{ $s->{ext_description} }, @d; + } else { + $s = { + description => $description, + #pkgpart => $part_pkg->pkgpart, + pkgnum => $cust_bill_pkg->pkgnum, + amount => $cust_bill_pkg->setup, + unit_amount => $cust_bill_pkg->unitsetup, + quantity => $cust_bill_pkg->quantity, + ext_description => \@d, + }; }; } @@ -2869,6 +2887,7 @@ sub _items_cust_bill_pkg { #$cust_bill_pkg->sdate) unless $cust_pkg->part_pkg->hide_svc_detail || $cust_bill_pkg->itemdesc + || $cust_bill_pkg->hidden || $is_summary; push @d, $cust_bill_pkg->details(%details_opt) @@ -2883,17 +2902,45 @@ sub _items_cust_bill_pkg { $amount = $cust_bill_pkg->usage; } - push @b, { - description => $description, - #pkgpart => $part_pkg->pkgpart, - pkgnum => $cust_bill_pkg->pkgnum, - amount => sprintf("%.2f", $amount), - unit_amount => sprintf("%.2f", $cust_bill_pkg->unitrecur), - quantity => $cust_bill_pkg->quantity, - ext_description => \@d, - } unless ( $type eq 'U' && ! $amount ); + if ( !$type || $type eq 'R' ) { + + if ( $cust_bill_pkg->hidden ) { + $r->{amount} += $amount; + $r->{unit_amount} += $cust_bill_pkg->unitrecur; + push @{ $r->{ext_description} }, @d; + } else { + $r = { + description => $description, + #pkgpart => $part_pkg->pkgpart, + pkgnum => $cust_bill_pkg->pkgnum, + amount => $amount, + unit_amount => $cust_bill_pkg->unitrecur, + quantity => $cust_bill_pkg->quantity, + ext_description => \@d, + }; + } + + } elsif ( $amount ) { # && $type eq 'U' + + if ( $cust_bill_pkg->hidden ) { + $u->{amount} += $amount; + $u->{unit_amount} += $cust_bill_pkg->unitrecur; + push @{ $u->{ext_description} }, @d; + } else { + $u = { + description => $description, + #pkgpart => $part_pkg->pkgpart, + pkgnum => $cust_bill_pkg->pkgnum, + amount => $amount, + unit_amount => $cust_bill_pkg->unitrecur, + quantity => $cust_bill_pkg->quantity, + ext_description => \@d, + }; + } - } + } + + } # recurring or usage with recurring charge } else { #pkgnum tax or one-shot line item (??) @@ -2918,6 +2965,14 @@ sub _items_cust_bill_pkg { } + foreach ( $s, $r, $u ) { + if ( $_ ) { + $_->{amount} = sprintf( "%.2f", $_->{amount} ), + $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ), + push @b, { %$_ }; + } + } + @b; } diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index fbc67c542..abf021845 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -63,6 +63,8 @@ supported: =item unitrecur - If not set, defaults to recur +=item hidden - If set to Y, indicates data should not appear as separate line item on invoice + =back sdate and edate are specified as UNIX timestamps; see L. Also @@ -236,6 +238,7 @@ sub check { || $self->ut_numbern('edate') || $self->ut_textn('itemdesc') || $self->ut_textn('itemcomment') + || $self->ut_enum('hidden', [ '', 'Y' ]) ; return $error if $error; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2957579d7..9d41c4bdf 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2535,6 +2535,7 @@ sub bill { 'recur' => \$total_recur, 'tax_matrix' => \%taxlisthash, 'time' => $time, + 'real_pkgpart' => $real_pkgpart, 'options' => \%options, ); if ($error) { @@ -2566,6 +2567,7 @@ sub bill { } elsif ( $postal_pkg ) { + my $real_pkgpart = $postal_pkg->pkgpart; foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) { my %postal_options = %options; delete $postal_options{cancel}; @@ -2578,6 +2580,7 @@ sub bill { 'recur' => \$total_recur, 'tax_matrix' => \%taxlisthash, 'time' => $time, + 'real_pkgpart' => $real_pkgpart, 'options' => \%postal_options, ); if ($error) { @@ -2790,7 +2793,7 @@ sub _make_lines { my (%options) = %{$params{options}}; my $dbh = dbh; - my $real_pkgpart = $cust_pkg->pkgpart; + my $real_pkgpart = $params{real_pkgpart}; my %hash = $cust_pkg->hash; my $old_cust_pkg = new FS::cust_pkg \%hash; @@ -2958,6 +2961,7 @@ sub _make_lines { 'unitrecur' => $unitrecur, 'quantity' => $cust_pkg->quantity, 'details' => \@details, + 'hidden' => $part_pkg->hidden, }; if ( $part_pkg->option('recur_temporality', 1) eq 'preceding' ) { @@ -2981,7 +2985,7 @@ sub _make_lines { ### my $error = - $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time}); + $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time}, $real_pkgpart); return $error if $error; push @$cust_bill_pkgs, $cust_bill_pkg; @@ -3001,6 +3005,7 @@ sub _handle_taxes { my $cust_bill_pkg = shift; my $cust_pkg = shift; my $invoice_time = shift; + my $real_pkgpart = shift; my %cust_bill_pkg = (); my %taxes = (); @@ -3091,20 +3096,29 @@ sub _handle_taxes { } my @display = (); - if ( $conf->exists('separate_usage') ) { + if ( $conf->exists('separate_usage') || $cust_bill_pkg->hidden ) { + + my $temp_pkg = new FS::cust_pkg { pkgpart => $real_pkgpart }; + my %hash = $cust_bill_pkg->hidden # maybe for all bill linked? + ? ( 'section' => $temp_pkg->part_pkg->categoryname ) + : (); + my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!'); my $summary = $cust_pkg->part_pkg->option('summarize_usage', 'Hush!'); - push @display, new FS::cust_bill_pkg_display { type => 'S' }; - push @display, new FS::cust_bill_pkg_display { type => 'R' }; - push @display, new FS::cust_bill_pkg_display { type => 'U', - section => $section - }; + push @display, new FS::cust_bill_pkg_display { type => 'S', %hash }; + push @display, new FS::cust_bill_pkg_display { type => 'R', %hash }; + if ($section && $summary) { - $display[2]->post_total('Y'); push @display, new FS::cust_bill_pkg_display { type => 'U', summary => 'Y', - } + %hash, + }; + $hash{post_total} = 'Y'; } + + $hash{section} = $section if $conf->exists('separate_usage'); + push @display, new FS::cust_bill_pkg_display { type => 'U', %hash }; + } $cust_bill_pkg->set('display', \@display); diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index ba80475eb..3ee9e7f01 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -917,10 +917,12 @@ sub svc_part_pkg_link { sub _part_pkg_link { my( $self, $type ) = @_; - qsearch('part_pkg_link', { 'src_pkgpart' => $self->pkgpart, - 'link_type' => $type, - } - ); + qsearch({ table => 'part_pkg_link', + hashref => { 'src_pkgpart' => $self->pkgpart, + 'link_type' => $type, + }, + order_by => "ORDER BY hidden", + }); } sub self_and_bill_linked { @@ -928,12 +930,18 @@ sub self_and_bill_linked { } sub _self_and_linked { - my( $self, $type ) = @_; + my( $self, $type, $hidden ) = @_; + $hidden ||= ''; + + my @result = (); + foreach ( ( $self, map { $_->dst_pkg->_self_and_linked($type, $_->hidden) } + $self->_part_pkg_link($type) ) ) + { + $_->hidden($hidden) if $hidden; + push @result, $_; + } - ( $self, - map { $_->dst_pkg->_self_and_linked($type) } - $self->_part_pkg_link($type) - ); + (@result); } =item part_pkg_taxoverride [ CLASS ] diff --git a/FS/FS/part_pkg_link.pm b/FS/FS/part_pkg_link.pm index f51736055..fb7a8d387 100644 --- a/FS/FS/part_pkg_link.pm +++ b/FS/FS/part_pkg_link.pm @@ -51,6 +51,11 @@ Destination package (see L) Link type - currently, "bill" (source package bills a line item from target package), or "svc" (source package includes services from target package). +=item hidden + +Flag indicating that this subpackage should be felt, but not seen as an invoice +line item when set to 'Y' + =back =head1 METHODS @@ -114,7 +119,8 @@ 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_text('link_type', [ 'bill', 'svc' ] ) + || $self->ut_enum('link_type', [ 'bill', 'svc' ] ) + || $self->ut_enum('hidden', [ '', 'Y' ] ) ; return $error if $error; diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 4a6079a85..23d4db30d 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -289,7 +289,8 @@ Example: % foreach grep exists($f->{$_}), qw( hashref agent_virt agent_null_right ); % % if ( $type eq 'tablebreak-tr-title' ) { -% $include_common{'table_id'} = 'TableNumber'. $tablenum++ +% $include_common{'table_id'} = 'TableNumber'. $tablenum++; +% $include_common{'colspan'} = $f->{colspan} if $f->{colspan}; % } % % my $layer_prefix_on = ''; @@ -317,6 +318,27 @@ Example: % @include; % }; % +% my $column_sub = sub { +% my %opt = @_; +% +% my $column = delete($opt{field}); +% my $fieldnum = delete($opt{fieldnum}); +% my $include = delete($opt{type}) || 'text'; +% $include = "input-$include" if $include =~ /^(text|money|percentage)$/; +% +% ( "/elements/$include.html", +% 'field' => $field.'__'.$column.$fieldnum, +% 'id' => $field.'__'.$column.$fieldnum, +% 'layer_prefix' => $field.'__'.$column.$fieldnum.".", +% ( $fieldnum +% ? ('cell_style' => 'border-top:1px solid black') +% : () +% ), +% 'cgi' => $cgi, +% %opt, +% ); +% }; +% % unless ( $type =~ /^column/ ) { % $g_row = 1 if $type eq 'tablebreak-tr-title'; % $g_row++; @@ -382,8 +404,35 @@ Example: % 'layer_values' => $layer_values, % 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ), % ); +% $existing[0] =~ s(^/elements/tr-)(/elements/); +% my @label = @existing; +% $label[0] = '/elements/tr-td-label.html'; + <% include( @label ) %> + <% include( @existing ) %> + + +% if ( $f->{'m2_fields'} ) { +% foreach my $c ( @{ $f->{'m2_fields'} } ) { +% my $column = $c->{field}; +% my @column = &{ $column_sub }( %$c, +% 'fieldnum' => $fieldnum, +% 'curr_value' => $name_obj->$column() +% ); + + + <% $c->{'label'} || '' %> + + + <% include( @column ) %> + +% } +% } + + % $fieldnum++; % $g_row++; @@ -409,9 +458,40 @@ Example: % 'onchange' => $onchange, % ( $fieldnum ? ('cell_style' => 'border-top:1px solid black') : () ), % ); +% +% if ( $f->{'m2name_table'} || $f->{'m2m_method'} ) { +% $include[0] =~ s(^/elements/tr-)(/elements/); +% my @label = @include; +% $label[0] = '/elements/tr-td-label.html'; + + <% include( @label ) %> + + <% include( @include ) %> + + +% if ( $f->{'m2_fields'} ) { +% foreach my $c ( @{ $f->{'m2_fields'} } ) { +% my $column = $c->{field}; +% my @column = &{ $column_sub }( %$c, 'fieldnum' => $fieldnum ); + + + <% $c->{'label'} || '' %> + + + <% include( @column ) %> + +% } +% } - <% include( @include ) %> + +% } else { + + <% include( @include ) %> + +% } % if ( $f->{'m2name_table'} || $f->{'m2m_method'} ) {