2 use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::otaker_Mixin FS::Record );
7 use FS::Record qw( qsearch qsearchs );
11 use FS::prospect_main;
12 use FS::quotation_pkg;
16 FS::quotation - Object methods for quotation records
22 $record = new FS::quotation \%hash;
23 $record = new FS::quotation { 'column' => 'value' };
25 $error = $record->insert;
27 $error = $new_record->replace($old_record);
29 $error = $record->delete;
31 $error = $record->check;
35 An FS::quotation object represents a quotation. FS::quotation inherits from
36 FS::Record. The following fields are currently supported:
73 Creates a new quotation. To add the quotation to the database, see L<"insert">.
75 Note that this stores the hash reference, not a distinct copy of the hash it
76 points to. You can ask the object for a copy with the I<hash> method.
80 sub table { 'quotation'; }
81 sub notice_name { 'Quotation'; }
82 sub template_conf { 'quotation_'; }
86 Adds this record to the database. If there is an error, returns the error,
87 otherwise returns false.
91 Delete this record from the database.
93 =item replace OLD_RECORD
95 Replaces the OLD_RECORD with this one in the database. If there is an error,
96 returns the error, otherwise returns false.
100 Checks all fields to make sure this is a valid quotation. If there is
101 an error, returns the error, otherwise returns false. Called by the insert
110 $self->ut_numbern('quotationnum')
111 || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum' )
112 || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum' )
113 || $self->ut_numbern('_date')
114 || $self->ut_enum('disabled', [ '', 'Y' ])
115 || $self->ut_numbern('usernum')
117 return $error if $error;
119 $self->_date(time) unless $self->_date;
121 $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
123 return 'prospectnum or custnum must be specified'
124 if ! $self->prospectnum
136 qsearchs('prospect_main', { 'prospectnum' => $self->prospectnum } );
145 qsearchs('cust_main', { 'custnum' => $self->custnum } );
152 sub cust_bill_pkg { #actually quotation_pkg objects
154 qsearch('quotation_pkg', { quotationnum=>$self->quotationnum });
163 $self->_total('setup');
166 =item total_recur [ FREQ ]
172 #=item total_recur [ FREQ ]
173 #my $freq = @_ ? shift : '';
174 $self->_total('recur');
178 my( $self, $method ) = @_;
181 $total += $_->$method() for $self->cust_bill_pkg;
182 sprintf('%.2f', $total);
186 #prevent things from falsely showing up as taxes, at least until we support
187 # quoting tax amounts..
192 shift->cust_bill_pkg;
196 my( $self, $total_items ) = @_;
198 if ( $self->total_setup > 0 ) {
199 push @$total_items, {
200 'total_item' => $self->mt( $self->total_recur > 0 ? 'Total Setup' : 'Total' ),
201 'total_amount' => $self->total_setup,
205 #could/should add up the different recurring frequencies on lines of their own
206 # but this will cover the 95% cases for now
207 if ( $self->total_recur > 0 ) {
208 push @$total_items, {
209 'total_item' => $self->mt('Total Recurring'),
210 'total_amount' => $self->total_recur,
216 =item enable_previous
220 sub enable_previous { 0 }
222 =item convert_cust_main
224 If this quotation already belongs to a customer, then returns that customer, as
225 an FS::cust_main object.
227 Otherwise, creates a new customer (FS::cust_main object and record, and
228 associated) based on this quotation's prospect, then orders this quotation's
229 packages as real packages for the customer.
231 If there is an error, returns an error message, otherwise, returns the
232 newly-created FS::cust_main object.
236 sub convert_cust_main {
239 my $cust_main = $self->cust_main;
240 return $cust_main if $cust_main; #already converted, don't again
242 my $oldAutoCommit = $FS::UID::AutoCommit;
243 local $FS::UID::AutoCommit = 0;
246 $cust_main = $self->prospect_main->convert_cust_main;
247 unless ( ref($cust_main) ) { # eq 'FS::cust_main' ) {
248 $dbh->rollback if $oldAutoCommit;
252 $self->prospectnum('');
253 $self->custnum( $cust_main->custnum );
254 my $error = $self->replace || $self->order;
256 $dbh->rollback if $oldAutoCommit;
260 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
268 This method is for use with quotations which are already associated with a customer.
270 Orders this quotation's packages as real packages for the customer.
272 If there is an error, returns an error message, otherwise returns false.
279 tie my %cust_pkg, 'Tie::RefHash',
280 map { FS::cust_pkg->new({ pkgpart => $_->pkgpart,
281 quantity => $_->quantity,
285 $self->quotation_pkg ;
287 $self->cust_main->order_pkgs( \%cust_pkg );
297 qsearch('quotation_pkg', { 'quotationnum' => $self->quotationnum } );
307 =item search_sql_where HASHREF
309 Class method which returns an SQL WHERE fragment to search for parameters
310 specified in HASHREF. Valid parameters are
316 List reference of start date, end date, as UNIX timestamps.
326 List reference of charged limits (exclusive).
330 List reference of charged limits (exclusive).
334 flag, return open invoices only
338 flag, return net invoices only
346 Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
350 sub search_sql_where {
351 my($class, $param) = @_;
353 # warn "$me search_sql_where called with params: \n".
354 # join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
360 if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
361 push @search, "( prospect_main.agentnum = $1 OR cust_main.agentnum = $1 )";
365 # if ( $param->{'refnum'} =~ /^(\d+)$/ ) {
366 # push @search, "cust_main.refnum = $1";
370 if ( $param->{'prospectnum'} =~ /^(\d+)$/ ) {
371 push @search, "quotation.prospectnum = $1";
375 if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
376 push @search, "cust_bill.custnum = $1";
380 if ( $param->{_date} ) {
381 my($beginning, $ending) = @{$param->{_date}};
383 push @search, "quotation._date >= $beginning",
384 "quotation._date < $ending";
388 if ( $param->{'quotationnum_min'} =~ /^(\d+)$/ ) {
389 push @search, "quotation.quotationnum >= $1";
391 if ( $param->{'quotationnum_max'} =~ /^(\d+)$/ ) {
392 push @search, "quotation.quotationnum <= $1";
396 # if ( $param->{charged} ) {
397 # my @charged = ref($param->{charged})
398 # ? @{ $param->{charged} }
399 # : ($param->{charged});
401 # push @search, map { s/^charged/cust_bill.charged/; $_; }
405 my $owed_sql = FS::cust_bill->owed_sql;
408 push @search, "quotation._date < ". (time-86400*$param->{'days'})
411 #agent virtualization
412 my $curuser = $FS::CurrentUser::CurrentUser;
413 #false laziness w/search/quotation.html
414 push @search,' ( '. $curuser->agentnums_sql( table=>'prospect_main' ).
415 ' OR '. $curuser->agentnums_sql( table=>'cust_main' ).
418 join(' AND ', @search );
428 L<FS::Record>, schema.html from the base documentation.