1 package FS::quotation_pkg;
2 use base qw( FS::TemplateItem_Mixin FS::Record );
5 use FS::Record qw( qsearchs qsearch dbh );
7 use FS::quotation_pkg_discount; #so its loaded when TemplateItem_Mixin needs it
8 use List::Util qw(sum);
12 FS::quotation_pkg - Object methods for quotation_pkg records
16 use FS::quotation_pkg;
18 $record = new FS::quotation_pkg \%hash;
19 $record = new FS::quotation_pkg { 'column' => 'value' };
21 $error = $record->insert;
23 $error = $new_record->replace($old_record);
25 $error = $record->delete;
27 $error = $record->check;
31 An FS::quotation_pkg object represents a quotation package.
32 FS::quotation_pkg inherits from FS::Record. The following fields are currently
43 pkgpart (L<FS::part_pkg>) of the package
47 locationnum (L<FS::cust_location>) where the package will be in service
51 expected start date for the package, as a timestamp
63 'Y' to waive the setup fee
67 The amount per package that will be charged in setup/one-time fees.
71 The amount per package that will be charged per billing cycle.
75 The length of the billing cycle. If zero it's a one-time charge; if any
76 other number it's that many months; other values are in L<FS::Misc::pkg_freqs>.
86 Creates a new quotation package. To add the quotation package to the database,
89 Note that this stores the hash reference, not a distinct copy of the hash it
90 points to. You can ask the object for a copy with the I<hash> method.
94 sub table { 'quotation_pkg'; }
96 sub display_table { 'quotation_pkg'; }
98 #forget it, just overriding cust_bill_pkg_display entirely
99 #sub display_table_orderby { 'quotationpkgnum'; } # something else?
100 # # (for invoice display order)
102 sub discount_table { 'quotation_pkg_discount'; }
106 Adds this record to the database. If there is an error, returns the error,
107 otherwise returns false.
112 my ($self, %options) = @_;
115 my $oldAutoCommit = $FS::UID::AutoCommit;
116 local $FS::UID::AutoCommit = 0;
118 my $error = $self->SUPER::insert;
120 if ( !$error and ($self->setup_discountnum || $self->recur_discountnum) ) {
121 #warn "inserting discount\n";
122 $error = $self->insert_discount;
123 $error .= ' (setting discount)' if $error;
127 $dbh->rollback if $oldAutoCommit;
130 $dbh->commit if $oldAutoCommit;
137 Delete this record from the database.
145 my $oldAutoCommit = $FS::UID::AutoCommit;
146 local $FS::UID::AutoCommit = 0;
148 foreach ($self->quotation_pkg_discount, $self->quotation_pkg_tax) {
149 my $error = $_->delete;
151 $dbh->rollback if $oldAutoCommit;
152 return $error . ' (deleting discount)';
156 my $error = $self->SUPER::delete;
158 $dbh->rollback if $oldAutoCommit;
161 $dbh->commit if $oldAutoCommit;
164 $self->quotation->estimate;
167 =item replace OLD_RECORD
169 Replaces the OLD_RECORD with this one in the database. If there is an error,
170 returns the error, otherwise returns false.
174 Checks all fields to make sure this is a valid quotation package. If there is
175 an error, returns the error, otherwise returns false. Called by the insert
183 my @freqs = ('', keys (%{ FS::Misc->pkg_freqs }));
186 $self->ut_numbern('quotationpkgnum')
187 || $self->ut_foreign_key( 'quotationnum', 'quotation', 'quotationnum' )
188 || $self->ut_foreign_key( 'pkgpart', 'part_pkg', 'pkgpart' )
189 || $self->ut_foreign_keyn( 'locationnum', 'cust_location', 'locationnum' )
190 || $self->ut_numbern('start_date')
191 || $self->ut_numbern('contract_end')
192 || $self->ut_numbern('quantity')
193 || $self->ut_moneyn('unitsetup')
194 || $self->ut_moneyn('unitrecur')
195 || $self->ut_enum('freq', \@freqs)
196 || $self->ut_enum('waive_setup', [ '', 'Y'] )
199 if ($self->locationnum eq '') {
200 # use the customer default
201 my $quotation = $self->quotation;
202 if ($quotation->custnum) {
203 $self->set('locationnum', $quotation->cust_main->ship_locationnum);
204 } elsif ($quotation->prospectnum) {
205 # use the first non-disabled location for that prospect
206 my $cust_location = qsearchs('cust_location',
207 { prospectnum => $quotation->prospectnum,
209 $self->set('locationnum', $cust_location->locationnum) if $cust_location;
210 } # else the quotation is invalid
213 return $error if $error;
218 #it looks redundant with a v4.x+ auto-generated method, but need to override
219 # FS::TemplateItem_Mixin's version
222 qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart } );
227 $self->part_pkg->pkg;
232 =item insert_discount
234 Associates this package with a discount (see L<FS::cust_pkg_discount>,
235 possibly inserting a new discount on the fly (see L<FS::discount>). Properties
236 of the discount will be taken from this object.
240 sub insert_discount {
241 #my ($self, %options) = @_;
244 foreach my $x (qw(setup recur)) {
245 if ( my $discountnum = $self->get("${x}_discountnum") ) {
246 my $cust_pkg_discount = FS::quotation_pkg_discount->new( {
247 'quotationpkgnum' => $self->quotationpkgnum,
248 'discountnum' => $discountnum,
250 #for the create a new discount case
251 'amount' => $self->get("${x}_discountnum_amount"),
252 'percent' => $self->get("${x}_discountnum_percent"),
253 'months' => $self->get("${x}_discountnum_months"),
255 if ( $x eq 'setup' ) {
256 $cust_pkg_discount->setup('Y');
257 $cust_pkg_discount->months('');
259 my $error = $cust_pkg_discount->insert;
260 return $error if $error;
268 my $setuprecur = $options{'setuprecur'};
269 # a little different from cust_bill_pkg::_item_discount, in that this one
270 # is asked specifically whether to show setup or recur discounts (because
271 # on the quotation they're separate sections entirely)
273 my @pkg_discounts = grep { $_->setuprecur eq $setuprecur }
275 return if @pkg_discounts == 0;
280 description => $self->mt('Discount'),
282 ext_description => \@ext,
283 # maybe should show quantity/unit discount?
285 foreach my $pkg_discount (@pkg_discounts) {
286 push @ext, $pkg_discount->description;
287 my $amount = $pkg_discount->get('amount');
288 $d->{amount} -= $amount;
290 $d->{amount} = sprintf('%.2f', $d->{amount} * $self->quantity);
297 return '0.00' if $self->waive_setup eq 'Y';;
298 my $discount_amount = sum(0, map { $_->amount }
299 grep { $_->setuprecur eq 'setup' }
302 ($self->unitsetup - $discount_amount) * ($self->quantity || 1);
308 sum(0, map { $_->setup_amount } $self->quotation_pkg_tax);
313 my $discount_amount = sum(0, map { $_->amount }
314 grep { $_->setuprecur eq 'recur' }
317 ($self->unitrecur - $discount_amount) * ($self->quantity || 1);
323 sum(0, map { $_->recur_amount } $self->quotation_pkg_tax);
326 =item part_pkg_currency_option OPTIONNAME
328 Returns a two item list consisting of the currency of this quotation's customer
329 or prospect, if any, and a value for the provided option. If the customer or
330 prospect has a currency, the value is the option value the given name and the
331 currency (see L<FS::part_pkg_currency>). Otherwise, if the customer or
332 prospect has no currency, is the regular option value for the given name (see
333 L<FS::part_pkg_option>).
337 #false laziness w/cust_pkg->part_pkg_currency_option
338 sub part_pkg_currency_option {
339 my( $self, $optionname ) = @_;
340 my $part_pkg = $self->part_pkg;
341 my $prospect_or_customer = $self->cust_main || $self->prospect_main;
342 if ( my $currency = $prospect_or_customer->currency ) {
343 ($currency, $part_pkg->part_pkg_currency_option($currency, $optionname) );
345 ('', $part_pkg->option($optionname) );
350 =item cust_bill_pkg_display [ type => TYPE ]
354 sub cust_bill_pkg_display {
355 my ( $self, %opt ) = @_;
357 my $type = $opt{type} if exists $opt{type};
358 return () if $type eq 'U'; #quotations don't have usage
360 if ( $self->get('display') ) {
361 return ( grep { defined($type) ? ($type eq $_->type) : 1 }
362 @{ $self->get('display') }
367 my $setup = $self->new($self->hashref);
368 $setup->{'_NO_RECUR_KLUDGE'} = 1;
369 $setup->{'type'} = 'S';
370 my $recur = $self->new($self->hashref);
371 $recur->{'_NO_SETUP_KLUDGE'} = 1;
372 $recur->{'type'} = 'R';
374 if ( $type eq 'S' ) {
376 } elsif ( $type eq 'R' ) {
379 #return ($setup, $recur);
389 Returns the customer (L<FS::cust_main> object).
395 my $quotation = $self->quotation or return '';
396 $quotation->cust_main;
401 Returns the prospect (L<FS::prospect_main> object).
407 my $quotation = $self->quotation or return '';
408 $quotation->prospect_main;
411 sub tax_locationnum {
418 my @quotation_pkg_without_location =
419 qsearch( 'quotation_pkg', { locationnum => '' } );
420 if (@quotation_pkg_without_location) {
421 warn "setting default location on quotation_pkg records\n";
422 foreach my $quotation_pkg (@quotation_pkg_without_location) {
423 # check() will fix this
424 my $error = $quotation_pkg->replace;
426 die "quotation #".$quotation_pkg->quotationnum.": $error\n";
437 Doesn't support fees, or add-on packages.
441 L<FS::Record>, schema.html from the base documentation.