diff options
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS.pm | 2 | ||||
-rw-r--r-- | FS/FS/AccessRight.pm | 2 | ||||
-rw-r--r-- | FS/FS/Schema.pm | 16 | ||||
-rw-r--r-- | FS/FS/cust_main.pm | 171 | ||||
-rw-r--r-- | FS/FS/cust_pkg.pm | 72 | ||||
-rw-r--r-- | FS/FS/cust_pkg_detail.pm | 140 | ||||
-rw-r--r-- | FS/MANIFEST | 2 | ||||
-rw-r--r-- | FS/t/cust_pkg_detail.t | 5 |
8 files changed, 326 insertions, 84 deletions
@@ -210,6 +210,8 @@ L<FS::cust_pkg> - Customer package class L<FS::cust_pkg_option> - Customer package option class +L<FS::cust_pkg_detail> - Customer package details class + L<FS::reason_type> - Reason type class L<FS::reason> - Reason class diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index db2a31f4d..9ef35249c 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -121,6 +121,8 @@ tie my %rights, 'Tie::IxHash', 'Cancel customer package later', 'Add on-the-fly cancel reason', #NEW 'Add on-the-fly suspend reason', #NEW + 'Edit customer package invoice details', #NEW + 'Edit customer package comments', #NEW ], ### diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 94a56248b..ed535f7de 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -500,9 +500,6 @@ sub tables_hashref { 'quantity', 'int', 'NULL', '', '', '', 'unitsetup', @money_typen, '', '', 'unitrecur', @money_typen, '', '', - 'duplicate', 'char', 'NULL', 1, '', '', - 'post_total', 'char', 'NULL', 1, '', '', - 'type', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'billpkgnum', 'unique' => [], @@ -945,6 +942,19 @@ sub tables_hashref { 'index' => [ [ 'pkgnum' ], [ 'optionname' ] ], }, + 'cust_pkg_detail' => { + 'columns' => [ + 'pkgdetailnum', 'serial', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'detail', 'varchar', '', $char_d, '', '', + 'detailtype', 'char', '', 1, '', '', # "I"nvoice or "C"omment + 'weight', 'int', '', '', '', '', + ], + 'primary_key' => 'pkgdetailnum', + 'unique' => [], + 'index' => [ [ 'pkgnum', 'detailtype' ] ], + }, + 'cust_pkg_reason' => { 'columns' => [ 'num', 'serial', '', '', '', '', diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index e9e21b80b..3f974b607 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -64,7 +64,7 @@ $realtime_bop_decline_quiet = 0; # 1 is mostly method/subroutine entry and options # 2 traces progress of some operations # 3 is even more information including possibly sensitive data -$DEBUG = 0; +$DEBUG = 2; $me = '[FS::cust_main]'; $import = 0; @@ -2047,7 +2047,6 @@ Used in conjunction with the I<time> option, this option specifies the date of f sub bill { my( $self, %options ) = @_; return '' if $self->payby eq 'COMP'; - local $DEBUG = 1; warn "$me bill customer ". $self->custnum. "\n" if $DEBUG; @@ -2442,6 +2441,13 @@ sub _make_lines { warn " charges (setup=$setup, recur=$recur); adding line items\n" if $DEBUG > 1; + my @cust_pkg_detail = map { $_->detail } $cust_pkg->cust_pkg_detail('I'); + if ( $DEBUG > 1 ) { + warn " adding customer package invoice detail: $_\n" + foreach @cust_pkg_detail; + } + push @details, @cust_pkg_detail; + my $cust_bill_pkg = new FS::cust_bill_pkg { 'pkgnum' => $cust_pkg->pkgnum, 'setup' => $setup, @@ -2463,13 +2469,11 @@ sub _make_lines { # handle taxes ### - my $err_or_cust_bill_pkg = + my $error = $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg); + return $error if $error; - return $err_or_cust_bill_pkg - unless ( ref($err_or_cust_bill_pkg) ); - - push @$cust_bill_pkgs, @$err_or_cust_bill_pkg; + push @$cust_bill_pkgs, $cust_bill_pkg; } #if $setup != 0 || $recur != 0 @@ -2518,7 +2522,7 @@ sub _handle_taxes { $taxes{''} = $err_or_ref; } - }elsif ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) { + } elsif ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) { my %taxhash = map { $_ => $self->get("$prefix$_") } qw( state county country ); @@ -2556,83 +2560,88 @@ sub _handle_taxes { } #if $conf->exists('enable_taxproducts') ... my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!') - if $cust_pkg->part_pkg->option('separate_usage'); + if $cust_pkg->part_pkg->option('separate_usage', 'Hush!' ); my $want_duplicate = $cust_pkg->part_pkg->option('summarize_usage', 'Hush!') && $cust_pkg->part_pkg->option('usage_section', 'Hush!'); - # XXX this mostly goes away with cust_bill_pkg refactor - - $cust_bill_pkg{setup} = $cust_bill_pkg if $cust_bill_pkg->setup; - $cust_bill_pkg{recur} = $cust_bill_pkg if $cust_bill_pkg->recur; - - #split setup and recur - if ($cust_bill_pkg->setup && $cust_bill_pkg->recur) { - my $cust_bill_pkg_recur = new FS::cust_bill_pkg { $cust_bill_pkg->hash }; - $cust_bill_pkg->set('details', []); - $cust_bill_pkg->recur(0); - $cust_bill_pkg->unitrecur(0); - $cust_bill_pkg->type(''); - $cust_bill_pkg_recur->setup(0); - $cust_bill_pkg_recur->unitsetup(0); - $cust_bill_pkg{recur} = $cust_bill_pkg_recur; - } - - #split usage from recur - my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage ); - warn "usage is $usage\n" if $DEBUG; - if ($usage) { - my $cust_bill_pkg_usage = - new FS::cust_bill_pkg { $cust_bill_pkg{recur}->hash }; - $cust_bill_pkg_usage->recur( $usage ); - $cust_bill_pkg_usage->type( 'U' ); - $cust_bill_pkg_usage->duplicate( $want_duplicate ? 'Y' : '' ); - $cust_bill_pkg_usage->section( $section ); - $cust_bill_pkg_usage->post_total( $want_duplicate ? 'Y' : '' ); - my $recur = sprintf( "%.2f", $cust_bill_pkg{recur}->recur - $usage ); - $cust_bill_pkg{recur}->recur( $recur ); - $cust_bill_pkg{recur}->type( '' ); - $cust_bill_pkg{recur}->set('details', []); - $cust_bill_pkg{''} = $cust_bill_pkg_usage; - } - - #subdivide usage by usage_class - if (exists($cust_bill_pkg{''})) { - foreach my $class (grep {$_ && $_ ne 'setup' && $_ ne 'recur' } @classes) { - my $usage = sprintf( "%.2f", $cust_bill_pkg{''}->usage($class) ); - my $cust_bill_pkg_usage = - new FS::cust_bill_pkg { $cust_bill_pkg{''}->hash }; - $cust_bill_pkg_usage->recur( $usage ); - $cust_bill_pkg_usage->set('details', []); - my $classless = sprintf( "%.2f", $cust_bill_pkg{''}->recur - $usage ); - $cust_bill_pkg{''}->recur( $classless ); - $cust_bill_pkg{$class} = $cust_bill_pkg_usage; - } - delete $cust_bill_pkg{''} unless $cust_bill_pkg{''}->recur; - } - - foreach my $key (keys %cust_bill_pkg) { - my @taxes = @{ $taxes{$key} }; - my $cust_bill_pkg = $cust_bill_pkg{$key}; - - foreach my $tax ( @taxes ) { - my $taxname = ref( $tax ). ' '. $tax->taxnum; - if ( exists( $taxlisthash->{ $taxname } ) ) { - push @{ $taxlisthash->{ $taxname } }, $cust_bill_pkg; - }else{ - $taxlisthash->{ $taxname } = [ $tax, $cust_bill_pkg ]; - } - } - } - - # sort setup,recur,'', and the rest numeric && return - my @result = map { $cust_bill_pkg{$_} } - sort { my $ad = ($a=~/^\d+$/); my $bd = ($b=~/^\d+$/); - ( $ad cmp $bd ) || ( $ad ? $a<=>$b : $b cmp $a ) - } - keys %cust_bill_pkg; - - \@result; +#BUNK. DO NOT CREATE DUPLICATE cust_bill_pkg!!!!!!!!!!!! +# +# # XXX this mostly goes away with cust_bill_pkg refactor +# +# $cust_bill_pkg{setup} = $cust_bill_pkg if $cust_bill_pkg->setup; +# $cust_bill_pkg{recur} = $cust_bill_pkg if $cust_bill_pkg->recur; +# +# +# #split setup and recur +# if ($cust_bill_pkg->setup && $cust_bill_pkg->recur) { +# my $cust_bill_pkg_recur = new FS::cust_bill_pkg { $cust_bill_pkg->hash }; +# $cust_bill_pkg_recur->details($cust_bill_pkg-> +# $cust_bill_pkg_recur->setup(0); +# $cust_bill_pkg_recur->unitsetup(0); +# $cust_bill_pkg{recur} = $cust_bill_pkg_recur; +# +# $cust_bill_pkg->set('details', []); +# $cust_bill_pkg->recur(0); +# $cust_bill_pkg->unitrecur(0); +# $cust_bill_pkg->type(''); +# } +# +# #split usage from recur +# my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage ); +# warn "usage is $usage\n" if $DEBUG; +# if ($usage) { +# my $cust_bill_pkg_usage = +# new FS::cust_bill_pkg { $cust_bill_pkg{recur}->hash }; +# $cust_bill_pkg_usage->recur( $usage ); +# $cust_bill_pkg_usage->type( 'U' ); +# $cust_bill_pkg_usage->duplicate( $want_duplicate ? 'Y' : '' ); +# $cust_bill_pkg_usage->section( $section ); +# $cust_bill_pkg_usage->post_total( $want_duplicate ? 'Y' : '' ); +# my $recur = sprintf( "%.2f", $cust_bill_pkg{recur}->recur - $usage ); +# $cust_bill_pkg{recur}->recur( $recur ); +# $cust_bill_pkg{recur}->type( '' ); +# $cust_bill_pkg{recur}->set('details', []); +# $cust_bill_pkg{''} = $cust_bill_pkg_usage; +# } +# +# #subdivide usage by usage_class +# if (exists($cust_bill_pkg{''})) { +# foreach my $class (grep {$_ && $_ ne 'setup' && $_ ne 'recur' } @classes) { +# my $usage = sprintf( "%.2f", $cust_bill_pkg{''}->usage($class) ); +# my $cust_bill_pkg_usage = +# new FS::cust_bill_pkg { $cust_bill_pkg{''}->hash }; +# $cust_bill_pkg_usage->recur( $usage ); +# $cust_bill_pkg_usage->set('details', []); +# my $classless = sprintf( "%.2f", $cust_bill_pkg{''}->recur - $usage ); +# $cust_bill_pkg{''}->recur( $classless ); +# $cust_bill_pkg{$class} = $cust_bill_pkg_usage; +# } +# delete $cust_bill_pkg{''} unless $cust_bill_pkg{''}->recur; +# } +# +# foreach my $key (keys %cust_bill_pkg) { +# my @taxes = @{ $taxes{$key} }; +# my $cust_bill_pkg = $cust_bill_pkg{$key}; +# +# foreach my $tax ( @taxes ) { +# my $taxname = ref( $tax ). ' '. $tax->taxnum; +# if ( exists( $taxlisthash->{ $taxname } ) ) { +# push @{ $taxlisthash->{ $taxname } }, $cust_bill_pkg; +# }else{ +# $taxlisthash->{ $taxname } = [ $tax, $cust_bill_pkg ]; +# } +# } +# } +# +# # sort setup,recur,'', and the rest numeric && return +# my @result = map { $cust_bill_pkg{$_} } +# sort { my $ad = ($a=~/^\d+$/); my $bd = ($b=~/^\d+$/); +# ( $ad cmp $bd ) || ( $ad ? $a<=>$b : $b cmp $a ) +# } +# keys %cust_bill_pkg; +# +# \@result; } sub _gather_taxes { diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 4f3579eba..b0e27f28c 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -16,6 +16,7 @@ use FS::cust_main; use FS::type_pkgs; use FS::pkg_svc; use FS::cust_bill_pkg; +use FS::cust_pkg_detail; use FS::cust_event; use FS::h_cust_svc; use FS::reg_code; @@ -1089,6 +1090,77 @@ sub cust_bill_pkg { qsearch( 'cust_bill_pkg', { 'pkgnum' => $self->pkgnum } ); } +=item cust_pkg_detail [ DETAILTYPE ] + +Returns any customer package details for this package (see +L<FS::cust_pkg_detail>). + +DETAILTYPE can be set to "I" for invoice details or "C" for comments. + +=cut + +sub cust_pkg_detail { + my $self = shift; + my %hash = ( 'pkgnum' => $self->pkgnum ); + $hash{detailtype} = shift if @_; + qsearch({ + 'table' => 'cust_pkg_detail', + 'hashref' => \%hash, + 'order_by' => 'ORDER BY weight, pkgdetailnum', + }); +} + +=item set_cust_pkg_detail DETAILTYPE [ DETAIL, DETAIL, ... ] + +Sets customer package details for this package (see L<FS::cust_pkg_detail>). + +DETAILTYPE can be set to "I" for invoice details or "C" for comments. + +If there is an error, returns the error, otherwise returns false. + +=cut + +sub set_cust_pkg_detail { + my( $self, $detailtype, @details ) = @_; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + foreach my $current ( $self->cust_pkg_detail($detailtype) ) { + my $error = $current->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error removing old detail: $error"; + } + } + + foreach my $detail ( @details ) { + my $cust_pkg_detail = new FS::cust_pkg_detail { + 'pkgnum' => $self->pkgnum, + 'detailtype' => $detailtype, + 'detail' => $detail, + }; + my $error = $cust_pkg_detail->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error adding new detail: $error"; + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + =item cust_event Returns the new-style customer billing events (see L<FS::cust_event>) for this invoice. diff --git a/FS/FS/cust_pkg_detail.pm b/FS/FS/cust_pkg_detail.pm new file mode 100644 index 000000000..e2d8987bd --- /dev/null +++ b/FS/FS/cust_pkg_detail.pm @@ -0,0 +1,140 @@ +package FS::cust_pkg_detail; + +use strict; +use vars qw( @ISA ); +use FS::Record; # qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_pkg_detail - Object methods for cust_pkg_detail records + +=head1 SYNOPSIS + + use FS::cust_pkg_detail; + + $record = new FS::cust_pkg_detail \%hash; + $record = new FS::cust_pkg_detail { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_pkg_detail object represents additional customer package details. +FS::cust_pkg_detail inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item pkgdetailnum + +primary key + +=item pkgnum + +pkgnum (see L<FS::cust_pkg>) + +=item detail + +detail + +=item detailtype + +"I" for Invoice details or "C" for comments + +=item weight + +Optional display weight + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record 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 + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_pkg_detail'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=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 + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('pkgdetailnum') + || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum') + || $self->ut_text('detail') + || $self->ut_enum('detailtype', [ 'I', 'C' ] ) + || $self->ut_numbern('weight') + ; + return $error if $error; + + $self->weight(0) unless $self->weight; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_pkg>, L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index a6fe21911..b1f201d0a 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -422,3 +422,5 @@ FS/cust_svc_option.pm t/cust_svc_option.t FS/usage_class.pm t/usage_class.t +FS/cust_pkg_detail.pm +t/cust_pkg_detail.t diff --git a/FS/t/cust_pkg_detail.t b/FS/t/cust_pkg_detail.t new file mode 100644 index 000000000..15dec0014 --- /dev/null +++ b/FS/t/cust_pkg_detail.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_pkg_detail; +$loaded=1; +print "ok 1\n"; |