From 55675d6cdd93f00b7c0ac93403e8c4d66567a729 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 1 Aug 2012 13:16:42 -0700 Subject: [PATCH] invoice voiding, RT#18677 --- FS/FS/TemplateItem_Mixin.pm | 317 +++++++++++++++++++++ FS/FS/cust_bill_pkg.pm | 317 +-------------------- FS/FS/cust_bill_pkg_discount_void.pm | 129 +++++++++ FS/FS/cust_bill_pkg_void.pm | 30 +- FS/FS/cust_bill_void.pm | 30 +- FS/t/cust_bill_pkg_discount_void.t | 5 + httemplate/view/cust_bill_void.html | 13 +- .../cust_main/payment_history/voided_invoice.html | 3 +- 8 files changed, 528 insertions(+), 316 deletions(-) create mode 100644 FS/FS/TemplateItem_Mixin.pm create mode 100644 FS/FS/cust_bill_pkg_discount_void.pm create mode 100644 FS/t/cust_bill_pkg_discount_void.t diff --git a/FS/FS/TemplateItem_Mixin.pm b/FS/FS/TemplateItem_Mixin.pm new file mode 100644 index 000000000..6d7ea26bc --- /dev/null +++ b/FS/FS/TemplateItem_Mixin.pm @@ -0,0 +1,317 @@ +package FS::TemplateItem_Mixin; + +use strict; +use vars qw( $DEBUG $me ); # but NOT $conf +use Carp; +use FS::UID; +use FS::Record qw( qsearch qsearchs dbh ); +use FS::part_pkg; +use FS::cust_pkg; + +$DEBUG = 0; +$me = '[FS::TemplateItem_Mixin]'; + +=item cust_pkg + +Returns the package (see L) for this invoice line item. + +=cut + +sub cust_pkg { + my $self = shift; + carp "$me $self -> cust_pkg" if $DEBUG; + qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); +} + +=item part_pkg + +Returns the package definition for this invoice line item. + +=cut + +sub part_pkg { + my $self = shift; + if ( $self->pkgpart_override ) { + qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } ); + } else { + my $part_pkg; + my $cust_pkg = $self->cust_pkg; + $part_pkg = $cust_pkg->part_pkg if $cust_pkg; + $part_pkg; + } + +} + +=item desc + +Returns a description for this line item. For typical line items, this is the +I field of the corresponding B object (see L). +For one-shot line items and named taxes, it is the I field of this +line item, and for generic taxes, simply returns "Tax". + +=cut + +sub desc { + my $self = shift; + + if ( $self->pkgnum > 0 ) { + $self->itemdesc || $self->part_pkg->pkg; + } else { + my $desc = $self->itemdesc || 'Tax'; + $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/; + $desc; + } +} + +=item details [ OPTION => VALUE ... ] + +Returns an array of detail information for the invoice line item. + +Currently available options are: I, I and +I. + +If I is set to html or latex then the array members are improved +for tabular appearance in those environments if possible. + +If I is set then the array members are processed by this +function before being returned. + +I overrides the normal HTML or LaTeX function for returning +formatted CDRs. It can be set to a subroutine which returns an empty list +to skip usage detail: + + 'format_function' => sub { () }, + +=cut + +sub details { + my ( $self, %opt ) = @_; + my $escape_function = $opt{escape_function} || sub { shift }; + + my $csv = new Text::CSV_XS; + + if ( $opt{format_function} ) { + + #this still expects to be passed a cust_bill_pkg_detail object as the + #second argument, which is expensive + carp "deprecated format_function passed to cust_bill_pkg->details"; + my $format_sub = $opt{format_function} if $opt{format_function}; + + map { ( $_->format eq 'C' + ? &{$format_sub}( $_->detail, $_ ) + : &{$escape_function}( $_->detail ) + ) + } + qsearch ({ 'table' => $self->detail_table, + 'hashref' => { 'billpkgnum' => $self->billpkgnum }, + 'order_by' => 'ORDER BY detailnum', + }); + + } elsif ( $opt{'no_usage'} ) { + + my $sql = "SELECT detail FROM ". $self->detail_table. + " WHERE billpkgnum = ". $self->billpkgnum. + " AND ( format IS NULL OR format != 'C' ) ". + " ORDER BY detailnum"; + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + + map &{$escape_function}( $_->[0] ), @{ $sth->fetchall_arrayref }; + + } else { + + my $format_sub; + my $format = $opt{format} || ''; + if ( $format eq 'html' ) { + + $format_sub = sub { my $detail = shift; + $csv->parse($detail) or return "can't parse $detail"; + join('', map { &$escape_function($_) } + $csv->fields + ); + }; + + } elsif ( $format eq 'latex' ) { + + $format_sub = sub { + my $detail = shift; + $csv->parse($detail) or return "can't parse $detail"; + #join(' & ', map { '\small{'. &$escape_function($_). '}' } + # $csv->fields ); + my $result = ''; + my $column = 1; + foreach ($csv->fields) { + $result .= ' & ' if $column > 1; + if ($column > 6) { # KLUDGE ALERT! + $result .= '\multicolumn{1}{l}{\scriptsize{'. + &$escape_function($_). '}}'; + }else{ + $result .= '\scriptsize{'. &$escape_function($_). '}'; + } + $column++; + } + $result; + }; + + } else { + + $format_sub = sub { my $detail = shift; + $csv->parse($detail) or return "can't parse $detail"; + join(' - ', map { &$escape_function($_) } + $csv->fields + ); + }; + + } + + my $sql = "SELECT format, detail FROM ". $self->detail_table. + " WHERE billpkgnum = ". $self->billpkgnum. + " ORDER BY detailnum"; + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + + #avoid the fetchall_arrayref and loop for less memory usage? + + map { (defined($_->[0]) && $_->[0] eq 'C') + ? &{$format_sub}( $_->[1] ) + : &{$escape_function}( $_->[1] ); + } + @{ $sth->fetchall_arrayref }; + + } + +} + +=item details_header [ OPTION => VALUE ... ] + +Returns a list representing an invoice line item detail header, if any. +This relies on the behavior of voip_cdr in that it expects the header +to be the first CSV formatted detail (as is expected by invoice generation +routines). Returns the empty list otherwise. + +=cut + +sub details_header { + my $self = shift; + + my $csv = new Text::CSV_XS; + + my @detail = + qsearch ({ 'table' => $self->detail_table, + 'hashref' => { 'billpkgnum' => $self->billpkgnum, + 'format' => 'C', + }, + 'order_by' => 'ORDER BY detailnum LIMIT 1', + }); + return() unless scalar(@detail); + $csv->parse($detail[0]->detail) or return (); + $csv->fields; +} + +=item quantity + +=cut + +sub quantity { + my( $self, $value ) = @_; + if ( defined($value) ) { + $self->setfield('quantity', $value); + } + $self->getfield('quantity') || 1; +} + +=item unitsetup + +=cut + +sub unitsetup { + my( $self, $value ) = @_; + if ( defined($value) ) { + $self->setfield('unitsetup', $value); + } + $self->getfield('unitsetup') eq '' + ? $self->getfield('setup') + : $self->getfield('unitsetup'); +} + +=item unitrecur + +=cut + +sub unitrecur { + my( $self, $value ) = @_; + if ( defined($value) ) { + $self->setfield('unitrecur', $value); + } + $self->getfield('unitrecur') eq '' + ? $self->getfield('recur') + : $self->getfield('unitrecur'); +} + +=item cust_bill_pkg_display [ type => TYPE ] + +Returns an array of display information for the invoice line item optionally +limited to 'TYPE'. + +=cut + +sub cust_bill_pkg_display { + my ( $self, %opt ) = @_; + + my $class = 'FS::'. $self->display_table; + + my $default = $class->new( { billpkgnum =>$self->billpkgnum } ); + + my $type = $opt{type} if exists $opt{type}; + my @result; + + if ( $self->get('display') ) { + @result = grep { defined($type) ? ($type eq $_->type) : 1 } + @{ $self->get('display') }; + } else { + my $hashref = { 'billpkgnum' => $self->billpkgnum }; + $hashref->{type} = $type if defined($type); + + @result = qsearch ({ 'table' => $self->display_table, + 'hashref' => { 'billpkgnum' => $self->billpkgnum }, + 'order_by' => 'ORDER BY billpkgdisplaynum', + }); + } + + push @result, $default unless ( scalar(@result) || $type ); + + @result; + +} + +=item cust_bill_pkg_detail [ CLASSNUM ] + +Returns the list of associated cust_bill_pkg_detail objects +The optional CLASSNUM argument will limit the details to the specified usage +class. + +=cut + +sub cust_bill_pkg_detail { + my $self = shift; + my $classnum = shift || ''; + + my %hash = ( 'billpkgnum' => $self->billpkgnum ); + $hash{classnum} = $classnum if $classnum; + + qsearch( $self->detail_table, \%hash ), + +} + +=item cust_bill_pkg_discount + +Returns the list of associated cust_bill_pkg_discount objects. + +=cut + +sub cust_bill_pkg_discount { + my $self = shift; + qsearch( $self->discount_table, { 'billpkgnum' => $self->billpkgnum } ); +} + +1; diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 2ceef0474..304d51d6a 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -1,14 +1,13 @@ package FS::cust_bill_pkg; +use base qw( FS::TemplateItem_Mixin FS::cust_main_Mixin FS::Record ); use strict; use vars qw( @ISA $DEBUG $me ); use Carp; use List::Util qw( sum ); use Text::CSV_XS; -use FS::Record qw( qsearch qsearchs dbdef dbh ); -use FS::cust_main_Mixin; +use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_pkg; -use FS::part_pkg; use FS::cust_bill; use FS::cust_bill_pkg_detail; use FS::cust_bill_pkg_display; @@ -26,7 +25,6 @@ use FS::cust_bill_pkg_tax_location_void; use FS::cust_bill_pkg_tax_rate_location_void; use FS::cust_tax_exempt_pkg_void; -@ISA = qw( FS::cust_main_Mixin FS::Record ); $DEBUG = 0; $me = '[FS::cust_bill_pkg]'; @@ -125,6 +123,13 @@ customer object (see L). sub table { 'cust_bill_pkg'; } +sub detail_table { 'cust_bill_pkg_detail'; } +sub display_table { 'cust_bill_pkg_display'; } +sub discount_table { 'cust_bill_pkg_discount'; } +#sub tax_location_table { 'cust_bill_pkg_tax_location'; } +#sub tax_rate_location_table { 'cust_bill_pkg_tax_rate_location'; } +#sub tax_exempt_pkg_table { 'cust_tax_exempt_pkg'; } + =item insert Adds this line item to the database. If there is an error, returns the error, @@ -270,6 +275,7 @@ sub void { foreach my $table (qw( cust_bill_pkg_detail cust_bill_pkg_display + cust_bill_pkg_discount cust_bill_pkg_tax_location cust_bill_pkg_tax_rate_location cust_tax_exempt_pkg @@ -326,6 +332,7 @@ sub delete { foreach my $table (qw( cust_bill_pkg_detail cust_bill_pkg_display + cust_bill_pkg_discount cust_bill_pkg_tax_location cust_bill_pkg_tax_rate_location cust_tax_exempt_pkg @@ -462,36 +469,6 @@ sub regularize_details { return; } -=item cust_pkg - -Returns the package (see L) for this invoice line item. - -=cut - -sub cust_pkg { - my $self = shift; - carp "$me $self -> cust_pkg" if $DEBUG; - qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); -} - -=item part_pkg - -Returns the package definition for this invoice line item. - -=cut - -sub part_pkg { - my $self = shift; - if ( $self->pkgpart_override ) { - qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } ); - } else { - my $part_pkg; - my $cust_pkg = $self->cust_pkg; - $part_pkg = $cust_pkg->part_pkg if $cust_pkg; - $part_pkg; - } -} - =item cust_bill Returns the invoice (see L) for this invoice line item. @@ -521,173 +498,6 @@ sub previous_cust_bill_pkg { }); } -=item details [ OPTION => VALUE ... ] - -Returns an array of detail information for the invoice line item. - -Currently available options are: I, I and -I. - -If I is set to html or latex then the array members are improved -for tabular appearance in those environments if possible. - -If I is set then the array members are processed by this -function before being returned. - -I overrides the normal HTML or LaTeX function for returning -formatted CDRs. It can be set to a subroutine which returns an empty list -to skip usage detail: - - 'format_function' => sub { () }, - -=cut - -sub details { - my ( $self, %opt ) = @_; - my $escape_function = $opt{escape_function} || sub { shift }; - - my $csv = new Text::CSV_XS; - - if ( $opt{format_function} ) { - - #this still expects to be passed a cust_bill_pkg_detail object as the - #second argument, which is expensive - carp "deprecated format_function passed to cust_bill_pkg->details"; - my $format_sub = $opt{format_function} if $opt{format_function}; - - map { ( $_->format eq 'C' - ? &{$format_sub}( $_->detail, $_ ) - : &{$escape_function}( $_->detail ) - ) - } - qsearch ({ 'table' => 'cust_bill_pkg_detail', - 'hashref' => { 'billpkgnum' => $self->billpkgnum }, - 'order_by' => 'ORDER BY detailnum', - }); - - } elsif ( $opt{'no_usage'} ) { - - my $sql = "SELECT detail FROM cust_bill_pkg_detail ". - " WHERE billpkgnum = ". $self->billpkgnum. - " AND ( format IS NULL OR format != 'C' ) ". - " ORDER BY detailnum"; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute or die $sth->errstr; - - map &{$escape_function}( $_->[0] ), @{ $sth->fetchall_arrayref }; - - } else { - - my $format_sub; - my $format = $opt{format} || ''; - if ( $format eq 'html' ) { - - $format_sub = sub { my $detail = shift; - $csv->parse($detail) or return "can't parse $detail"; - join('', map { &$escape_function($_) } - $csv->fields - ); - }; - - } elsif ( $format eq 'latex' ) { - - $format_sub = sub { - my $detail = shift; - $csv->parse($detail) or return "can't parse $detail"; - #join(' & ', map { '\small{'. &$escape_function($_). '}' } - # $csv->fields ); - my $result = ''; - my $column = 1; - foreach ($csv->fields) { - $result .= ' & ' if $column > 1; - if ($column > 6) { # KLUDGE ALERT! - $result .= '\multicolumn{1}{l}{\scriptsize{'. - &$escape_function($_). '}}'; - }else{ - $result .= '\scriptsize{'. &$escape_function($_). '}'; - } - $column++; - } - $result; - }; - - } else { - - $format_sub = sub { my $detail = shift; - $csv->parse($detail) or return "can't parse $detail"; - join(' - ', map { &$escape_function($_) } - $csv->fields - ); - }; - - } - - my $sql = "SELECT format, detail FROM cust_bill_pkg_detail ". - " WHERE billpkgnum = ". $self->billpkgnum. - " ORDER BY detailnum"; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute or die $sth->errstr; - - #avoid the fetchall_arrayref and loop for less memory usage? - - map { (defined($_->[0]) && $_->[0] eq 'C') - ? &{$format_sub}( $_->[1] ) - : &{$escape_function}( $_->[1] ); - } - @{ $sth->fetchall_arrayref }; - - } - -} - -=item details_header [ OPTION => VALUE ... ] - -Returns a list representing an invoice line item detail header, if any. -This relies on the behavior of voip_cdr in that it expects the header -to be the first CSV formatted detail (as is expected by invoice generation -routines). Returns the empty list otherwise. - -=cut - -sub details_header { - my $self = shift; - return '' unless defined dbdef->table('cust_bill_pkg_detail'); - - my $csv = new Text::CSV_XS; - - my @detail = - qsearch ({ 'table' => 'cust_bill_pkg_detail', - 'hashref' => { 'billpkgnum' => $self->billpkgnum, - 'format' => 'C', - }, - 'order_by' => 'ORDER BY detailnum LIMIT 1', - }); - return() unless scalar(@detail); - $csv->parse($detail[0]->detail) or return (); - $csv->fields; -} - -=item desc - -Returns a description for this line item. For typical line items, this is the -I field of the corresponding B object (see L). -For one-shot line items and named taxes, it is the I field of this -line item, and for generic taxes, simply returns "Tax". - -=cut - -sub desc { - my $self = shift; - - if ( $self->pkgnum > 0 ) { - $self->itemdesc || $self->part_pkg->pkg; - } else { - my $desc = $self->itemdesc || 'Tax'; - $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/; - $desc; - } -} - =item owed_setup Returns the amount owed (still outstanding) on this line item's setup fee, @@ -765,45 +575,6 @@ sub units { $self->pkgnum ? $self->part_pkg->calc_units($self->cust_pkg) : 0; # 1? } -=item quantity - -=cut - -sub quantity { - my( $self, $value ) = @_; - if ( defined($value) ) { - $self->setfield('quantity', $value); - } - $self->getfield('quantity') || 1; -} - -=item unitsetup - -=cut - -sub unitsetup { - my( $self, $value ) = @_; - if ( defined($value) ) { - $self->setfield('unitsetup', $value); - } - $self->getfield('unitsetup') eq '' - ? $self->getfield('setup') - : $self->getfield('unitsetup'); -} - -=item unitrecur - -=cut - -sub unitrecur { - my( $self, $value ) = @_; - if ( defined($value) ) { - $self->setfield('unitrecur', $value); - } - $self->getfield('unitrecur') eq '' - ? $self->getfield('recur') - : $self->getfield('unitrecur'); -} =item set_display OPTION => VALUE ... @@ -1015,44 +786,8 @@ sub usage_classes { } -=item cust_bill_pkg_display [ type => TYPE ] - -Returns an array of display information for the invoice line item optionally -limited to 'TYPE'. - -=cut - -sub cust_bill_pkg_display { - my ( $self, %opt ) = @_; - - my $default = - new FS::cust_bill_pkg_display { billpkgnum =>$self->billpkgnum }; - - my $type = $opt{type} if exists $opt{type}; - my @result; - - if ( $self->get('display') ) { - @result = grep { defined($type) ? ($type eq $_->type) : 1 } - @{ $self->get('display') }; - } else { - my $hashref = { 'billpkgnum' => $self->billpkgnum }; - $hashref->{type} = $type if defined($type); - - @result = qsearch ({ 'table' => 'cust_bill_pkg_display', - 'hashref' => { 'billpkgnum' => $self->billpkgnum }, - 'order_by' => 'ORDER BY billpkgdisplaynum', - }); - } - - push @result, $default unless ( scalar(@result) || $type ); - - @result; - -} - # reserving this name for my friends FS::{tax_rate|cust_main_county}::taxline # and FS::cust_main::bill - sub _cust_tax_exempt_pkg { my ( $self ) = @_; @@ -1080,36 +815,6 @@ sub cust_bill_pkg_tax_Xlocation { } -=item cust_bill_pkg_detail [ CLASSNUM ] - -Returns the list of associated cust_bill_pkg_detail objects -The optional CLASSNUM argument will limit the details to the specified usage -class. - -=cut - -sub cust_bill_pkg_detail { - my $self = shift; - my $classnum = shift || ''; - - my %hash = ( 'billpkgnum' => $self->billpkgnum ); - $hash{classnum} = $classnum if $classnum; - - qsearch( 'cust_bill_pkg_detail', \%hash ), - -} - -=item cust_bill_pkg_discount - -Returns the list of associated cust_bill_pkg_discount objects. - -=cut - -sub cust_bill_pkg_discount { - my $self = shift; - qsearch( 'cust_bill_pkg_discount', { 'billpkgnum' => $self->billpkgnum } ); -} - =item recur_show_zero =cut diff --git a/FS/FS/cust_bill_pkg_discount_void.pm b/FS/FS/cust_bill_pkg_discount_void.pm new file mode 100644 index 000000000..859ef3cf2 --- /dev/null +++ b/FS/FS/cust_bill_pkg_discount_void.pm @@ -0,0 +1,129 @@ +package FS::cust_bill_pkg_discount_void; + +use strict; +use base qw( FS::Record ); +use FS::Record; # qw( qsearch qsearchs ); +use FS::cust_bill_pkg_void; +use FS::cust_pkg_discount; + +=head1 NAME + +FS::cust_bill_pkg_discount_void - Object methods for cust_bill_pkg_discount_void records + +=head1 SYNOPSIS + + use FS::cust_bill_pkg_discount_void; + + $record = new FS::cust_bill_pkg_discount_void \%hash; + $record = new FS::cust_bill_pkg_discount_void { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_bill_pkg_discount_void object represents the slice of a customer +discount applied to a specific voided line item. +FS::cust_bill_pkg_discount_void inherits from FS::Record. The following fields +are currently supported: + +=over 4 + +=item billpkgdiscountnum + +primary key + +=item billpkgnum + +billpkgnum + +=item pkgdiscountnum + +pkgdiscountnum + +=item amount + +amount + +=item months + +months + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +sub table { 'cust_bill_pkg_discount_void'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_number('billpkgdiscountnum') + || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg_void', 'billpkgnum' ) + || $self->ut_foreign_key('pkgdiscountnum', 'cust_pkg_discount', 'pkgdiscountnum' ) + || $self->ut_money('amount') + || $self->ut_float('months') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_bill_pkg_void.pm b/FS/FS/cust_bill_pkg_void.pm index 198283955..7855d58c6 100644 --- a/FS/FS/cust_bill_pkg_void.pm +++ b/FS/FS/cust_bill_pkg_void.pm @@ -1,8 +1,12 @@ package FS::cust_bill_pkg_void; +use base qw( FS::TemplateItem_Mixin FS::Record ); use strict; -use base qw( FS::Record ); -use FS::Record; # qw( qsearch qsearchs ); +use FS::Record qw( qsearch qsearchs ); +use FS::cust_bill_void; +use FS::cust_bill_pkg_detail_void; +use FS::cust_bill_pkg_display_void; +use FS::cust_bill_pkg_discount_void; =head1 NAME @@ -113,6 +117,13 @@ points to. You can ask the object for a copy with the I method. sub table { 'cust_bill_pkg_void'; } +sub detail_table { 'cust_bill_pkg_detail_void'; } +sub display_table { 'cust_bill_pkg_display_void'; } +sub discount_table { 'cust_bill_pkg_discount_void'; } +#sub tax_location_table { 'cust_bill_pkg_tax_location'; } +#sub tax_rate_location_table { 'cust_bill_pkg_tax_rate_location'; } +#sub tax_exempt_pkg_table { 'cust_tax_exempt_pkg'; } + =item insert Adds this record to the database. If there is an error, returns the error, @@ -147,7 +158,7 @@ sub check { my $error = $self->ut_number('billpkgnum') || $self->ut_snumber('pkgnum') - || $self->ut_number('invnum') #cust_bill or cust_bill_void ? + || $self->ut_number('invnum') #cust_bill or cust_bill_void, if we ever support line item voiding || $self->ut_numbern('pkgpart_override') || $self->ut_money('setup') || $self->ut_money('recur') @@ -167,6 +178,19 @@ sub check { $self->SUPER::check; } +=item cust_bill + +Returns the voided invoice (see L) for this voided line +item. + +=cut + +sub cust_bill { + my $self = shift; + #cust_bill or cust_bill_void, if we ever support line item voiding + qsearchs( 'cust_bill_void', { 'invnum' => $self->invnum } ); +} + =back =head1 BUGS diff --git a/FS/FS/cust_bill_void.pm b/FS/FS/cust_bill_void.pm index c782172b5..cd6a9e13b 100644 --- a/FS/FS/cust_bill_void.pm +++ b/FS/FS/cust_bill_void.pm @@ -2,10 +2,11 @@ package FS::cust_bill_void; use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::otaker_Mixin FS::Record ); use strict; -use FS::Record qw( qsearchs ); #qsearch ); +use FS::Record qw( qsearch qsearchs ); use FS::cust_main; use FS::cust_statement; use FS::access_user; +use FS::cust_bill_pkg_void; =head1 NAME @@ -203,6 +204,33 @@ sub void_access_user { qsearchs('access_user', { 'usernum' => $self->void_usernum } ); } +=item cust_main + +=cut + +sub cust_main { + my $self = shift; + qsearchs('cust_main', { 'custnum' => $self->custnum } ); +} + +=item cust_bill_pkg + +=cut + +sub cust_bill_pkg { #actually cust_bill_pkg_void objects + my $self = shift; + qsearch('cust_bill_pkg_void', { invnum=>$self->invnum }); +} + +=back + +=item enable_previous + +=cut + +sub enable_previous { 0 } + + =back =head1 BUGS diff --git a/FS/t/cust_bill_pkg_discount_void.t b/FS/t/cust_bill_pkg_discount_void.t new file mode 100644 index 000000000..e591eb03d --- /dev/null +++ b/FS/t/cust_bill_pkg_discount_void.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_bill_pkg_discount_void; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/view/cust_bill_void.html b/httemplate/view/cust_bill_void.html index c7c5da146..148c0ed7e 100755 --- a/httemplate/view/cust_bill_void.html +++ b/httemplate/view/cust_bill_void.html @@ -11,6 +11,11 @@ %#

% #} +
VOID
+ % if ( $conf->exists('invoice_html') ) { <% join('', $cust_bill_void->print_html(\%opt) ) %> % } else { @@ -43,13 +48,13 @@ my $cust_bill_void = qsearchs({ 'select' => 'cust_bill_void.*', 'table' => 'cust_bill_void', #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', - 'hashref' => { 'quotationnum' => $quotationnum }, + 'hashref' => { 'invnum' => $invnum }, #'extra_sql' => ' AND '. $curuser->agentnums_sql, }); -die "Quotation #$quotationnum not found!" unless $quotation; +die "Voided invoice #$invnum not found!" unless $cust_bill_void; -my $custnum = $cust_bill->custnum; -my $display_custnum = $cust_bill->cust_main->display_custnum; +my $custnum = $cust_bill_void->custnum; +my $display_custnum = $cust_bill_void->cust_main->display_custnum; #my $link = "invnum=$invnum"; diff --git a/httemplate/view/cust_main/payment_history/voided_invoice.html b/httemplate/view/cust_main/payment_history/voided_invoice.html index 422edb2f6..7bf206352 100644 --- a/httemplate/view/cust_main/payment_history/voided_invoice.html +++ b/httemplate/view/cust_main/payment_history/voided_invoice.html @@ -21,9 +21,8 @@ my $under = ''; my $invnum = $cust_bill_void->invnum; -#XXX use cust_bill.cgi or? my $link = $curuser->access_right('View invoices') - ? qq!! + ? qq!! : ''; my $unvoid = ''; #XXX unvoid -- 2.11.0