X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fquotation.pm;h=1a6641f7dc3bfeddad2fccc618abc14e58582079;hb=71ec18a5da6072ae52bcbcca22b4ef7a5290f7ba;hp=0e6b4e7d09f44a95a84bf7a1af9e80491b65b849;hpb=02b3110eb22945351c1b840a6686b9ad6541be0b;p=freeside.git diff --git a/FS/FS/quotation.pm b/FS/FS/quotation.pm index 0e6b4e7d0..1a6641f7d 100644 --- a/FS/FS/quotation.pm +++ b/FS/FS/quotation.pm @@ -65,6 +65,13 @@ disabled usernum +=item close_date + +projected date when the quotation will be closed + +=item confidence + +projected confidence (expressed as integer) that quotation will close =back @@ -117,6 +124,8 @@ sub check { || $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; @@ -124,6 +133,10 @@ sub check { $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; @@ -301,6 +314,17 @@ sub _items_total { }); 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; @@ -311,9 +335,9 @@ sub _items_total { } } - if ( $total_setup > 0 ) { + if ( $setup_show ) { 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 ) }; @@ -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 - my $total_recur = $self->total_recur; # 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, }; - # 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; - 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; } } - 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, }; } + } return @items; @@ -375,7 +409,7 @@ sub _items_total { 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. @@ -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. +Accepts the same params as L. + =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 @@ -407,7 +444,7 @@ sub convert_cust_main { $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; @@ -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. @@ -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 HASHREF is passed, it will be filled with a hash mapping the +C of each quoted package to the C 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; + 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; + $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($_) ); } @@ -448,7 +503,52 @@ sub order { $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 }