show a total range for prorate quotations
[freeside.git] / FS / FS / quotation.pm
index 0e6b4e7..1a6641f 100644 (file)
@@ -65,6 +65,13 @@ disabled
 
 usernum
 
 
 usernum
 
+=item close_date
+
+projected date when the quotation will be closed
+
+=item confidence
+
+projected confidence (expressed as integer) that quotation will close
 
 =back
 
 
 =back
 
@@ -117,6 +124,8 @@ sub check {
     || $self->ut_numbern('_date')
     || $self->ut_enum('disabled', [ '', 'Y' ])
     || $self->ut_numbern('usernum')
     || $self->ut_numbern('_date')
     || $self->ut_enum('disabled', [ '', 'Y' ])
     || $self->ut_numbern('usernum')
+    || $self->ut_numbern('close_date')
+    || $self->ut_numbern('confidence')
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
@@ -124,6 +133,10 @@ sub check {
 
   $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
 
 
   $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
 
+  return 'confidence percentage must be an integer between 1 and 100'
+    if length($self->confidence)
+    && ( ($self->confidence < 1) || ($self->confidence > 100) );
+
   return 'prospectnum or custnum must be specified'
     if ! $self->prospectnum
     && ! $self->custnum;
   return 'prospectnum or custnum must be specified'
     if ! $self->prospectnum
     && ! $self->custnum;
@@ -301,6 +314,17 @@ sub _items_total {
   });
 
   my $total_setup = $self->total_setup;
   });
 
   my $total_setup = $self->total_setup;
+  my $total_recur = $self->total_recur;
+  my $setup_show = $total_setup > 0 ? 1 : 0;
+  my $recur_show = $total_recur > 0 ? 1 : 0;
+  unless ($setup_show && $recur_show) {
+    foreach my $quotation_pkg ($self->quotation_pkg) {
+      $setup_show = 1 if !$setup_show and $quotation_pkg->setup_show_zero;
+      $recur_show = 1 if !$recur_show and $quotation_pkg->recur_show_zero;
+      last if $setup_show && $recur_show;
+    }
+  }
+
   foreach my $pkg_tax (@setup_tax) {
     if ($pkg_tax->setup_amount > 0) {
       $total_setup += $pkg_tax->setup_amount;
   foreach my $pkg_tax (@setup_tax) {
     if ($pkg_tax->setup_amount > 0) {
       $total_setup += $pkg_tax->setup_amount;
@@ -311,9 +335,9 @@ sub _items_total {
     }
   }
 
     }
   }
 
-  if ( $total_setup > 0 ) {
+  if ( $setup_show ) {
     push @items, {
     push @items, {
-      'total_item'   => $self->mt( $self->total_recur > 0 ? 'Total Setup' : 'Total' ),
+      'total_item'   => $self->mt( $recur_show ? 'Total Setup' : 'Total' ),
       'total_amount' => sprintf('%.2f',$total_setup),
       'break_after'  => ( scalar(@recur_tax) ? 1 : 0 )
     };
       'total_amount' => sprintf('%.2f',$total_setup),
       'break_after'  => ( scalar(@recur_tax) ? 1 : 0 )
     };
@@ -321,7 +345,6 @@ sub _items_total {
 
   #could/should add up the different recurring frequencies on lines of their own
   # but this will cover the 95% cases for now
 
   #could/should add up the different recurring frequencies on lines of their own
   # but this will cover the 95% cases for now
-  my $total_recur = $self->total_recur;
   # label these with the frequency
   foreach my $pkg_tax (@recur_tax) {
     if ($pkg_tax->recur_amount > 0) {
   # label these with the frequency
   foreach my $pkg_tax (@recur_tax) {
     if ($pkg_tax->recur_amount > 0) {
@@ -336,33 +359,44 @@ sub _items_total {
     }
   }
 
     }
   }
 
-  if ( $total_recur > 0 ) {
+  if ( $recur_show ) {
     push @items, {
       'total_item'   => $self->mt('Total Recurring'),
       'total_amount' => sprintf('%.2f',$total_recur),
       'break_after'  => 1,
     };
     push @items, {
       'total_item'   => $self->mt('Total Recurring'),
       'total_amount' => sprintf('%.2f',$total_recur),
       'break_after'  => 1,
     };
-    # show 'first payment' line (setup + recur) if there are no prorated 
-    # packages included
-    my $disable_total = 0;
+
+    my $prorate_total = 0;
     foreach my $quotation_pkg ($self->quotation_pkg) {
       my $part_pkg = $quotation_pkg->part_pkg;
     foreach my $quotation_pkg ($self->quotation_pkg) {
       my $part_pkg = $quotation_pkg->part_pkg;
-      if (   $part_pkg->plan =~ /^prorate/
-          or $part_pkg->plan eq 'agent'
-          or $part_pkg->plan =~ /^torrus/
-          or $part_pkg->option('sync_bill_date')
-          or $part_pkg->option('recur_method') eq 'prorate' ) {
-        $disable_total = 1;
+      if (    $part_pkg->plan =~ /^(prorate|torrus|agent$)/
+           || $part_pkg->option('recur_method') eq 'prorate'
+           || ( $part_pkg->option('sync_bill_date')
+                  && $self->custnum
+                  && $self->cust_main->billing_pkgs #num_billing_pkgs when we have it
+              )
+      ) {
+        $prorate_total = 1;
         last;
       }
     }
         last;
       }
     }
-    if (!$disable_total) {
+
+    if ( $prorate_total ) {
+      push @items, {
+        'total_item'   => $self->mt('First payment (depending on day of month)'),
+        'total_amount' => [ sprintf('%.2f', $total_setup),
+                            sprintf('%.2f', $total_setup + $total_recur)
+                          ],
+        'break_after'  => 1,
+      };
+    } else {
       push @items, {
         'total_item'   => $self->mt('First payment'),
         'total_amount' => sprintf('%.2f', $total_setup + $total_recur),
         'break_after'  => 1,
       };
     }
       push @items, {
         'total_item'   => $self->mt('First payment'),
         'total_amount' => sprintf('%.2f', $total_setup + $total_recur),
         'break_after'  => 1,
       };
     }
+
   }
 
   return @items;
   }
 
   return @items;
@@ -375,7 +409,7 @@ sub _items_total {
 
 sub enable_previous { 0 }
 
 
 sub enable_previous { 0 }
 
-=item convert_cust_main
+=item convert_cust_main [ PARAMS ]
 
 If this quotation already belongs to a customer, then returns that customer, as
 an FS::cust_main object.
 
 If this quotation already belongs to a customer, then returns that customer, as
 an FS::cust_main object.
@@ -387,10 +421,13 @@ packages as real packages for the customer.
 If there is an error, returns an error message, otherwise, returns the
 newly-created FS::cust_main object.
 
 If there is an error, returns an error message, otherwise, returns the
 newly-created FS::cust_main object.
 
+Accepts the same params as L</order>.
+
 =cut
 
 sub convert_cust_main {
   my $self = shift;
 =cut
 
 sub convert_cust_main {
   my $self = shift;
+  my $params = shift || {};
 
   my $cust_main = $self->cust_main;
   return $cust_main if $cust_main; #already converted, don't again
 
   my $cust_main = $self->cust_main;
   return $cust_main if $cust_main; #already converted, don't again
@@ -407,7 +444,7 @@ sub convert_cust_main {
 
   $self->prospectnum('');
   $self->custnum( $cust_main->custnum );
 
   $self->prospectnum('');
   $self->custnum( $cust_main->custnum );
-  my $error = $self->replace || $self->order;
+  my $error = $self->replace || $self->order(undef,$params);
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
@@ -419,7 +456,7 @@ sub convert_cust_main {
 
 }
 
 
 }
 
-=item order
+=item order [ HASHREF ] [ PARAMS ]
 
 This method is for use with quotations which are already associated with a customer.
 
 
 This method is for use with quotations which are already associated with a customer.
 
@@ -427,14 +464,32 @@ Orders this quotation's packages as real packages for the customer.
 
 If there is an error, returns an error message, otherwise returns false.
 
 
 If there is an error, returns an error message, otherwise returns false.
 
+If HASHREF is passed, it will be filled with a hash mapping the 
+C<quotationpkgnum> of each quoted package to the C<pkgnum> of the package
+as ordered.
+
+If PARAMS hashref is passed, the following params are accepted:
+
+onhold - if true, suspends newly ordered packages
+
 =cut
 
 sub order {
   my $self = shift;
 =cut
 
 sub order {
   my $self = shift;
+  my $pkgnum_map = shift || {};
+  my $params = shift || {};
+  my $details_map = {};
 
   tie my %all_cust_pkg, 'Tie::RefHash';
   foreach my $quotation_pkg ($self->quotation_pkg) {
     my $cust_pkg = FS::cust_pkg->new;
 
   tie my %all_cust_pkg, 'Tie::RefHash';
   foreach my $quotation_pkg ($self->quotation_pkg) {
     my $cust_pkg = FS::cust_pkg->new;
+    $pkgnum_map->{ $quotation_pkg->quotationpkgnum } = $cust_pkg;
+
+    # details will be copied below, after package is ordered
+    $details_map->{ $quotation_pkg->quotationpkgnum } = [ 
+      map { $_->copy_on_order ? $_->detail : () } $quotation_pkg->quotation_pkg_detail
+    ];
+
     foreach (qw(pkgpart locationnum start_date contract_end quantity waive_setup)) {
       $cust_pkg->set( $_, $quotation_pkg->get($_) );
     }
     foreach (qw(pkgpart locationnum start_date contract_end quantity waive_setup)) {
       $cust_pkg->set( $_, $quotation_pkg->get($_) );
     }
@@ -448,7 +503,52 @@ sub order {
     $all_cust_pkg{$cust_pkg} = []; # no services
   }
 
     $all_cust_pkg{$cust_pkg} = []; # no services
   }
 
-  $self->cust_main->order_pkgs( \%all_cust_pkg );
+  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->cust_main->order_pkgs( \%all_cust_pkg );
+  
+  unless ($error) {
+    # copy details (copy_on_order filtering handled above)
+    foreach my $quotationpkgnum (keys %$details_map) {
+      next unless @{$details_map->{$quotationpkgnum}};
+      $error = $pkgnum_map->{$quotationpkgnum}->set_cust_pkg_detail(
+        'I',
+        @{$details_map->{$quotationpkgnum}}
+      );
+      last if $error;
+    }
+  }
+
+  if ($$params{'onhold'}) {
+    foreach my $quotationpkgnum (keys %$pkgnum_map) {
+      last if $error;
+      $error = $pkgnum_map->{$quotationpkgnum}->suspend();
+    }
+  }
+
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  foreach my $quotationpkgnum (keys %$pkgnum_map) {
+    # convert the objects to just pkgnums
+    my $cust_pkg = $pkgnum_map->{$quotationpkgnum};
+    $pkgnum_map->{$quotationpkgnum} = $cust_pkg->pkgnum;
+  }
+
+  ''; #no error
 
 }
 
 
 }