X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_bill_pkg.pm;h=2c79209c5d5aacace86b82cde857841e4032f19c;hp=1629b79f5be0b8e3090f0b753666908a2e8e323e;hb=c7bf005860b761a55ca075df987fb3b5ade8c242;hpb=6115c160d19e6c82e3f713d0d79128fbf45e46da diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 1629b79f5..2c79209c5 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -1,7 +1,8 @@ package FS::cust_bill_pkg; use strict; -use vars qw( @ISA $DEBUG ); +use vars qw( @ISA $DEBUG $me ); +use Carp; use FS::Record qw( qsearch qsearchs dbdef dbh ); use FS::cust_main_Mixin; use FS::cust_pkg; @@ -9,12 +10,18 @@ use FS::part_pkg; use FS::cust_bill; use FS::cust_bill_pkg_detail; use FS::cust_bill_pkg_display; +use FS::cust_bill_pkg_discount; use FS::cust_bill_pay_pkg; use FS::cust_credit_bill_pkg; +use FS::cust_tax_exempt_pkg; +use FS::cust_bill_pkg_tax_location; +use FS::cust_bill_pkg_tax_rate_location; +use FS::cust_tax_adjustment; @ISA = qw( FS::cust_main_Mixin FS::Record ); $DEBUG = 0; +$me = '[FS::cust_bill_pkg]'; =head1 NAME @@ -29,10 +36,6 @@ FS::cust_bill_pkg - Object methods for cust_bill_pkg records $error = $record->insert; - $error = $new_record->replace($old_record); - - $error = $record->delete; - $error = $record->check; =head1 DESCRIPTION @@ -43,28 +46,57 @@ supported: =over 4 -=item billpkgnum - primary key +=item billpkgnum + +primary key + +=item invnum + +invoice (see L) + +=item pkgnum + +package (see L) or 0 for the special virtual sales tax package, or -1 for the virtual line item (itemdesc is used for the line) + +=item pkgpart_override + +optional package definition (see L) override + +=item setup -=item invnum - invoice (see L) +setup fee -=item pkgnum - package (see L) or 0 for the special virtual sales tax package, or -1 for the virtual line item (itemdesc is used for the line) +=item recur -=item pkgpart_override - optional package definition (see L) override -=item setup - setup fee +recurring fee -=item recur - recurring fee +=item sdate -=item sdate - starting date of recurring fee +starting date of recurring fee -=item edate - ending date of recurring fee +=item edate -=item itemdesc - Line item description (overrides normal package description) +ending date of recurring fee + +=item itemdesc + +Line item description (overrides normal package description) + +=item quantity + +If not set, defaults to 1 + +=item unitsetup + +If not set, defaults to setup + +=item unitrecur -=item quantity - If not set, defaults to 1 +If not set, defaults to recur -=item unitsetup - If not set, defaults to setup +=item hidden -=item unitrecur - If not set, defaults to recur +If set to Y, indicates data should not appear as separate line item on invoice =back @@ -112,7 +144,7 @@ sub insert { return $error; } - if ( defined dbdef->table('cust_bill_pkg_detail') && $self->get('details') ) { + if ( $self->get('details') ) { foreach my $detail ( @{$self->get('details')} ) { my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail { 'billpkgnum' => $self->billpkgnum, @@ -120,26 +152,86 @@ sub insert { 'detail' => (ref($detail) ? $detail->[1] : $detail ), 'amount' => (ref($detail) ? $detail->[2] : '' ), 'classnum' => (ref($detail) ? $detail->[3] : '' ), + 'phonenum' => (ref($detail) ? $detail->[4] : '' ), + 'accountcode' => (ref($detail) ? $detail->[5] : '' ), + 'duration' => (ref($detail) ? $detail->[6] : '' ), + 'regionname' => (ref($detail) ? $detail->[7] : '' ), }; $error = $cust_bill_pkg_detail->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error; + return "error inserting cust_bill_pkg_detail: $error"; } } } - if ( defined dbdef->table('cust_bill_pkg_display') && $self->get('display') ){ + if ( $self->get('display') ) { foreach my $cust_bill_pkg_display ( @{ $self->get('display') } ) { $cust_bill_pkg_display->billpkgnum($self->billpkgnum); $error = $cust_bill_pkg_display->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error; + return "error inserting cust_bill_pkg_display: $error"; + } + } + } + + if ( $self->get('discounts') ) { + foreach my $cust_bill_pkg_discount ( @{$self->get('discounts')} ) { + $cust_bill_pkg_discount->billpkgnum($self->billpkgnum); + $error = $cust_bill_pkg_discount->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error inserting cust_bill_pkg_discount: $error"; + } + } + } + + if ( $self->_cust_tax_exempt_pkg ) { + foreach my $cust_tax_exempt_pkg ( @{$self->_cust_tax_exempt_pkg} ) { + $cust_tax_exempt_pkg->billpkgnum($self->billpkgnum); + $error = $cust_tax_exempt_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error inserting cust_tax_exempt_pkg: $error"; } } } + my $tax_location = $self->get('cust_bill_pkg_tax_location'); + if ( $tax_location ) { + foreach my $cust_bill_pkg_tax_location ( @$tax_location ) { + $cust_bill_pkg_tax_location->billpkgnum($self->billpkgnum); + $error = $cust_bill_pkg_tax_location->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error inserting cust_bill_pkg_tax_location: $error"; + } + } + } + + my $tax_rate_location = $self->get('cust_bill_pkg_tax_rate_location'); + if ( $tax_rate_location ) { + foreach my $cust_bill_pkg_tax_rate_location ( @$tax_rate_location ) { + $cust_bill_pkg_tax_rate_location->billpkgnum($self->billpkgnum); + $error = $cust_bill_pkg_tax_rate_location->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error inserting cust_bill_pkg_tax_rate_location: $error"; + } + } + } + + my $cust_tax_adjustment = $self->get('cust_tax_adjustment'); + if ( $cust_tax_adjustment ) { + $cust_tax_adjustment->billpkgnum($self->billpkgnum); + $error = $cust_tax_adjustment->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error replacing cust_tax_adjustment: $error"; + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -147,26 +239,80 @@ sub insert { =item delete -Currently unimplemented. I don't remove line items because there would then be -no record the items ever existed (which is bad, no?) +Not recommended. =cut sub delete { - return "Can't delete cust_bill_pkg records!"; -} + my $self = shift; -=item replace OLD_RECORD + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; -Currently unimplemented. This would be even more of an accounting nightmare -than deleteing the items. Just don't do it. + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; -=cut + foreach my $table (qw( + cust_bill_pkg_detail + cust_bill_pkg_display + cust_bill_pkg_tax_location + cust_bill_pkg_tax_rate_location + cust_tax_exempt_pkg + cust_bill_pay_pkg + cust_credit_bill_pkg + )) { + + foreach my $linked ( qsearch($table, { billpkgnum=>$self->billpkgnum }) ) { + my $error = $linked->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + } + + foreach my $cust_tax_adjustment ( + qsearch('cust_tax_adjustment', { billpkgnum=>$self->billpkgnum }) + ) { + $cust_tax_adjustment->billpkgnum(''); #NULL + my $error = $cust_tax_adjustment->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + my $error = $self->SUPER::delete(@_); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; -sub replace { - return "Can't modify cust_bill_pkg records!"; } +#alas, bin/follow-tax-rename +# +#=item replace OLD_RECORD +# +#Currently unimplemented. This would be even more of an accounting nightmare +#than deleteing the items. Just don't do it. +# +#=cut +# +#sub replace { +# return "Can't modify cust_bill_pkg records!"; +#} + =item check Checks all fields to make sure this is a valid line item. If there is an @@ -187,6 +333,8 @@ sub check { || $self->ut_numbern('sdate') || $self->ut_numbern('edate') || $self->ut_textn('itemdesc') + || $self->ut_textn('itemcomment') + || $self->ut_enum('hidden', [ '', 'Y' ]) ; return $error if $error; @@ -210,6 +358,7 @@ Returns the package (see L) for this invoice line item. sub cust_pkg { my $self = shift; + carp "$me $self -> cust_pkg" if $DEBUG; qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); } @@ -224,7 +373,10 @@ sub part_pkg { if ( $self->pkgpart_override ) { qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } ); } else { - $self->cust_pkg->part_pkg; + my $part_pkg; + my $cust_pkg = $self->cust_pkg; + $part_pkg = $cust_pkg->part_pkg if $cust_pkg; + $part_pkg; } } @@ -239,11 +391,30 @@ sub cust_bill { qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); } +=item previous_cust_bill_pkg + +Returns the previous cust_bill_pkg for this package, if any. + +=cut + +sub previous_cust_bill_pkg { + my $self = shift; + return unless $self->sdate; + qsearchs({ + 'table' => 'cust_bill_pkg', + 'hashref' => { 'pkgnum' => $self->pkgnum, + 'sdate' => { op=>'<', value=>$self->sdate }, + }, + 'order_by' => 'ORDER BY sdate DESC LIMIT 1', + }); +} + =item details [ OPTION => VALUE ... ] Returns an array of detail information for the invoice line item. -Currently available options are: I I +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. @@ -251,6 +422,12 @@ 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 { @@ -301,7 +478,7 @@ sub details { $format_sub = $opt{format_function} if $opt{format_function}; map { ( $_->format eq 'C' - ? &{$format_sub}( $_->detail ) + ? &{$format_sub}( $_->detail, $_ ) : &{$escape_function}( $_->detail ) ) } @@ -312,6 +489,35 @@ sub details { #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum }); } +=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'); + + eval "use Text::CSV_XS;"; + die $@ if $@; + 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 @@ -327,7 +533,9 @@ sub desc { if ( $self->pkgnum > 0 ) { $self->itemdesc || $self->part_pkg->pkg; } else { - $self->itemdesc || 'Tax'; + my $desc = $self->itemdesc || 'Tax'; + $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/; + $desc; } } @@ -370,6 +578,16 @@ sub owed { $balance; } +#modeled after owed +sub payable { + my( $self, $field ) = @_; + my $balance = $self->$field(); + $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) ); + $balance = sprintf( '%.2f', $balance ); + $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp + $balance; +} + sub cust_bill_pay_pkg { my( $self, $field ) = @_; qsearch( 'cust_bill_pay_pkg', { 'billpkgnum' => $self->billpkgnum, @@ -470,8 +688,9 @@ sub disintegrate { } #split usage from recur - my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage ); - warn "usage is $usage\n" if $DEBUG; + my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage ) + if exists($cust_bill_pkg{recur}); + warn "usage is $usage\n" if $DEBUG > 1; if ($usage) { my $cust_bill_pkg_usage = new FS::cust_bill_pkg { $cust_bill_pkg{recur}->hash }; @@ -496,7 +715,10 @@ sub disintegrate { $cust_bill_pkg{''}->recur( $classless ); $cust_bill_pkg{$class} = $cust_bill_pkg_usage; } - delete $cust_bill_pkg{''} unless $cust_bill_pkg{''}->recur; + warn "Unexpected classless usage value: ". $cust_bill_pkg{''}->recur + if ($cust_bill_pkg{''}->recur && $cust_bill_pkg{''}->recur < 0); + delete $cust_bill_pkg{''} + unless ($cust_bill_pkg{''}->recur && $cust_bill_pkg{''}->recur > 0); } # # sort setup,recur,'', and the rest numeric && return @@ -593,10 +815,10 @@ sub cust_bill_pkg_display { my $type = $opt{type} if exists $opt{type}; my @result; - if ( scalar( $self->get('display') ) ) { + if ( $self->get('display') ) { @result = grep { defined($type) ? ($type eq $_->type) : 1 } @{ $self->get('display') }; - }else{ + } else { my $hashref = { 'billpkgnum' => $self->billpkgnum }; $hashref->{type} = $type if defined($type); @@ -612,6 +834,78 @@ sub cust_bill_pkg_display { } +# 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 ) = @_; + + $self->{Hash}->{_cust_tax_exempt_pkg} or + $self->{Hash}->{_cust_tax_exempt_pkg} = []; + +} + +=item cust_bill_pkg_tax_Xlocation + +Returns the list of associated cust_bill_pkg_tax_location and/or +cust_bill_pkg_tax_rate_location objects + +=cut + +sub cust_bill_pkg_tax_Xlocation { + my $self = shift; + + my %hash = ( 'billpkgnum' => $self->billpkgnum ); + + ( + qsearch ( 'cust_bill_pkg_tax_location', { %hash } ), + qsearch ( 'cust_bill_pkg_tax_rate_location', { %hash } ) + ); + +} + +=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 + +sub recur_show_zero { + my $self = shift; + + $self->recur == 0 + && $self->pkgnum + && $self->cust_pkg->part_pkg->recur_show_zero; + +} =back