specify Avalara tax product for per-line taxes, #73063
[freeside.git] / FS / FS / part_pkg.pm
index 9ed623f..ae63487 100644 (file)
@@ -735,6 +735,7 @@ sub check {
     || $self->ut_floatn('pay_weight')
     || $self->ut_floatn('credit_weight')
     || $self->ut_numbern('taxproductnum')
+    || $self->ut_numbern('units_taxproductnum')
     || $self->ut_foreign_keyn('classnum',       'pkg_class', 'classnum')
     || $self->ut_foreign_keyn('addon_classnum', 'pkg_class', 'classnum')
     || $self->ut_foreign_keyn('taxproductnum',
@@ -770,6 +771,40 @@ sub check {
   '';
 }
 
+=item check_options
+
+For a passed I<$options> hashref, validates any options that
+have 'validate' subroutines defined in the info hash, 
+then validates the entire hashref if the price plan has 
+its own 'validate' subroutine defined in the info hash 
+(I<$options> values might be altered.)  
+
+Returns error message, or empty string if valid.
+
+Invoked by L</insert> and L</replace> via the equivalent
+methods in L<FS::option_Common>.
+
+=cut
+
+sub check_options {
+  my ($self,$options) = @_;
+  foreach my $option (keys %$options) {
+    if (exists $plans{ $self->plan }->{fields}->{$option}) {
+      if (exists($plans{$self->plan}->{fields}->{$option}->{'validate'})) {
+        # pass option name for use in error message
+        # pass a reference to the $options value, so it can be cleaned up
+        my $error = &{$plans{$self->plan}->{fields}->{$option}->{'validate'}}($option,\($options->{$option}));
+        return $error if $error;
+      }
+    } # else "option does not exist" error?
+  }
+  if (exists($plans{$self->plan}->{'validate'})) {
+    my $error = &{$plans{$self->plan}->{'validate'}}($options);
+    return $error if $error;
+  }
+  return '';
+}
+
 =item check_pkg_svc
 
 Checks pkg_svc records as a whole (for part_svc_link dependencies).
@@ -1697,6 +1732,19 @@ sub taxproduct_description {
   $part_pkg_taxproduct ? $part_pkg_taxproduct->description : '';
 }
 
+=item units_taxproduct
+
+Returns the L<FS::part_pkg_taxproduct> record used to report the taxable
+service units (usually phone lines) on this package.
+
+=cut
+
+sub units_taxproduct {
+  my $self = shift;
+  $self->units_taxproductnum
+    ? FS::part_pkg_taxproduct->by_key($self->units_taxproductnum)
+    : '';
+}
 
 =item tax_rates DATA_PROVIDER, GEOCODE, [ CLASS ]
 
@@ -1883,13 +1931,27 @@ 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
+count_available_phones option is set, the number available to be provisioned
 in the package.
 
 =cut
 
-#fallback that returns 0 for old legacy packages with no plan
-sub calc_units  { 0; }
+sub calc_units {
+  my($self, $cust_pkg ) = @_;
+  my $count = 0;
+  if ( $self->option('count_available_phones', 1)) {
+    foreach my $pkg_svc ($cust_pkg->part_pkg->pkg_svc) {
+      if ($pkg_svc->part_svc->svcdb eq 'svc_phone') { # svc_pbx?
+        $count += $pkg_svc->quantity || 0;
+      }
+    }
+    $count *= $cust_pkg->quantity;
+  } else {
+    $count =
+      scalar(grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc);
+  }
+  $count;
+}
 
 #fallback for everything not based on flat.pm
 sub recur_temporality { 'upcoming'; }
@@ -1981,6 +2043,18 @@ sub recur_margin_permonth {
   $self->base_recur_permonth(@_) - $self->recur_cost_permonth(@_);
 }
 
+=item intro_end PACKAGE
+
+Takes an L<FS::cust_pkg> object.  If this plan has an introductory rate,
+returns the expected date the intro period will end. If there is no intro
+rate, returns zero.
+
+=cut
+
+sub intro_end {
+  0;
+}
+
 =item format OPTION DATA
 
 Returns data formatted according to the function 'format' described