Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / quotation_pkg.pm
index 33c761e..4c78be7 100644 (file)
@@ -2,9 +2,10 @@ package FS::quotation_pkg;
 use base qw( FS::TemplateItem_Mixin FS::Record );
 
 use strict;
-use FS::Record qw( qsearchs ); #qsearch
+use FS::Record qw( qsearchs qsearch dbh );
 use FS::part_pkg;
 use FS::quotation_pkg_discount; #so its loaded when TemplateItem_Mixin needs it
+use List::Util qw(sum);
 
 =head1 NAME
 
@@ -39,19 +40,19 @@ primary key
 
 =item pkgpart
 
-pkgpart
+pkgpart (L<FS::part_pkg>) of the package
 
 =item locationnum
 
-locationnum
+locationnum (L<FS::cust_location>) where the package will be in service
 
 =item start_date
 
-start_date
+expected start date for the package, as a timestamp
 
 =item contract_end
 
-contract_end
+contract end date
 
 =item quantity
 
@@ -59,8 +60,20 @@ quantity
 
 =item waive_setup
 
-waive_setup
+'Y' to waive the setup fee
 
+=item unitsetup
+
+The amount per package that will be charged in setup/one-time fees.
+
+=item unitrecur
+
+The amount per package that will be charged per billing cycle.
+
+=item freq
+
+The length of the billing cycle. If zero it's a one-time charge; if any 
+other number it's that many months; other values are in L<FS::Misc::pkg_freqs>.
 
 =back
 
@@ -93,10 +106,64 @@ sub discount_table        { 'quotation_pkg_discount'; }
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
+=cut
+
+sub insert {
+  my ($self, %options) = @_;
+
+  my $dbh = dbh;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  my $error = $self->SUPER::insert;
+
+  if ( !$error and $self->discountnum ) {
+    warn "inserting discount #".$self->discountnum."\n";
+    $error = $self->insert_discount;
+    $error .= ' (setting discount)' if $error;
+  }
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  } else {
+    $dbh->commit if $oldAutoCommit;
+    return '';
+  }
+}
+
 =item delete
 
 Delete this record from the database.
 
+=cut
+
+sub delete {
+  my $self = shift;
+
+  my $dbh = dbh;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  foreach ($self->quotation_pkg_discount, $self->quotation_pkg_tax) {
+    my $error = $_->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error . ' (deleting discount)';
+    }
+  }
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  } else {
+    $dbh->commit if $oldAutoCommit;
+  }
+  
+  $self->quotation->estimate;
+}
+
 =item replace OLD_RECORD
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
@@ -113,6 +180,8 @@ and replace methods.
 sub check {
   my $self = shift;
 
+  my @freqs = ('', keys (%{ FS::Misc->pkg_freqs }));
+
   my $error = 
     $self->ut_numbern('quotationpkgnum')
     || $self->ut_foreign_key(  'quotationnum', 'quotation',    'quotationnum' )
@@ -121,8 +190,26 @@ sub check {
     || $self->ut_numbern('start_date')
     || $self->ut_numbern('contract_end')
     || $self->ut_numbern('quantity')
+    || $self->ut_moneyn('unitsetup')
+    || $self->ut_moneyn('unitrecur')
+    || $self->ut_enum('freq', \@freqs)
     || $self->ut_enum('waive_setup', [ '', 'Y'] )
   ;
+
+  if ($self->locationnum eq '') {
+    # use the customer default
+    my $quotation = $self->quotation;
+    if ($quotation->custnum) {
+      $self->set('locationnum', $quotation->cust_main->ship_locationnum);
+    } elsif ($quotation->prospectnum) {
+      # use the first non-disabled location for that prospect
+      my $cust_location = qsearchs('cust_location',
+        { prospectnum => $quotation->prospectnum,
+          disabled => '' });
+      $self->set('locationnum', $cust_location->locationnum) if $cust_location;
+    } # else the quotation is invalid
+  }
+
   return $error if $error;
 
   $self->SUPER::check;
@@ -140,48 +227,83 @@ sub desc {
   $self->part_pkg->pkg;
 }
 
-sub setup {
+=cut
+
+=item insert_discount
+
+Associates this package with a discount (see L<FS::cust_pkg_discount>,
+possibly inserting a new discount on the fly (see L<FS::discount>). Properties
+of the discount will be taken from this object.
+
+=cut
+
+sub insert_discount {
+  #my ($self, %options) = @_;
   my $self = shift;
-  return '0.00' if $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  #my $setup = $part_pkg->can('base_setup') ? $part_pkg->base_setup
-  #                                         : $part_pkg->option('setup_fee');
-  my $setup = $part_pkg->option('setup_fee');
-  #XXX discounts
-  $setup *= $self->quantity if $self->quantity;
-  sprintf('%.2f', $setup);
 
+  my $quotation_pkg_discount = FS::quotation_pkg_discount->new( {
+    'quotationpkgnum' => $self->quotationpkgnum,
+    'discountnum'     => $self->discountnum,
+    #for the create a new discount case
+    '_type'           => $self->discountnum__type,
+    'amount'      => $self->discountnum_amount,
+    'percent'     => $self->discountnum_percent,
+    'months'      => $self->discountnum_months,
+    'setup'       => $self->discountnum_setup,
+  } );
+
+  $quotation_pkg_discount->insert;
 }
 
-sub recur {
+sub _item_discount {
   my $self = shift;
-  return '0.00' if $self->{'_NO_RECUR_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  my $recur = $part_pkg->can('base_recur') ? $part_pkg->base_recur($self)
-                                           : $part_pkg->option('recur_fee');
-  #XXX discounts
-  $recur *= $self->quantity if $self->quantity;
-  sprintf('%.2f', $recur);
+  my %options = @_;
+  my $setuprecur = $options{'setuprecur'};
+
+  # kind of silly treating this as multiple records, but it works, and will
+  # work if we allow multiple discounts at some point
+  my @pkg_discounts = $self->pkg_discount;
+  return if @pkg_discounts == 0;
+  
+  my @ext;
+  my $d = {
+    _is_discount    => 1,
+    description     => $self->mt('Discount'),
+    amount          => 0,
+    ext_description => \@ext,
+    # maybe should show quantity/unit discount?
+  };
+  foreach my $pkg_discount (@pkg_discounts) {
+    push @ext, $pkg_discount->description;
+    my $amount = $pkg_discount->get($setuprecur.'_amount');
+    $d->{amount} -= $amount;
+  }
+  $d->{amount} = sprintf('%.2f', $d->{amount} * $self->quantity);
+  
+  return $d;
 }
 
-sub unitsetup {
+sub setup {
   my $self = shift;
-  return '0.00' if $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  my $setup = $part_pkg->option('setup_fee');
+  ($self->unitsetup - sum(0, map { $_->setup_amount } $self->pkg_discount))
+    * ($self->quantity || 1);
 
-  #XXX discounts
-  sprintf('%.2f', $setup);
 }
 
-sub unitrecur {
+sub setup_tax {
   my $self = shift;
-  return '0.00' if $self->{'_NO_RECUR_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  my $recur = $part_pkg->can('base_recur') ? $part_pkg->base_recur
-                                           : $part_pkg->option('recur_fee');
-  #XXX discounts
-  sprintf('%.2f', $recur);
+  sum(0, map { $_->setup_amount } $self->quotation_pkg_tax);
+}
+
+sub recur {
+  my $self = shift;
+  ($self->unitrecur - sum(0, map { $_->recur_amount } $self->pkg_discount))
+    * ($self->quantity || 1)
+}
+
+sub recur_tax {
+  my $self = shift;
+  sum(0, map { $_->recur_amount } $self->quotation_pkg_tax);
 }
 
 =item part_pkg_currency_option OPTIONNAME
@@ -253,7 +375,7 @@ Returns the customer (L<FS::cust_main> object).
 
 sub cust_main {
   my $self = shift;
-  my $quotation = FS::quotation->by_key($self->quotationnum) or return '';
+  my $quotation = $self->quotation or return '';
   $quotation->cust_main;
 }
 
@@ -265,14 +387,38 @@ Returns the prospect (L<FS::prospect_main> object).
 
 sub prospect_main {
   my $self = shift;
-  my $quotation = FS::quotation->by_key($self->quotationnum) or return '';
+  my $quotation = $self->quotation or return '';
   $quotation->prospect_main;
 }
 
+sub tax_locationnum {
+  my $self = shift;
+  $self->locationnum;
+}
+
+sub _upgrade_data {
+  my $class = shift;
+  my @quotation_pkg_without_location =
+    qsearch( 'quotation_pkg', { locationnum => '' } );
+  if (@quotation_pkg_without_location) {
+    warn "setting default location on quotation_pkg records\n";
+    foreach my $quotation_pkg (@quotation_pkg_without_location) {
+      # check() will fix this
+      my $error = $quotation_pkg->replace;
+      if ($error) {
+        die "quotation #".$quotation_pkg->quotationnum.": $error\n";
+      }
+    }
+  }
+  '';
+}
+
 =back
 
 =head1 BUGS
 
+Doesn't support fees, or add-on packages.
+
 =head1 SEE ALSO
 
 L<FS::Record>, schema.html from the base documentation.