=item delay_start - Number of days to delay package start, by default
+=item start_on_hold - 'Y' to suspend this package immediately when it is
+ordered. The package will not start billing or have a setup fee charged
+until it is manually unsuspended.
+
=back
=head1 METHODS
|| $self->ut_textn('comment')
|| $self->ut_textn('promo_code')
|| $self->ut_alphan('plan')
- || $self->ut_enum('setuptax', [ '', 'Y' ] )
- || $self->ut_enum('recurtax', [ '', 'Y' ] )
+ || $self->ut_flag('setuptax')
+ || $self->ut_flag('recurtax')
|| $self->ut_textn('taxclass')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- || $self->ut_enum('custom', [ '', 'Y' ] )
- || $self->ut_enum('no_auto', [ '', 'Y' ])
- || $self->ut_enum('recur_show_zero', [ '', 'Y' ])
- || $self->ut_enum('setup_show_zero', [ '', 'Y' ])
+ || $self->ut_flag('disabled')
+ || $self->ut_flag('custom')
+ || $self->ut_flag('no_auto')
+ || $self->ut_flag('recur_show_zero')
+ || $self->ut_flag('setup_show_zero')
+ || $self->ut_flag('start_on_hold')
#|| $self->ut_moneyn('setup_cost')
#|| $self->ut_moneyn('recur_cost')
|| $self->ut_floatn('setup_cost')
sub can_discount { 0; }
# whether the plan allows changing the start date
-sub can_start_date { 1; }
+sub can_start_date {
+ my $self = shift;
+ $self->start_on_hold ? 0 : 1;
+}
# whether the plan supports part_pkg_usageprice add-ons (a specific kind of
# pre-selectable usage pricing, there's others this doesn't refer to)
$self;
}
+=item calc_setup CUST_PKG START_DATE DETAILS_ARRAYREF OPTIONS_HASHREF
+
+=item calc_recur CUST_PKG START_DATE DETAILS_ARRAYREF OPTIONS_HASHREF
+
+Calculates and returns the setup or recurring fees, respectively, for this
+package. Implementation is in the FS::part_pkg:* module specific to this price
+plan.
+
+Adds invoicing details to the passed-in DETAILS_ARRAYREF
+
+Options are passed as a hashref. Available options:
+
+=over 4
+
+=item freq_override
+
+Frequency override (for calc_recur)
+
+=item discounts
+
+This option is filled in by the method rather than controlling its operation.
+It is an arrayref. Applicable discounts will be added to the arrayref, as
+L<FS::cust_bill_pkg_discount|FS::cust_bill_pkg_discount records>.
+
+=item real_pkgpart
+
+For package add-ons, is the base L<FS::part_pkg|package definition>, otherwise
+no different than pkgpart.
+
+=item precommit_hooks
+
+This option is filled in by the method rather than controlling its operation.
+It is an arrayref. Anonymous coderefs will be added to the arrayref. They
+need to be called before completing the billing operation. For calc_recur
+only.
+
+=item increment_next_bill
+
+Increment the next bill date (boolean, for calc_recur). Typically true except
+for particular situations.
+
+=item setup_fee
+
+This option is filled in by the method rather than controlling its operation.
+It indicates a deferred setup fee that is billed at calc_recur time (see price
+plan option prorate_defer_bill).
+
+=back
+
+Note: Don't calculate prices when not actually billing the package. For that,
+see the L</base_setup|base_setup> and L</base_recur|base_recur> methods.
+
+=cut
+
#fatal fallbacks
sub calc_setup { die 'no calc_setup for '. shift->plan. "\n"; }
sub calc_recur { die 'no calc_recur for '. shift->plan. "\n"; }
-#fallback that return 0 for old legacy packages with no plan
+=item calc_remain CUST_PKG [ OPTION => VALUE ... ]
+
+Calculates and returns the remaining value to be credited upon package
+suspension, change, or cancellation, if enabled.
+
+Options are passed as a list of keys and values. Available options:
+
+=over 4
+
+=item time
+
+Override for the current time
+
+=item cust_credit_source_bill_pkg
+
+This option is filled in by the method rather than controlling its operation.
+It is an arrayref.
+L<FS::cust_credit_source_bill_pkg|FS::cust_credit_source_bill_pkg> records will
+be added to the arrayref indicating the specific line items and amounts which
+are the source of this remaining credit.
+
+=back
+
+Note: Don't calculate prices when not actually suspending or cancelling the
+package.
+
+=cut
+
+#fallback that returns 0 for old legacy packages with no plan
sub calc_remain { 0; }
+
+=item calc_units CUST_PKG
+
+This returns the number of provisioned svc_phone records, or, of the package
+count_available_phones option is set, the number available to be provisoined
+in the package.
+
+=cut
+
+#fallback that returns 0 for old legacy packages with no plan
sub calc_units { 0; }
#fallback for everything not based on flat.pm
sub recur_temporality { 'upcoming'; }
+
+=item calc_cancel START_DATE DETAILS_ARRAYREF OPTIONS_HASHREF
+
+Runs any necessary billing on cancellation: another recurring cycle for
+recur_temporailty 'preceding' pacakges with the bill_recur_on_cancel option
+set (calc_recur), or, any outstanding usage for pacakges with the
+bill_usage_on_cancel option set (calc_usage).
+
+=cut
+
+#fallback for everything not based on flat.pm, doesn't do this yet (which is
+#okay, nothing of ours not based on flat.pm does usage-on-cancel billing
sub calc_cancel { 0; }
#fallback for everything except bulk.pm
sub recur_cost_permonth {
my($self, $cust_pkg) = @_;
return 0 unless $self->freq =~ /^\d+$/ && $self->freq > 0;
- sprintf('%.2f', $self->recur_cost / $self->freq );
+ sprintf('%.2f', ($self->recur_cost || 0) / $self->freq );
}
=item cust_bill_pkg_recur CUST_PKG
sub setup_margin {
my $self = shift;
- $self->unit_setup(@_) - $self->setup_cost;
+ $self->unit_setup(@_) - ($self->setup_cost || 0);
}
=item recur_margin_permonth
# Used by FS::Upgrade to migrate to a new database.
sub _upgrade_data { # class method
- my($class, %opts) = @_;
+ my($class, %opts) = @_;
warn "[FS::part_pkg] upgrading $class\n" if $DEBUG;
$part_pkg->replace;
}
+
+ # Convert RADIUS accounting usage metrics from megabytes to gigabytes
+ # (FS RT#28105)
+ my $upgrade = 'part_pkg_gigabyte_usage';
+ if (!FS::upgrade_journal->is_done($upgrade)) {
+ foreach my $part_pkg (qsearch('part_pkg',
+ { plan => 'sqlradacct_hour' })
+ ){
+
+ my $pkgpart = $part_pkg->pkgpart;
+
+ foreach my $opt (qsearch('part_pkg_option',
+ { 'optionname' => { op => 'LIKE',
+ value => 'recur_included_%',
+ },
+ pkgpart => $pkgpart,
+ })){
+
+ next if $opt->optionname eq 'recur_included_hours'; # unfortunately named field
+ next if $opt->optionvalue == 0;
+
+ $opt->optionvalue($opt->optionvalue / 1024);
+
+ my $error = $opt->replace;
+ die $error if $error;
+ }
+
+ foreach my $opt (qsearch('part_pkg_option',
+ { 'optionname' => { op => 'LIKE',
+ value => 'recur_%_charge',
+ },
+ pkgpart => $pkgpart,
+ })){
+ $opt->optionvalue($opt->optionvalue * 1024);
+
+ my $error = $opt->replace;
+ die $error if $error;
+ }
+
+ }
+ FS::upgrade_journal->set_done($upgrade);
+ }
+
# the rest can be done asynchronously
}