summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2012-08-01 13:16:42 -0700
committerIvan Kohler <ivan@freeside.biz>2012-08-01 13:16:42 -0700
commit55675d6cdd93f00b7c0ac93403e8c4d66567a729 (patch)
tree8cdcfe054c725489d25a4fce515234364e7c1a1d /FS
parent338eca0837fdaddbda2a34ddf8af3f815de13d26 (diff)
invoice voiding, RT#18677
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/TemplateItem_Mixin.pm317
-rw-r--r--FS/FS/cust_bill_pkg.pm317
-rw-r--r--FS/FS/cust_bill_pkg_discount_void.pm129
-rw-r--r--FS/FS/cust_bill_pkg_void.pm30
-rw-r--r--FS/FS/cust_bill_void.pm30
-rw-r--r--FS/t/cust_bill_pkg_discount_void.t5
6 files changed, 518 insertions, 310 deletions
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<FS::cust_pkg>) 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<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
+For one-shot line items and named taxes, it is the I<itemdesc> 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<format>, I<escape_function> and
+I<format_function>.
+
+If I<format> is set to html or latex then the array members are improved
+for tabular appearance in those environments if possible.
+
+If I<escape_function> is set then the array members are processed by this
+function before being returned.
+
+I<format_function> 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('</TD><TD>', 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<FS::cust_main>).
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<FS::cust_pkg>) 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<FS::cust_bill>) 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<format>, I<escape_function> and
-I<format_function>.
-
-If I<format> is set to html or latex then the array members are improved
-for tabular appearance in those environments if possible.
-
-If I<escape_function> is set then the array members are processed by this
-function before being returned.
-
-I<format_function> 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('</TD><TD>', 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<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
-For one-shot line items and named taxes, it is the I<itemdesc> 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<hash> 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<FS::Record>, 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<hash> 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<FS::cust_bill_void>) 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";