[freeside-commits] branch master updated. 6fd39bf8dfa989aaedea59e5e3cd609642f9e024

Mark Wells mark at 420.am
Thu Feb 25 17:11:43 PST 2016


The branch, master has been updated
       via  6fd39bf8dfa989aaedea59e5e3cd609642f9e024 (commit)
       via  808fdb0f9c2b3468cda54cc92a945a1fcd9ee7f4 (commit)
       via  fa97f29ea43276a6f2636e1f251c775c7fa9e67c (commit)
       via  50f247ce6da7497428ce772d3c37c6cba3b9c736 (commit)
       via  2e49e11b18901a0d0cdace8d0dd8b06a5ac25262 (commit)
       via  84e973ebe4c82881c62b71f996ec9ca124a5dce5 (commit)
      from  54874515da35cc39db56c647b992ae8dea17ad21 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 6fd39bf8dfa989aaedea59e5e3cd609642f9e024
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Feb 25 17:10:56 2016 -0800

    sipwise export, part 2

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 36418ac..d20385e 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -5842,6 +5842,7 @@ sub tables_hashref {
         'pbxsvc',                         'int', 'NULL',      '', '', '',
         'domsvc',                         'int', 'NULL',      '', '', '', 
         'locationnum',                    'int', 'NULL',      '', '', '',
+        'forward_svcnum',                 'int', 'NULL',      '', '', '',
         'forwarddst',                 'varchar', 'NULL',      15, '', '', 
         'email',                      'varchar', 'NULL',     255, '', '', 
         'lnp_status',                 'varchar', 'NULL', $char_d, '', '',
diff --git a/FS/FS/part_export/sipwise.pm b/FS/FS/part_export/sipwise.pm
index 690a14c..d2af7da 100644
--- a/FS/FS/part_export/sipwise.pm
+++ b/FS/FS/part_export/sipwise.pm
@@ -5,7 +5,6 @@ use strict;
 
 use FS::Record qw(qsearch qsearchs dbh);
 use Tie::IxHash;
-use Carp;
 use LWP::UserAgent;
 use URI;
 use Cpanel::JSON::XS;
@@ -13,6 +12,7 @@ use HTTP::Request::Common qw(GET POST PUT DELETE);
 use FS::Misc::DateTime qw(parse_datetime);
 use DateTime;
 use Number::Phone;
+use Try::Tiny;
 
 our $me = '[sipwise]';
 our $DEBUG = 2;
@@ -34,7 +34,7 @@ tie my %options, 'Tie::IxHash',
 
 tie my %roles, 'Tie::IxHash',
   'subscriber'    => {  label     => 'Subscriber',
-                        svcdb     => 'svc_phone',
+                        svcdb     => 'svc_acct',
                         multiple  => 1,
                      },
   'did'           => {  label     => 'DID',
@@ -44,7 +44,7 @@ tie my %roles, 'Tie::IxHash',
 ;
 
 our %info = (
-  'svc'      => [qw( svc_phone )],
+  'svc'      => [qw( svc_acct svc_phone )],
   'desc'     => 'Provision to a Sipwise sip:provider server',
   'options'  => \%options,
   'roles'    => \%roles,
@@ -52,12 +52,12 @@ our %info = (
 <P>Export to a <b>sip:provider</b> server.</P>
 <P>This requires two service definitions to be configured on the same package:
   <OL>
-    <LI>A phone service for a SIP client account ("subscriber"). The
-    <i>phonenum</i> will be the SIP username. The <i>domsvc</i> should point
+    <LI>An account service for a SIP client account ("subscriber"). The
+    <i>username</i> will be the SIP username. The <i>domsvc</i> should point
     to a domain service to use as the SIP domain name.</LI>
     <LI>A phone service for a DID. The <i>phonenum</i> here will be a PSTN
-    number. The <i>forwarddst</i> field should be set to the SIP username
-    of the subscriber who should receive calls directed to this number.</LI>
+    number. The <i>forward_svcnum</i> field should be set to the account that
+    will receive calls at this number.
   </OL>
 </P>
 <P>Export options:
@@ -68,63 +68,61 @@ END
 sub export_insert {
   my($self, $svc_x) = (shift, shift);
 
-  local $@;
+  my $error;
   my $role = $self->svc_role($svc_x);
   if ( $role eq 'subscriber' ) {
 
-    eval { $self->insert_subscriber($svc_x) };
-    return "$me $@" if $@;
+    try { $self->insert_subscriber($svc_x) }
+    catch { $error = $_ };
 
   } elsif ( $role eq 'did' ) {
 
-    # only export the DID if it's set to forward to somewhere...
-    return if $svc_x->forwarddst eq '';
-    my $subscriber = qsearchs('svc_phone', { phonenum => $svc_x->forwarddst });
-    # and there is a service for the forwarding destination...
-    return if !$subscriber;
-    # and that service is managed by this export.
-    return if !$self->svc_role($subscriber);
-
-    eval { $self->replace_subscriber($subscriber) };
-    return "$me $@" if $@;
+    try { $self->export_did($svc_x) }
+    catch { $error = $_ };
 
   }
+  return "$me $error" if $error;
   '';
 }
 
 sub export_replace {
   my ($self, $svc_new, $svc_old) = @_;
   my $role = $self->svc_role($svc_new);
-  local $@;
+
+  my $error;
   if ( $role eq 'subscriber' ) {
-    eval { $self->replace_subscriber($svc_new, $svc_old) };
+
+    try { $self->replace_subscriber($svc_new, $svc_old) }
+    catch { $error = $_ };
+
   } elsif ( $role eq 'did' ) {
-    eval { $self->replace_did($svc_new, $svc_old) };
+
+    try { $self->export_did($svc_new, $svc_old) }
+    catch { $error = $_ };
+
   }
-  return "$me $@" if $@;
+  return "$me $error" if $error;
   '';
 }
 
 sub export_delete {
   my ($self, $svc_x) = (shift, shift);
   my $role = $self->svc_role($svc_x);
-  local $@;
+  my $error;
+
   if ( $role eq 'subscriber' ) {
 
     # no need to remove DIDs from it, just drop the subscriber record
-    eval { $self->delete_subscriber($svc_x) };
+    try { $self->delete_subscriber($svc_x) }
+    catch { $error = $_ };
 
   } elsif ( $role eq 'did' ) {
 
-    return if !$svc_x->forwarddst;
-    my $subscriber = qsearchs('svc_phone', { phonenum => $svc_x->forwarddst });
-    return if !$subscriber;
-    return if !$self->svc_role($subscriber);
- 
-    eval { $self->delete_did($svc_x, $subscriber) };
+    try { $self->export_did($svc_x) }
+    catch { $error = $_ };
 
   }
-  return "$me $@" if $@;
+  return "$me $error" if $error;
   '';
 }
 
@@ -194,7 +192,7 @@ sub find_or_create_customer {
     ]
   );
   if (!$billing_profile) {
-    croak "can't find billing profile '". $self->option('billing_profile') . "'";
+    die "can't find billing profile '". $self->option('billing_profile') . "'\n";
   }
   my $bpid = $billing_profile->{id};
 
@@ -255,6 +253,41 @@ sub find_or_create_domain {
   );
 }
 
+########
+# DIDS #
+########
+
+=item acct_for_did SVC_PHONE
+
+Returns the subscriber svc_acct linked to SVC_PHONE.
+
+=cut
+
+sub acct_for_did {
+  my $self = shift;
+  my $svc_phone = shift;
+  my $svcnum = $svc_phone->forward_svcnum or return;
+  my $svc_acct = FS::svc_acct->by_key($svcnum) or return;
+  $self->svc_role($svc_acct) eq 'subscriber' or return;
+  $svc_acct;
+}
+
+=item export_did NEW, OLD
+
+Refreshes the subscriber information for the service the DID was linked to
+previously, and the one it's linked to now.
+
+=cut
+
+sub export_did {
+  my $self = shift;
+  my ($new, $old) = @_;
+  if ( $old and $new->forward_svcnum ne $old->forward_svcnum ) {
+    $self->replace_subscriber( $self->acct_for_did($old) );
+  }
+  $self->replace_subscriber( $self->acct_for_did($new) );
+}
+
 ###############
 # SUBSCRIBERS #
 ###############
@@ -270,7 +303,7 @@ sub get_subscriber {
   my $svc = shift;
 
   my $svcnum = $svc->svcnum;
-  my $svcid = "svc_phone#$svcnum";
+  my $svcid = "svc#$svcnum";
 
   my $pkgnum = $svc->cust_svc->pkgnum;
   my $custid = "cust_pkg#$pkgnum";
@@ -291,12 +324,11 @@ sub did_numbers_for_svc {
   my $self = shift;
   my $svc = shift;
   my @numbers;
-  my @possible_dids = qsearch({
+  my @dids = qsearch({
       'table'     => 'svc_phone',
-      'hashref'   => { 'forwarddst' => $svc->phonenum },
-      'order_by'  => ' ORDER BY phonenum'
+      'hashref'   => { 'forward_svcnum' => $svc->svcnum }
   });
-  foreach my $did (@possible_dids) {
+  foreach my $did (@dids) {
     # only include them if they're interesting to this export
     if ( $self->svc_role($did) eq 'did' ) {
       my $phonenum;
@@ -308,7 +340,7 @@ sub did_numbers_for_svc {
         $phonenum = Number::Phone->new($country, $did->phonenum);
       }
       if (!$phonenum) {
-        croak "Can't process phonenum ".$did->countrycode . $did->phonenum;
+        die "Can't process phonenum ".$did->countrycode . $did->phonenum . "\n";
       }
       push @numbers,
         { 'cc' => $phonenum->country_code,
@@ -325,7 +357,7 @@ sub insert_subscriber {
   my $svc = shift;
 
   my $cust = $self->find_or_create_customer($svc);
-  my $svcid = "svc_phone#" . $svc->svcnum;
+  my $svcid = "svc#" . $svc->svcnum;
   my $status = $svc->cust_svc->cust_pkg->susp ? 'locked' : 'active';
   my $domain = $self->find_or_create_domain($svc->domain);
 
@@ -336,14 +368,13 @@ sub insert_subscriber {
     {
       'alias_numbers'   => \@numbers,
       'customer_id'     => $cust->{id},
-      'display_name'    => $svc->phone_name,
+      'display_name'    => $svc->finger,
       'domain_id'       => $domain->{id},
-      'email'           => $svc->email,
       'external_id'     => $svcid,
-      'password'        => $svc->sip_password,
+      'password'        => $svc->_password,
       'primary_number'  => $first_number,
       'status'          => $status,
-      'username'        => $svc->phonenum,
+      'username'        => $svc->username,
     }
   );
 }
@@ -351,8 +382,8 @@ sub insert_subscriber {
 sub replace_subscriber {
   my $self = shift;
   my $svc = shift;
-  my $old = shift;
-  my $svcid = "svc_phone#" . $svc->svcnum;
+  my $old = shift || $svc->replace_old;
+  my $svcid = "svc#" . $svc->svcnum;
 
   my $cust = $self->find_or_create_customer($svc);
   my $status = $svc->cust_svc->cust_pkg->susp ? 'locked' : 'active';
@@ -365,7 +396,7 @@ sub replace_subscriber {
 
   if ( $subscriber ) {
     my $id = $subscriber->{id};
-    if ( $svc->phonenum ne $old->phonenum ) {
+    if ( $svc->username ne $old->username ) {
       # have to delete and recreate
       $self->api_delete("subscribers/$id");
       $self->insert_subscriber($svc);
@@ -374,14 +405,14 @@ sub replace_subscriber {
         {
           'alias_numbers'   => \@numbers,
           'customer_id'     => $cust->{id},
-          'display_name'    => $svc->phone_name,
+          'display_name'    => $svc->finger,
           'domain_id'       => $domain->{id},
           'email'           => $svc->email,
           'external_id'     => $svcid,
-          'password'        => $svc->sip_password,
+          'password'        => $svc->_password,
           'primary_number'  => $first_number,
           'status'          => $status,
-          'username'        => $svc->phonenum,
+          'username'        => $svc->username,
         }
       );
     }
@@ -394,7 +425,7 @@ sub replace_subscriber {
 sub delete_subscriber {
   my $self = shift;
   my $svc = shift;
-  my $svcid = "svc_phone#" . $svc->svcnum;
+  my $svcid = "svc#" . $svc->svcnum;
   my $pkgnum = $svc->cust_svc->pkgnum;
   my $custid = "cust_pkg#$pkgnum";
 
@@ -490,7 +521,7 @@ sub api_create {
   if ( $result->{location} ) {
     return $self->api_request('GET', $result->{location});
   } else {
-    croak $result->{message};
+    die $result->{message} . "\n";
   }
 }
 
@@ -508,7 +539,7 @@ sub api_update {
   my ($endpoint, $content) = @_;
   my $result = $self->api_request('PUT', $endpoint, $content);
   if ( $result->{message} ) {
-    croak $result->{message};
+    die $result->{message} . "\n";
   }
   return;
 }
@@ -529,7 +560,7 @@ sub api_delete {
     warn "$me api_delete $endpoint: does not exist\n";
     return;
   } elsif ( $result->{message} ) {
-    croak $result->{message};
+    die $result->{message} . "\n";
   }
   return;
 }
@@ -591,7 +622,7 @@ sub api_request {
     if ( $@ ) {
       # then it can't be parsed; probably a low-level error of some kind.
       warn "$me Parse error.\n".$response->content."\n\n";
-      croak $response->content;
+      die "$me Parse error:".$response->content . "\n";
     }
   }
   if ( $response->header('Location') ) {
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index c2f5d71..67fce41 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -847,6 +847,17 @@ sub delete {
     }
   }
 
+  foreach my $svc_phone (
+    qsearch( 'svc_phone', { 'forward_svcnum' => $self->svcnum })
+  ) {
+    $svc_phone->set('forward_svcnum', '');
+    my $error = $svc_phone->replace;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
   my $error = $self->delete_password_history
            || $self->SUPER::delete; # usergroup here
   if ( $error ) {
diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm
index cf9d9b4..3a58b46 100644
--- a/FS/FS/svc_phone.pm
+++ b/FS/FS/svc_phone.pm
@@ -93,9 +93,14 @@ Voicemail PIN
 
 Optional svcnum from svc_pbx
 
+=item forward_svcnum
+
+Forward destination, if it's another service. Some exports use this
+configuration.
+
 =item forwarddst
 
-Forwarding destination
+Forwarding destination, if it's not a service.
 
 =item email
 
@@ -225,6 +230,9 @@ sub table_info {
 	'forwarddst' => {	label => 'Forward Destination', 
 				%dis2,
 			},
+        'forward_svcnum' => {   label => 'Route to service',
+                                %dis2,
+                            },
 	'email' => {		label => 'Email',
 				%dis2,
 		    },
@@ -529,7 +537,9 @@ sub check {
     || $self->ut_alphan('sms_account')
     || $self->ut_numbern('max_simultaneous')
     || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
-    || $self->ut_numbern('forwarddst')
+    || $self->ut_numbern('forward_svcnum')
+    || $self->ut_foreign_keyn('forward_svcnum', 'cust_svc', 'svcnum')
+    || $self->ut_textn('forwarddst')
     || $self->ut_textn('email')
     || $self->ut_numbern('lrn')
     || $self->ut_numbern('lnp_desired_due_date')
diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi
index f1471e2..e74ffbb 100644
--- a/httemplate/edit/svc_phone.cgi
+++ b/httemplate/edit/svc_phone.cgi
@@ -60,6 +60,9 @@ my $begin_callback = sub {
                 type  => 'text',
                 maxlength => $conf->config('svc_phone-phone_name-max_length'),
               },
+              { field => 'forward_svcnum',
+                type  => 'select-svc_phone-forward',
+              },
 	      'forwarddst',
 	      'email',
 
diff --git a/httemplate/elements/tr-select-svc_phone-forward.html b/httemplate/elements/tr-select-svc_phone-forward.html
new file mode 100644
index 0000000..ef9ef17
--- /dev/null
+++ b/httemplate/elements/tr-select-svc_phone-forward.html
@@ -0,0 +1,50 @@
+% if ( $hide ) {
+  <tr style="display: none"><td>
+  <INPUT TYPE="hidden" NAME="<% $opt{field} %>" VALUE="<% $opt{curr_value}%>">
+  </td></tr>
+% } else {
+  <& tr-select-table.html,
+    'table'       => 'svc_acct', # for now
+    'name_col'    => 'email',
+    'order_by'    => 'order by username',
+    'empty_label' => ' ',
+    %select_hash,
+    %opt
+  &>
+% } 
+<%init>
+
+my %opt = @_;
+my $pkgnum = $opt{pkgnum};
+my $svcpart = $opt{svcpart};
+
+my $field = $opt{'field'} ||= 'forward_svcnum';
+
+my $part_svc = FS::part_svc->by_key($svcpart);
+# kludgey assumptions for now:
+# - this is only used to route DIDs to their real destinations
+# - which is a svc_acct
+# - in the same package (part_export::svc_with_role assumes this)
+# - and shares an export
+
+my $cust_pkg = FS::cust_pkg->by_key($pkgnum);
+my @svcparts;
+foreach my $part_export ( $part_svc->part_export ) {
+  foreach my $export_svc ( $part_export->export_svc ) {
+    push @svcparts, $export_svc->svcpart;
+  }
+}
+
+$pkgnum =~ /^(\d+)$/ or die "bad pkgnum $pkgnum";
+
+my %select_hash = (
+  'addl_from' => ' JOIN cust_svc USING (svcnum) ',
+  'extra_sql' => "WHERE pkgnum = $pkgnum AND svcpart IN(".
+    join(',', @svcparts) . ")"
+);
+
+my $hide = 0;
+$hide = 1 if $part_svc->part_svc_column($field) eq 'F';
+$hide = 1 if !@svcparts;
+
+</%init>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
index ab69c4f..416f138 100644
--- a/httemplate/view/svc_phone.cgi
+++ b/httemplate/view/svc_phone.cgi
@@ -18,6 +18,23 @@ my %labels = map { $_ =>  ( ref($fields->{$_})
 
 my @fields = qw( countrycode phonenum sim_imsi );
 push @fields, 'domain' if $conf->exists('svc_phone-domain');
+
+$labels{forward_svcnum} = mt('Route to service');
+push @fields, { field => 'forward_svcnum',
+                link => [ $p.'view/cust_svc.cgi?', 'forward_svcnum' ],
+                value_callback => sub {
+                  my $self = shift;
+                  if ($self->forward_svcnum) {
+                    my $cust_svc = FS::cust_svc->by_key($self->forward_svcnum);
+                    if ( $cust_svc ) {
+                      return $cust_svc->svc_x->label;
+                    }
+                  }
+                  '';
+                },
+              };
+
+
 push @fields, qw( pbx_title );
 $labels{pbx_title} = 'PBX';
 

commit 808fdb0f9c2b3468cda54cc92a945a1fcd9ee7f4
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Feb 25 16:30:36 2016 -0800

    don't enforce service dependencies that have been disabled, #33685

diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
index 13e2e0f..d432747 100644
--- a/FS/FS/cust_svc.pm
+++ b/FS/FS/cust_svc.pm
@@ -601,7 +601,11 @@ link_type.
 sub part_svc_link {
   my $self = shift;
   my $agentnum = $self->pkgnum ? $self->cust_pkg->cust_main->agentnum : '';
-  FS::part_svc_link->by_agentnum($agentnum, src_svcpart=>$self->svcpart, @_);
+  FS::part_svc_link->by_agentnum($agentnum,
+    src_svcpart=>$self->svcpart,
+    disabled   => '',
+    @_
+  );
 }
 
 =item display_svcnum 

commit fa97f29ea43276a6f2636e1f251c775c7fa9e67c
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Feb 25 15:16:35 2016 -0800

    allow records with password history to be deleted, from #32456

diff --git a/FS/FS/Password_Mixin.pm b/FS/FS/Password_Mixin.pm
index 3fb787c..ac7ba50 100644
--- a/FS/FS/Password_Mixin.pm
+++ b/FS/FS/Password_Mixin.pm
@@ -215,6 +215,26 @@ sub insert_password_history {
 
 }
 
+=item delete_password_history;
+
+Removes all password history records attached to this object, in preparation
+to delete the object.
+
+=cut
+
+sub delete_password_history {
+  my $self = shift;
+  my @records = qsearch('password_history', {
+      $self->password_history_key => $self->get($self->primary_key)
+  });
+  my $error = '';
+  foreach (@records) {
+    $error ||= $_->delete;
+  }
+  return $error . ' (clearing password history)' if $error;
+  '';
+}
+
 =item _blowfishcrypt PASSWORD
 
 For internal use: takes PASSWORD and returns a new
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index 72f2320..3b36e46 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -161,7 +161,8 @@ sub delete {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $error = $self->SUPER::delete(@_);
+  my $error = $self->delete_password_history
+           || $self->SUPER::delete(@_);
 
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm
index a824b8e..c1b558d 100644
--- a/FS/FS/contact.pm
+++ b/FS/FS/contact.pm
@@ -378,7 +378,8 @@ sub delete {
     }
   }
 
-  my $error = $self->SUPER::delete;
+  my $error = $self->delete_password_history
+           || $self->SUPER::delete;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index 6bb67af..c2f5d71 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -847,7 +847,8 @@ sub delete {
     }
   }
 
-  my $error = $self->SUPER::delete; # usergroup here
+  my $error = $self->delete_password_history
+           || $self->SUPER::delete; # usergroup here
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;

commit 50f247ce6da7497428ce772d3c37c6cba3b9c736
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Feb 25 13:39:17 2016 -0800

    fix UI nit

diff --git a/httemplate/elements/tr-part_pkg-taxproducts.html b/httemplate/elements/tr-part_pkg-taxproducts.html
index ad464ca..c57232c 100644
--- a/httemplate/elements/tr-part_pkg-taxproducts.html
+++ b/httemplate/elements/tr-part_pkg-taxproducts.html
@@ -24,7 +24,7 @@ my $pkgpart = delete($opt{pkgpart});
 my %pkg_options;
 if ($pkgpart) {
   my $part_pkg = FS::part_pkg->by_key($pkgpart);
-  my %pkg_options = $part_pkg->options;
+  %pkg_options = $part_pkg->options;
   $pkg_options{'usage_taxproductnum_'} = $part_pkg->taxproductnum;
 }
 

commit 2e49e11b18901a0d0cdace8d0dd8b06a5ac25262
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Feb 25 13:39:07 2016 -0800

    optionally calculate Suretax taxes based only on the service location, #32043

diff --git a/FS/FS/TaxEngine/suretax.pm b/FS/FS/TaxEngine/suretax.pm
index 073d19b..2c418f9 100644
--- a/FS/FS/TaxEngine/suretax.pm
+++ b/FS/FS/TaxEngine/suretax.pm
@@ -137,8 +137,17 @@ sub build_item {
   my @items;
   my $recur_without_usage = $cust_bill_pkg->recur;
 
+  # use the _configured_ tax location as 'Zipcode' (respecting 
+  # tax-ship_address and tax-pkg_address configs)
   my $location = $cust_bill_pkg->tax_location;
-  my ($svc_zip, $svc_plus4) = split('-', $location->zip);
+  my ($zip, $plus4) = split('-', $location->zip);
+
+  # and the _real_ location as 'P2PZipcode'
+  my $svc_location = $location;
+  if ( $cust_bill_pkg->pkgnum ) {
+    $svc_location = $cust_bill_pkg->cust_pkg->cust_location;
+  }
+  my ($svc_zip, $svc_plus4) = split('-', $svc_location->zip);
 
   my $startdate =
     DateTime->from_epoch( epoch => $cust_bill->_date )->strftime('%m-%d-%Y');
@@ -150,8 +159,8 @@ sub build_item {
     'OrigNumber'      => '',
     'TermNumber'      => '',
     'BillToNumber'    => '',
-    'Zipcode'         => $self->{bill_zip},
-    'Plus4'           => ($self->{bill_plus4} ||= '0000'),
+    'Zipcode'         => $zip,
+    'Plus4'           => ($plus4 ||= '0000'),
     'P2PZipcode'      => $svc_zip,
     'P2PPlus4'        => ($svc_plus4 ||= '0000'),
     # we don't support Order Placement/Approval zip codes
@@ -204,14 +213,6 @@ sub build_item {
     while ( my $cdr = $cdrs->fetch ) {
       my $calldate =
         DateTime->from_epoch( epoch => $cdr->startdate )->strftime('%m-%d-%Y');
-      # determine the tax situs rule; it's different (probably more accurate) 
-      # if the call has PSTN phone numbers at both ends
-      my $tsr = $TSR_CALL_OTHER;
-      if ( $cdr->charged_party =~ /^\d{10}$/ and
-           $cdr->src           =~ /^\d{10}$/ and
-           $cdr->dst           =~ /^\d{10}$/ ) {
-        $tsr = $TSR_CALL_NPANXX;
-      }
       my %hash = (
         %base_item,
         'LineNumber'      => 'C' . $cdr->acctid,
@@ -222,9 +223,21 @@ sub build_item {
         'Revenue'         => $cdr->rated_price, # 4 decimal places
         'Units'           => 0, # right?
         'CallDuration'    => $cdr->duration,
-        'TaxSitusRule'    => $tsr,
+        'TaxSitusRule'    => $TSR_CALL_OTHER,
         'TransTypeCode'   => $taxproduct,
       );
+      # determine the tax situs rule; it's different (probably more accurate) 
+      # if the call has PSTN phone numbers at both ends
+      if ( $cdr->charged_party =~ /^\d{10}$/ and
+           $cdr->src           =~ /^\d{10}$/ and
+           $cdr->dst           =~ /^\d{10}$/ and
+           !$cdr->is_tollfree ) {
+        $hash{TaxSitusRule} = $TSR_CALL_NPANXX;
+        $hash{OrigNumber}   = $cdr->src;
+        $hash{TermNumber}   = $cdr->dst;
+        $hash{BillToNumber} = $cdr->charged_party;
+      }
+
       push @items, \%hash;
 
     } # while ($cdrs->fetch)

commit 84e973ebe4c82881c62b71f996ec9ca124a5dce5
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Feb 25 12:44:14 2016 -0800

    fix cust_paydate_within event condition vs. cust_payby, #23741

diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 206f1b0..be02f9c 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -28,6 +28,7 @@ use Date::Format;
 #use Date::Manip;
 use File::Temp; #qw( tempfile );
 use Business::CreditCard 0.28;
+use List::Util qw(min);
 use FS::UID qw( dbh driver_name );
 use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
 use FS::Cursor;
@@ -2701,64 +2702,34 @@ sub payment_info {
 
 =item paydate_epoch
 
-Returns the exact time in seconds corresponding to the payment method 
-expiration date.  For CARD/DCRD customers this is the end of the month;
-for others (COMP is the only other payby that uses paydate) it's the start.
-Returns 0 if the paydate is empty or set to the far future.
+Returns the next payment expiration date for this customer. If they have no
+payment methods that will expire, returns 0.
 
 =cut
 
-#XXX i need to be updated for 4.x+
 sub paydate_epoch {
   my $self = shift;
-  my ($month, $year) = $self->paydate_monthyear;
-  return 0 if !$year or $year >= 2037;
-  if ( $self->payby eq 'CARD' or $self->payby eq 'DCRD' ) {
-    $month++;
-    if ( $month == 13 ) {
-      $month = 1;
-      $year++;
-    }
-    return timelocal(0,0,0,1,$month-1,$year) - 1;
-  }
-  else {
-    return timelocal(0,0,0,1,$month-1,$year);
-  }
+  # filter out the ones that individually return 0, but then return 0 if
+  # there are no results
+  my @epochs = grep { $_ > 0 } map { $_->paydate_epoch } $self->cust_payby;
+  min( @epochs ) || 0;
 }
 
 =item paydate_epoch_sql
 
-Class method.  Returns an SQL expression to obtain the payment expiration date
-as a number of seconds.
+Returns an SQL expression to get the next payment expiration date for a
+customer. Returns 2143260000 (2037-12-01) if there are no payment expiration
+dates, so that it's safe to test for "will it expire before date X" for any
+date up to then.
 
 =cut
 
-# XXX i need to be updated for 4.x+
-# Special expiration date behavior for non-CARD/DCRD customers has been 
-# carefully preserved.  Do we really use that?
 sub paydate_epoch_sql {
   my $class = shift;
-  my $table = shift || 'cust_main';
-  my ($case1, $case2);
-  if ( driver_name eq 'Pg' ) {
-    $case1 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) + INTERVAL '1 month') - 1";
-    $case2 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) )";
-  }
-  elsif ( lc(driver_name) eq 'mysql' ) {
-    $case1 = "UNIX_TIMESTAMP( DATE_ADD( CAST( $table.paydate AS DATETIME ), INTERVAL 1 month ) ) - 1";
-    $case2 = "UNIX_TIMESTAMP( CAST( $table.paydate AS DATETIME ) )";
-  }
-  else { return '' }
-  return "CASE WHEN $table.payby IN('CARD','DCRD') 
-  THEN ($case1)
-  ELSE ($case2)
-  END"
+  my $paydate = FS::cust_payby->paydate_epoch_sql;
+  "(SELECT COALESCE(MIN($paydate), 2143260000) FROM cust_payby WHERE cust_payby.custnum = cust_main.custnum)";
 }
 
-=item tax_exemption TAXNAME
-
-=cut
-
 sub tax_exemption {
   my( $self, $taxname ) = @_;
 
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index 56efbc4..4176818 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -4,6 +4,8 @@ use strict;
 use Business::CreditCard;
 use FS::payby;
 use FS::Record qw(qsearch);
+use FS::UID qw(driver_name);
+use Time::Local qw(timelocal);
 
 use vars qw($ignore_masked_payinfo);
 
@@ -348,6 +350,60 @@ sub paydate_monthyear {
   }
 }
 
+=item paydate_epoch
+
+Returns the exact time in seconds corresponding to the payment method 
+expiration date.  For CARD/DCRD customers this is the end of the month;
+for others (COMP is the only other payby that uses paydate) it's the start.
+Returns 0 if the paydate is empty or set to the far future.
+
+=cut
+
+sub paydate_epoch {
+  my $self = shift;
+  my ($month, $year) = $self->paydate_monthyear;
+  return 0 if !$year or $year >= 2037;
+  if ( $self->payby eq 'CARD' or $self->payby eq 'DCRD' ) {
+    $month++;
+    if ( $month == 13 ) {
+      $month = 1;
+      $year++;
+    }
+    return timelocal(0,0,0,1,$month-1,$year) - 1;
+  }
+  else {
+    return timelocal(0,0,0,1,$month-1,$year);
+  }
+}
+
+=item paydate_epoch_sql
+
+Class method.  Returns an SQL expression to obtain the payment expiration date
+as a number of seconds.
+
+=cut
+
+# Special expiration date behavior for non-CARD/DCRD customers has been 
+# carefully preserved.  Do we really use that?
+sub paydate_epoch_sql {
+  my $class = shift;
+  my $table = $class->table;
+  my ($case1, $case2);
+  if ( driver_name eq 'Pg' ) {
+    $case1 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) + INTERVAL '1 month') - 1";
+    $case2 = "EXTRACT( EPOCH FROM CAST( $table.paydate AS TIMESTAMP ) )";
+  }
+  elsif ( lc(driver_name) eq 'mysql' ) {
+    $case1 = "UNIX_TIMESTAMP( DATE_ADD( CAST( $table.paydate AS DATETIME ), INTERVAL 1 month ) ) - 1";
+    $case2 = "UNIX_TIMESTAMP( CAST( $table.paydate AS DATETIME ) )";
+  }
+  else { return '' }
+  return "CASE WHEN $table.payby IN('CARD','DCRD') 
+  THEN ($case1)
+  ELSE ($case2)
+  END"
+}
+
 =back
 
 =head1 BUGS

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Password_Mixin.pm                            |   20 +++
 FS/FS/Schema.pm                                    |    1 +
 FS/FS/TaxEngine/suretax.pm                         |   37 +++--
 FS/FS/access_user.pm                               |    3 +-
 FS/FS/contact.pm                                   |    3 +-
 FS/FS/cust_main.pm                                 |   55 ++------
 FS/FS/cust_svc.pm                                  |    6 +-
 FS/FS/part_export/sipwise.pm                       |  143 ++++++++++++--------
 FS/FS/payinfo_Mixin.pm                             |   56 ++++++++
 FS/FS/svc_acct.pm                                  |   14 +-
 FS/FS/svc_phone.pm                                 |   14 +-
 httemplate/edit/svc_phone.cgi                      |    3 +
 httemplate/elements/tr-part_pkg-taxproducts.html   |    2 +-
 .../elements/tr-select-svc_phone-forward.html      |   50 +++++++
 httemplate/view/svc_phone.cgi                      |   17 +++
 15 files changed, 307 insertions(+), 117 deletions(-)
 create mode 100644 httemplate/elements/tr-select-svc_phone-forward.html




More information about the freeside-commits mailing list