2 use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::otaker_Mixin FS::Record
9 use FS::Maketext qw( emt );
15 FS::quotation - Object methods for quotation records
21 $record = new FS::quotation \%hash;
22 $record = new FS::quotation { 'column' => 'value' };
24 $error = $record->insert;
26 $error = $new_record->replace($old_record);
28 $error = $record->delete;
30 $error = $record->check;
34 An FS::quotation object represents a quotation. FS::quotation inherits from
35 FS::Record. The following fields are currently supported:
72 Creates a new quotation. To add the quotation to the database, see L<"insert">.
74 Note that this stores the hash reference, not a distinct copy of the hash it
75 points to. You can ask the object for a copy with the I<hash> method.
79 sub table { 'quotation'; }
80 sub notice_name { 'Quotation'; }
81 sub template_conf { 'quotation_'; }
85 Adds this record to the database. If there is an error, returns the error,
86 otherwise returns false.
90 Delete this record from the database.
92 =item replace OLD_RECORD
94 Replaces the OLD_RECORD with this one in the database. If there is an error,
95 returns the error, otherwise returns false.
99 Checks all fields to make sure this is a valid quotation. If there is
100 an error, returns the error, otherwise returns false. Called by the insert
109 $self->ut_numbern('quotationnum')
110 || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum' )
111 || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum' )
112 || $self->ut_numbern('_date')
113 || $self->ut_enum('disabled', [ '', 'Y' ])
114 || $self->ut_numbern('usernum')
116 return $error if $error;
118 $self->_date(time) unless $self->_date;
120 $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
122 return 'prospectnum or custnum must be specified'
123 if ! $self->prospectnum
137 sub cust_bill_pkg { #actually quotation_pkg objects
138 shift->quotation_pkg(@_);
147 $self->_total('setup');
150 =item total_recur [ FREQ ]
156 #=item total_recur [ FREQ ]
157 #my $freq = @_ ? shift : '';
158 $self->_total('recur');
162 my( $self, $method ) = @_;
165 $total += $_->$method() for $self->cust_bill_pkg;
166 sprintf('%.2f', $total);
170 =item cust_or_prospect_label_link P
172 HTML links to either the customer or prospect.
174 Returns a list consisting of two elements. The first is a text label for the
175 link, and the second is the URL.
179 sub cust_or_prospect_label_link {
180 my( $self, $p ) = @_;
182 if ( my $custnum = $self->custnum ) {
183 my $display_custnum = $self->cust_main->display_custnum;
184 my $target = $FS::CurrentUser::CurrentUser->default_customer_view eq 'jumbo'
186 : ';show=quotations';
188 emt("View this customer (#[_1])",$display_custnum) =>
189 "${p}view/cust_main.cgi?custnum=$custnum$target"
191 } elsif ( my $prospectnum = $self->prospectnum ) {
193 emt("View this prospect (#[_1])",$prospectnum) =>
194 "${p}view/prospect_main.html?$prospectnum"
202 #prevent things from falsely showing up as taxes, at least until we support
203 # quoting tax amounts..
208 shift->cust_bill_pkg;
212 my( $self, $total_items ) = @_;
214 if ( $self->total_setup > 0 ) {
215 push @$total_items, {
216 'total_item' => $self->mt( $self->total_recur > 0 ? 'Total Setup' : 'Total' ),
217 'total_amount' => $self->total_setup,
221 #could/should add up the different recurring frequencies on lines of their own
222 # but this will cover the 95% cases for now
223 if ( $self->total_recur > 0 ) {
224 push @$total_items, {
225 'total_item' => $self->mt('Total Recurring'),
226 'total_amount' => $self->total_recur,
232 =item enable_previous
236 sub enable_previous { 0 }
238 =item convert_cust_main
240 If this quotation already belongs to a customer, then returns that customer, as
241 an FS::cust_main object.
243 Otherwise, creates a new customer (FS::cust_main object and record, and
244 associated) based on this quotation's prospect, then orders this quotation's
245 packages as real packages for the customer.
247 If there is an error, returns an error message, otherwise, returns the
248 newly-created FS::cust_main object.
252 sub convert_cust_main {
255 my $cust_main = $self->cust_main;
256 return $cust_main if $cust_main; #already converted, don't again
258 my $oldAutoCommit = $FS::UID::AutoCommit;
259 local $FS::UID::AutoCommit = 0;
262 $cust_main = $self->prospect_main->convert_cust_main;
263 unless ( ref($cust_main) ) { # eq 'FS::cust_main' ) {
264 $dbh->rollback if $oldAutoCommit;
268 $self->prospectnum('');
269 $self->custnum( $cust_main->custnum );
270 my $error = $self->replace || $self->order;
272 $dbh->rollback if $oldAutoCommit;
276 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
284 This method is for use with quotations which are already associated with a customer.
286 Orders this quotation's packages as real packages for the customer.
288 If there is an error, returns an error message, otherwise returns false.
295 tie my %cust_pkg, 'Tie::RefHash',
296 map { FS::cust_pkg->new({ pkgpart => $_->pkgpart,
297 quantity => $_->quantity,
301 $self->quotation_pkg ;
303 $self->cust_main->order_pkgs( \%cust_pkg );
309 Disables this quotation (sets disabled to Y, which hides the quotation on
310 prospects and customers).
312 If there is an error, returns an error message, otherwise returns false.
318 $self->disabled('Y');
324 Enables this quotation.
326 If there is an error, returns an error message, otherwise returns false.
343 =item search_sql_where HASHREF
345 Class method which returns an SQL WHERE fragment to search for parameters
346 specified in HASHREF. Valid parameters are
352 List reference of start date, end date, as UNIX timestamps.
362 List reference of charged limits (exclusive).
366 List reference of charged limits (exclusive).
370 flag, return open invoices only
374 flag, return net invoices only
382 Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
386 sub search_sql_where {
387 my($class, $param) = @_;
389 # warn "$me search_sql_where called with params: \n".
390 # join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
396 if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
397 push @search, "( prospect_main.agentnum = $1 OR cust_main.agentnum = $1 )";
401 # if ( $param->{'refnum'} =~ /^(\d+)$/ ) {
402 # push @search, "cust_main.refnum = $1";
406 if ( $param->{'prospectnum'} =~ /^(\d+)$/ ) {
407 push @search, "quotation.prospectnum = $1";
411 if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
412 push @search, "cust_bill.custnum = $1";
416 if ( $param->{_date} ) {
417 my($beginning, $ending) = @{$param->{_date}};
419 push @search, "quotation._date >= $beginning",
420 "quotation._date < $ending";
424 if ( $param->{'quotationnum_min'} =~ /^(\d+)$/ ) {
425 push @search, "quotation.quotationnum >= $1";
427 if ( $param->{'quotationnum_max'} =~ /^(\d+)$/ ) {
428 push @search, "quotation.quotationnum <= $1";
432 # if ( $param->{charged} ) {
433 # my @charged = ref($param->{charged})
434 # ? @{ $param->{charged} }
435 # : ($param->{charged});
437 # push @search, map { s/^charged/cust_bill.charged/; $_; }
441 my $owed_sql = FS::cust_bill->owed_sql;
444 push @search, "quotation._date < ". (time-86400*$param->{'days'})
447 #agent virtualization
448 my $curuser = $FS::CurrentUser::CurrentUser;
449 #false laziness w/search/quotation.html
450 push @search,' ( '. $curuser->agentnums_sql( table=>'prospect_main' ).
451 ' OR '. $curuser->agentnums_sql( table=>'cust_main' ).
454 join(' AND ', @search );
464 L<FS::Record>, schema.html from the base documentation.