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);
172 my $opt = shift || {};
173 if ($opt and !ref($opt)) {
174 die ref($self). '->email called with positional parameters';
177 my $conf = $self->conf;
179 my $from = delete $opt->{from};
181 # this is where we set the From: address
182 $from ||= $conf->config('quotation_from', $self->cust_or_prospect->agentnum )
183 || $conf->config('invoice_from', $self->cust_or_prospect->agentnum );
185 $self->SUPER::email( {
196 $self->conf->config('quotation_subject') #, $self->cust_main->agentnum)
199 #my $cust_main = $self->cust_main;
200 #my $name = $cust_main->name;
201 #my $name_short = $cust_main->name_short;
202 #my $invoice_number = $self->invnum;
203 #my $invoice_date = $self->_date_pretty;
208 =item cust_or_prosect
212 sub cust_or_prospect {
214 $self->custnum ? $self->cust_main : $self->prospect_main;
217 =item cust_or_prospect_label_link P
219 HTML links to either the customer or prospect.
221 Returns a list consisting of two elements. The first is a text label for the
222 link, and the second is the URL.
226 sub cust_or_prospect_label_link {
227 my( $self, $p ) = @_;
229 if ( my $custnum = $self->custnum ) {
230 my $display_custnum = $self->cust_main->display_custnum;
231 my $target = $FS::CurrentUser::CurrentUser->default_customer_view eq 'jumbo'
233 : ';show=quotations';
235 emt("View this customer (#[_1])",$display_custnum) =>
236 "${p}view/cust_main.cgi?custnum=$custnum$target"
238 } elsif ( my $prospectnum = $self->prospectnum ) {
240 emt("View this prospect (#[_1])",$prospectnum) =>
241 "${p}view/prospect_main.html?$prospectnum"
249 #prevent things from falsely showing up as taxes, at least until we support
250 # quoting tax amounts..
255 shift->cust_bill_pkg;
259 my( $self, $total_items ) = @_;
261 if ( $self->total_setup > 0 ) {
262 push @$total_items, {
263 'total_item' => $self->mt( $self->total_recur > 0 ? 'Total Setup' : 'Total' ),
264 'total_amount' => $self->total_setup,
268 #could/should add up the different recurring frequencies on lines of their own
269 # but this will cover the 95% cases for now
270 if ( $self->total_recur > 0 ) {
271 push @$total_items, {
272 'total_item' => $self->mt('Total Recurring'),
273 'total_amount' => $self->total_recur,
279 =item enable_previous
283 sub enable_previous { 0 }
285 =item convert_cust_main
287 If this quotation already belongs to a customer, then returns that customer, as
288 an FS::cust_main object.
290 Otherwise, creates a new customer (FS::cust_main object and record, and
291 associated) based on this quotation's prospect, then orders this quotation's
292 packages as real packages for the customer.
294 If there is an error, returns an error message, otherwise, returns the
295 newly-created FS::cust_main object.
299 sub convert_cust_main {
302 my $cust_main = $self->cust_main;
303 return $cust_main if $cust_main; #already converted, don't again
305 my $oldAutoCommit = $FS::UID::AutoCommit;
306 local $FS::UID::AutoCommit = 0;
309 $cust_main = $self->prospect_main->convert_cust_main;
310 unless ( ref($cust_main) ) { # eq 'FS::cust_main' ) {
311 $dbh->rollback if $oldAutoCommit;
315 $self->prospectnum('');
316 $self->custnum( $cust_main->custnum );
317 my $error = $self->replace || $self->order;
319 $dbh->rollback if $oldAutoCommit;
323 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
331 This method is for use with quotations which are already associated with a customer.
333 Orders this quotation's packages as real packages for the customer.
335 If there is an error, returns an error message, otherwise returns false.
342 tie my %cust_pkg, 'Tie::RefHash',
343 map { FS::cust_pkg->new({ pkgpart => $_->pkgpart,
344 quantity => $_->quantity,
348 $self->quotation_pkg ;
350 $self->cust_main->order_pkgs( \%cust_pkg );
356 Disables this quotation (sets disabled to Y, which hides the quotation on
357 prospects and customers).
359 If there is an error, returns an error message, otherwise returns false.
365 $self->disabled('Y');
371 Enables this quotation.
373 If there is an error, returns an error message, otherwise returns false.
390 =item search_sql_where HASHREF
392 Class method which returns an SQL WHERE fragment to search for parameters
393 specified in HASHREF. Valid parameters are
399 List reference of start date, end date, as UNIX timestamps.
409 List reference of charged limits (exclusive).
413 List reference of charged limits (exclusive).
417 flag, return open invoices only
421 flag, return net invoices only
429 Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
433 sub search_sql_where {
434 my($class, $param) = @_;
436 # warn "$me search_sql_where called with params: \n".
437 # join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
443 if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
444 push @search, "( prospect_main.agentnum = $1 OR cust_main.agentnum = $1 )";
448 # if ( $param->{'refnum'} =~ /^(\d+)$/ ) {
449 # push @search, "cust_main.refnum = $1";
453 if ( $param->{'prospectnum'} =~ /^(\d+)$/ ) {
454 push @search, "quotation.prospectnum = $1";
458 if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
459 push @search, "cust_bill.custnum = $1";
463 if ( $param->{_date} ) {
464 my($beginning, $ending) = @{$param->{_date}};
466 push @search, "quotation._date >= $beginning",
467 "quotation._date < $ending";
471 if ( $param->{'quotationnum_min'} =~ /^(\d+)$/ ) {
472 push @search, "quotation.quotationnum >= $1";
474 if ( $param->{'quotationnum_max'} =~ /^(\d+)$/ ) {
475 push @search, "quotation.quotationnum <= $1";
479 # if ( $param->{charged} ) {
480 # my @charged = ref($param->{charged})
481 # ? @{ $param->{charged} }
482 # : ($param->{charged});
484 # push @search, map { s/^charged/cust_bill.charged/; $_; }
488 my $owed_sql = FS::cust_bill->owed_sql;
491 push @search, "quotation._date < ". (time-86400*$param->{'days'})
494 #agent virtualization
495 my $curuser = $FS::CurrentUser::CurrentUser;
496 #false laziness w/search/quotation.html
497 push @search,' ( '. $curuser->agentnums_sql( table=>'prospect_main' ).
498 ' OR '. $curuser->agentnums_sql( table=>'cust_main' ).
501 join(' AND ', @search );
511 L<FS::Record>, schema.html from the base documentation.