add cust_bill_pay_pkg and cust_credit_bill_pkg - applying credits and payments agains...
authorivan <ivan>
Mon, 21 Aug 2006 23:01:43 +0000 (23:01 +0000)
committerivan <ivan>
Mon, 21 Aug 2006 23:01:43 +0000 (23:01 +0000)
12 files changed:
FS/FS/Schema.pm
FS/FS/cust_bill.pm
FS/FS/cust_bill_ApplicationCommon.pm [new file with mode: 0644]
FS/FS/cust_bill_pay.pm
FS/FS/cust_bill_pay_pkg.pm [new file with mode: 0644]
FS/FS/cust_bill_pkg.pm
FS/FS/cust_credit_bill.pm
FS/FS/cust_credit_bill_pkg.pm [new file with mode: 0644]
FS/MANIFEST
FS/t/cust_bill_ApplicationCommon.t [new file with mode: 0644]
FS/t/cust_bill_pay_pkg.t [new file with mode: 0644]
FS/t/cust_credit_bill_pkg.t [new file with mode: 0644]

index ce2d790..b3d8a56 100644 (file)
@@ -390,6 +390,19 @@ sub tables_hashref {
       'index' => [ ['crednum'], ['invnum'] ],
     },
 
+    'cust_credit_bill_pkg' => {
+      'columns' => [
+        'creditbillpkgnum', 'serial', '',      '', '', '',
+        'creditbillnum',       'int', '',      '', '', '',
+        'billpkgnum',          'int', '',      '', '', '',
+        'amount',            @money_type,          '', '',
+        'setuprecur',      'varchar', '', $char_d, '', '',
+      ],
+      'primary_key' => 'creditbillpkgnum',
+      'unique'      => [],
+      'index'       => [ [ 'creditbillnum' ], [ 'billpkgnum' ], ],
+    },
+
     'cust_main' => {
       'columns' => [
         'custnum',  'serial',  '',     '', '', '', 
@@ -540,6 +553,19 @@ sub tables_hashref {
       'index' => [ [ 'paynum' ], [ 'invnum' ] ],
     },
 
+    'cust_bill_pay_pkg' => {
+      'columns' => [
+        'billpaypkgnum', 'serial', '', '', '', '',
+        'billpaynum',       'int', '', '', '', '',
+        'billpkgnum',       'int', '', '', '', '',
+        'amount',         @money_type,     '', '',
+        'setuprecur',      'varchar', '', $char_d, '', '',
+      ],
+      'primary_key' => 'billpaypkgnum',
+      'unique'      => [],
+      'index'       => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
+    },
+
     'pay_batch' => { #batches of payments to an external processor
       'columns' => [
         'batchnum',   'serial',    '',   '', '', '', 
index d45b66f..a93d175 100644 (file)
@@ -225,6 +225,33 @@ sub cust_bill_pkg {
   qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
 }
 
+=item open_cust_bill_pkg
+
+Returns the open line items for this invoice.
+
+Note that cust_bill_pkg with both setup and recur fees are returned as two
+separate line items, each with only one fee.
+
+=cut
+
+# modeled after cust_main::open_cust_bill
+sub open_cust_bill_pkg {
+  my $self = shift;
+
+  # grep { $_->owed > 0 } $self->cust_bill_pkg
+
+  my %other = ( 'recur' => 'setup',
+                'setup' => 'recur', );
+  my @open = ();
+  foreach my $field ( qw( recur setup )) {
+    push @open, map  { $_->set( $other{$field}, 0 ); $_; }
+                grep { $_->owed($field) > 0 }
+                $self->cust_bill_pkg;
+  }
+
+  @open;
+}
+
 =item cust_bill_event
 
 Returns the completed invoice events (see L<FS::cust_bill_event>) for this
diff --git a/FS/FS/cust_bill_ApplicationCommon.pm b/FS/FS/cust_bill_ApplicationCommon.pm
new file mode 100644 (file)
index 0000000..fb06a4b
--- /dev/null
@@ -0,0 +1,243 @@
+package FS::cust_bill_ApplicationCommon;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs dbh );
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 1;
+
+=head1 NAME
+
+FS::cust_bill_ApplicationCommon - Base class for bill application classes
+
+=head1 SYNOPSIS
+
+use FS::cust_bill_ApplicationCommon;
+
+@ISA = qw( FS::cust_bill_ApplicationCommon );
+
+sub _app_source_name  { 'payment'; }
+sub _app_source_table { 'cust_pay'; }
+sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; }
+
+=head1 DESCRIPTION
+
+FS::cust_bill_ApplicationCommon is intended as a base class for classes which
+represent application of things to invoices, currently payments
+(see L<FS::cust_bill_pay>) or credits (see L<FS::cust_credit_bill>).
+
+=head1 METHODS
+
+=item insert
+
+=cut
+
+sub insert {
+  my $self = shift;
+
+  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;
+
+  my $error =    $self->SUPER::insert(@_)
+              || $self->apply_to_lineitems;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item delete
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  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 $app ( $self->lineitem_applications ) {
+    my $error = $app->delete;
+    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;
+
+  '';
+
+}
+
+=item apply_to_lineitems
+
+Auto-applies this invoice application to specific line items, if possible.
+
+=cut
+
+sub apply_to_lineitems {
+  my $self = shift;
+
+  my @apply = ();
+
+  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;
+
+  my @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...?
+  warn scalar(@open). " open line items for invoice ".
+       $self->cust_bill->invnum. "\n"
+    if $DEBUG;
+  my $total = 0;
+  $total += $_->setup + $_->recur foreach @open;
+  $total = sprintf('%.2f', $total);
+
+  if ( $self->amount > $total ) {
+    dbh->rollback if $oldAutoCommit;
+    return "Can't apply a ". $self->_app_source_name. ' of $'. $self->amount.
+           " greater than the remaining owed on line items (\$$total)";
+  }
+
+  #easy cases:
+  # - one lineitem (a simple special case of:)
+  # - amount is for whole invoice (well, all of remaining lineitem links)
+  if ( $self->amount == $total ) {
+
+    #@apply = map { [ $_, $_->amount ]; } @open;
+    @apply = map { [ $_, $_->setup || $_->recur ]; } @open;
+
+  } else {
+
+    #slightly magic case:
+    # - amount exactly and uniquely matches a single open lineitem
+    #   (you must be trying to pay or credit that item, then)
+
+    my @same = grep {    $_->setup == $self->amount
+                      || $_->recur == $self->amount
+                    }
+                    @open;
+    @apply = map { [ $_, $self->amount ]; } @same
+      if scalar(@same) == 1;
+
+  }
+
+  #and the rest:
+  # - leave unapplied, for now
+  # - eventually, auto-apply?  sequentially?  pro-rated against total remaining?
+
+  # do the applicaiton(s)
+  my $table = $self->lineitem_breakdown_table;
+  my $source_key = dbdef->table($self->table)->primary_key;
+  foreach my $apply ( @apply ) {
+    my ( $cust_bill_pkg, $amount ) = @$apply;
+    my $application = "FS::$table"->new( {
+      $source_key  => $self->$source_key(),
+      'billpkgnum' => $cust_bill_pkg->billpkgnum,
+      'amount'     => $amount,
+      'setuprecur' => ( $cust_bill_pkg->setup > 0 ? 'setup' : 'recur' ),
+    });
+    my $error = $application->insert;
+    if ( $error ) {
+      dbh->rollbck if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  '';
+
+}
+
+=item lineitem_applications
+
+Returns all the specific line item applications for this invoice application.
+
+=cut
+
+sub lineitem_applications {
+  my $self = shift;
+  my $primary_key = dbdef->table($self->table)->primary_key;
+  qsearchs({
+    'table'   => $self->lineitem_breakdown_table, 
+    'hashref' => { $primary_key => $self->$primary_key() },
+  });
+
+}
+
+=item cust_bill 
+
+Returns the invoice (see L<FS::cust_bill>)
+
+=cut
+
+sub cust_bill {
+  my $self = shift;
+  qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=item lineitem_breakdown_table 
+
+=cut
+
+sub lineitem_breakdown_table {
+  my $self = shift;
+  $self->_load_table($self->_app_lineitem_breakdown_table);
+}
+
+sub _load_table {
+  my( $self, $table ) = @_;
+  eval "use FS::$table";
+  die $@ if $@;
+  $table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_bill_pay> and L<FS::cust_bill_pay_pkg>,
+L<FS::cust_credit_bill> and L<FS::cust_credit_bill_pkg>
+
+=cut
+
+1;
+
index 7abbe9a..67f8eaf 100644 (file)
@@ -2,11 +2,12 @@ package FS::cust_bill_pay;
 
 use strict;
 use vars qw( @ISA $conf );
-use FS::Record qw( qsearch qsearchs dbh );
+use FS::Record qw( qsearchs );
+use FS::cust_bill_ApplicationCommon;
 use FS::cust_bill;
 use FS::cust_pay;
 
-@ISA = qw( FS::Record );
+@ISA = qw( FS::cust_bill_ApplicationCommon );
 
 #ask FS::UID to run this stuff for us later
 FS::UID->install_callback( sub { 
@@ -35,8 +36,9 @@ FS::cust_bill_pay - Object methods for cust_bill_pay records
 =head1 DESCRIPTION
 
 An FS::cust_bill_pay object represents the application of a payment to a
-specific invoice.  FS::cust_bill_pay inherits from FS::Record.  The following
-fields are currently supported:
+specific invoice.  FS::cust_bill_pay inherits from
+FS::cust_bill_ApplicationCommon and FS::Record.  The following fields are
+currently supported:
 
 =over 4
 
@@ -65,6 +67,10 @@ Creates a new record.  To add the record to the database, see L<"insert">.
 
 sub table { 'cust_bill_pay'; }
 
+sub _app_source_name   { 'payment'; }
+sub _app_source_table { 'cust_pay'; }
+sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; }
+
 =item insert
 
 Adds this record to the database.  If there is an error, returns the error,
@@ -81,6 +87,8 @@ sub delete {
   my $self = shift;
   return "Can't delete application for closed payment"
     if $self->cust_pay->closed =~ /^Y/i;
+  return "Can't delete application for closed invoice"
+    if $self->cust_bill->closed =~ /^Y/i;
   $self->SUPER::delete(@_);
 }
 
@@ -91,13 +99,14 @@ Currently unimplemented (accounting reasons).
 =cut
 
 sub replace {
-   return "Can't (yet?) modify cust_bill_pay records!";
+   return "Can't modify application of payment!";
 }
 
 =item check
 
-Checks all fields to make sure this is a valid payment.  If there is an error,
-returns the error, otherwise returns false.  Called by the insert method.
+Checks all fields to make sure this is a valid payment application.  If there
+is an error, returns the error, otherwise returns false.  Called by the insert
+method.
 
 =cut
 
@@ -106,30 +115,22 @@ sub check {
 
   my $error = 
     $self->ut_numbern('billpaynum')
-    || $self->ut_number('invnum')
-    || $self->ut_number('paynum')
-    || $self->ut_money('amount')
+    || $self->ut_foreign_key('paynum', 'cust_pay', 'paynum' )
+    || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' )
     || $self->ut_numbern('_date')
+    || $self->ut_money('amount')
   ;
   return $error if $error;
 
   return "amount must be > 0" if $self->amount <= 0;
   
-  return "Unknown invoice"
-    unless my $cust_bill =
-      qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-
-  return "Unknown payment"
-    unless my $cust_pay = 
-      qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
-
   $self->_date(time) unless $self->_date;
 
   return "Cannot apply more than remaining value of invoice"
-    unless $self->amount <= $cust_bill->owed;
+    unless $self->amount <= $self->cust_bill->owed;
 
   return "Cannot apply more than remaining value of payment"
-    unless $self->amount <= $cust_pay->unapplied;
+    unless $self->amount <= $self->cust_pay->unapplied;
 
   $self->SUPER::check;
 }
@@ -145,26 +146,12 @@ sub cust_pay {
   qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
 }
 
-=item cust_bill 
-
-Returns the invoice (see L<FS::cust_bill>)
-
-=cut
-
-sub cust_bill {
-  my $self = shift;
-  qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-}
-
 =back
 
 =head1 BUGS
 
 Delete and replace methods.
 
-the checks for over-applied payments could be better done like the ones in
-cust_bill_credit
-
 =head1 SEE ALSO
 
 L<FS::cust_pay>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
diff --git a/FS/FS/cust_bill_pay_pkg.pm b/FS/FS/cust_bill_pay_pkg.pm
new file mode 100644 (file)
index 0000000..af331cd
--- /dev/null
@@ -0,0 +1,132 @@
+package FS::cust_bill_pay_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records
+
+=head1 SYNOPSIS
+
+  use FS::cust_bill_pay_pkg;
+
+  $record = new FS::cust_bill_pay_pkg \%hash;
+  $record = new FS::cust_bill_pay_pkg { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pay_pkg object represents application of a payment (see
+L<FS::cust_bill_pay>) to a specific line item within an invoice (see
+L<FS::cust_bill_pkg>).  FS::cust_bill_pay_pkg inherits from FS::Record.  The
+following fields are currently supported:
+
+=over 4
+
+=item billpaypkgnum - primary key
+
+=item billpaynum - Payment application to the overall invoice (see L<FS::cust_bill_pay>)
+
+=item billpkgnum -  Line item to which payment is applied (see L<FS::cust_bill_pkg>)
+
+=item amount - Amount of the payment applied to this line item.
+
+=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
+
+=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_bill_pay_pkg'; }
+
+=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 payment application.  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('billpaypkgnum')
+    || $self->ut_foreign_key('billpaynum', 'cust_bill_pay', 'billpaynum' )
+    || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+    || $self->ut_money('amount')
+    || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
+setup and recur fields.  It should be removed once that's fixed.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index d718b05..e41a3c5 100644 (file)
@@ -7,6 +7,8 @@ use FS::cust_main_Mixin;
 use FS::cust_pkg;
 use FS::cust_bill;
 use FS::cust_bill_pkg_detail;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
 
 @ISA = qw( FS::cust_main_Mixin FS::Record );
 
@@ -224,18 +226,78 @@ sub desc {
   }
 }
 
-=back
+=item owed_setup
 
-=head1 CLASS METHODS
+Returns the amount owed (still outstanding) on this line item's setup fee,
+which is the amount of the line item minus all payment applications (see
+L<FS::cust_bill_pay_pkg> and credit applications (see
+L<FS::cust_credit_bill_pkg>).
 
-=over 4
+=cut
+
+sub owed_setup {
+  my $self = shift;
+  $self->owed('setup', @_);
+}
 
-=item  
+=item owed_recur
+
+Returns the amount owed (still outstanding) on this line item's recurring fee,
+which is the amount of the line item minus all payment applications (see
+L<FS::cust_bill_pay_pkg> and credit applications (see
+L<FS::cust_credit_bill_pkg>).
+
+=cut
+
+sub owed_recur {
+  my $self = shift;
+  $self->owed('recur', @_);
+}
+
+# modeled after cust_bill::owed...
+sub owed {
+  my( $self, $field ) = @_;
+  my $balance = $self->$field();
+  $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg($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,
+                                  'setuprecur' => $field,
+                                }
+         );
+}
+
+sub cust_credit_bill_pkg {
+  my( $self, $field ) = @_;
+  qsearch( 'cust_credit_bill_pkg', { 'billpkgnum' => $self->billpkgnum,
+                                     'setuprecur' => $field,
+                                   }
+         );
+}
 
 =back
 
 =head1 BUGS
 
+setup and recur shouldn't be separate fields.  There should be one "amount"
+field and a flag to tell you if it is a setup/one-time fee or a recurring fee.
+
+A line item with both should really be two separate records (preserving
+sdate and edate for setup fees for recurring packages - that information may
+be valuable later).  Invoice generation (cust_main::bill), invoice printing
+(cust_bill), tax reports (report_tax.cgi) and line item reports 
+(cust_bill_pkg.cgi) would need to be updated.
+
+owed_setup and owed_recur could then be repaced by just owed, and
+cust_bill::open_cust_bill_pkg and
+cust_bill_ApplicationCommon::apply_to_lineitems could be simplified.
+
 =head1 SEE ALSO
 
 L<FS::Record>, L<FS::cust_bill>, L<FS::cust_pkg>, L<FS::cust_main>, schema.html
index 695df6e..c015ec1 100644 (file)
@@ -4,12 +4,11 @@ use strict;
 use vars qw( @ISA $conf );
 use FS::UID qw( getotaker );
 use FS::Record qw( qsearch qsearchs );
-use FS::cust_main;
-#use FS::cust_refund;
-use FS::cust_credit;
+use FS::cust_bill_ApplicationCommon;
 use FS::cust_bill;
+use FS::cust_credit;
 
-@ISA = qw( FS::Record );
+@ISA = qw( FS::cust_bill_ApplicationCommon );
 
 #ask FS::UID to run this stuff for us later
 FS::UID->install_callback( sub { 
@@ -39,7 +38,8 @@ FS::cust_credit_bill - Object methods for cust_credit_bill records
 
 An FS::cust_credit_bill object represents application of a credit (see
 L<FS::cust_credit>) to an invoice (see L<FS::cust_bill>).  FS::cust_credit_bill
-inherits from FS::Record.  The following fields are currently supported:
+inherits from FS::cust_bill_ApplicationCommon and FS::Record.  The following
+fields are currently supported:
 
 =over 4
 
@@ -69,6 +69,10 @@ see L<"insert">.
 
 sub table { 'cust_credit_bill'; }
 
+sub _app_source_name  { 'credit'; }
+sub _app_source_table { 'cust_credit'; }
+sub _app_lineitem_breakdown_table { 'cust_credit_bill_pkg'; }
+
 =item insert
 
 Adds this cust_credit_bill to the database ("Posts" all or part of a credit).
@@ -84,6 +88,8 @@ sub delete {
   my $self = shift;
   return "Can't delete application for closed credit"
     if $self->cust_credit->closed =~ /^Y/i;
+  return "Can't delete application for closed invoice"
+    if $self->cust_bill->closed =~ /^Y/i;
   $self->SUPER::delete(@_);
 }
 
@@ -110,8 +116,8 @@ sub check {
 
   my $error =
     $self->ut_numbern('creditbillnum')
-    || $self->ut_number('crednum')
-    || $self->ut_number('invnum')
+    || $self->ut_foreign_key('crednum', 'cust_credit', 'crednum')
+    || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' )
     || $self->ut_numbern('_date')
     || $self->ut_money('amount')
   ;
@@ -119,21 +125,13 @@ sub check {
 
   return "amount must be > 0" if $self->amount <= 0;
 
-  return "Unknown credit"
-    unless my $cust_credit = 
-      qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
-
-  return "Unknown invoice"
-    unless my $cust_bill =
-      qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-
   $self->_date(time) unless $self->_date;
 
   return "Cannot apply more than remaining value of credit"
-    unless $self->amount <= $cust_credit->credited;
+    unless $self->amount <= $self->cust_credit->credited;
 
   return "Cannot apply more than remaining value of invoice"
-    unless $self->amount <= $cust_bill->owed;
+    unless $self->amount <= $self->cust_bill->owed;
 
   $self->SUPER::check;
 }
@@ -149,26 +147,17 @@ sub cust_credit {
   qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
 }
 
-=item cust_bill 
-
-Returns the invoice (see L<FS::cust_bill>)
-
-=cut
-
-sub cust_bill {
-  my $self = shift;
-  qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-}
-
 =back
 
 =head1 BUGS
 
 The delete method.
 
+This probably should have been called cust_bill_credit.
+
 =head1 SEE ALSO
 
-L<FS::Record>, L<FS::cust_refund>, L<FS::cust_bill>, L<FS::cust_credit>,
+L<FS::Record>, L<FS::cust_bill>, L<FS::cust_credit>,
 schema.html from the base documentation.
 
 =cut
diff --git a/FS/FS/cust_credit_bill_pkg.pm b/FS/FS/cust_credit_bill_pkg.pm
new file mode 100644 (file)
index 0000000..98521d6
--- /dev/null
@@ -0,0 +1,132 @@
+package FS::cust_credit_bill_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
+
+=head1 SYNOPSIS
+
+  use FS::cust_credit_bill_pkg;
+
+  $record = new FS::cust_credit_bill_pkg \%hash;
+  $record = new FS::cust_credit_bill_pkg { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_credit_bill_pkg object represents application of a credit (see 
+L<FS::cust_credit_bill>) to a specific line item within an invoice
+(see L<FS::cust_bill_pkg>).  FS::cust_credit_bill_pkg inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item creditbillpkg -  primary key
+
+=item creditbillnum - Credit application to the overall invoice (see L<FS::cust_credit::bill>)
+
+=item billpkgnum - Line item to which credit is applied (see L<FS::cust_bill_pkg>)
+
+=item amount - Amount of the credit applied to this line item.
+
+=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
+
+=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
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_credit_bill_pkg'; }
+
+=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 credit applicaiton.  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('creditbillpkgnum')
+    || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
+    || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+    || $self->ut_money('amount')
+    || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
+setup and recur fields.  It should be removed once that's fixed.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index 42b6165..10f9ffa 100644 (file)
@@ -350,3 +350,7 @@ FS/CurrentUser.pm
 FS/svc_phone.pm
 t/svc_phone.t
 FS/h_svc_phone.pm
+FS/cust_bill_pay_pkg.pm
+t/cust_bill_pay_pkg.t
+FS/cust_credit_bill_pkg.pm
+t/cust_credit_bill_pkg.t
diff --git a/FS/t/cust_bill_ApplicationCommon.t b/FS/t/cust_bill_ApplicationCommon.t
new file mode 100644 (file)
index 0000000..fa03d34
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_ApplicationCommon;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pay_pkg.t b/FS/t/cust_bill_pay_pkg.t
new file mode 100644 (file)
index 0000000..b8fcddb
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pay_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_credit_bill_pkg.t b/FS/t/cust_credit_bill_pkg.t
new file mode 100644 (file)
index 0000000..4eb84c3
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_credit_bill_pkg;
+$loaded=1;
+print "ok 1\n";