Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Wed, 28 Jan 2015 10:26:52 +0000 (02:26 -0800)
committerIvan Kohler <ivan@freeside.biz>
Wed, 28 Jan 2015 10:26:52 +0000 (02:26 -0800)
20 files changed:
FS/FS/Conf.pm
FS/FS/Cron/agent_email.pm
FS/FS/Cron/upload.pm
FS/FS/Template_Mixin.pm
FS/FS/Upgrade.pm
FS/FS/cust_bill.pm
FS/FS/cust_credit.pm
FS/FS/cust_main.pm
FS/FS/cust_main/Billing_Realtime.pm
FS/FS/cust_pay.pm
FS/FS/cust_pkg.pm
FS/FS/discount.pm
FS/FS/msg_template.pm
FS/FS/part_export/send_email.pm
FS/FS/pay_batch.pm
FS/FS/quotation.pm
FS/FS/upload_target.pm
httemplate/edit/part_pkg.cgi
httemplate/edit/process/bulk-svc_phone.html
httemplate/misc/email-customers.html

index f9a1696..239e304 100644 (file)
@@ -1233,9 +1233,24 @@ sub reason_type_options {
   {
     'key'         => 'invoice_from',
     'section'     => 'required',
-    'description' => 'Return address on email invoices',
+    'description' => 'Return address on email invoices (address only, see invoice_from_name)',
     'type'        => 'text',
     'per_agent'   => 1,
+    'validate'    => sub { $_[0] =~
+                             /^[^@]+\@[[:alnum:]-]+(\.[[:alnum:]-]+)+$/
+                             ? '' : 'Invalid email address';
+                         }
+  },
+
+  {
+    'key'         => 'invoice_from_name',
+    'section'     => 'invoicing',
+    'description' => 'Return name on email invoices (set address in invoice_from)',
+    'type'        => 'text',
+    'per_agent'   => 1,
+    'validate'    => sub { (($_[0] =~ /[^[:alnum:][:space:]]/) && ($_[0] !~ /^\".*\"$/))
+                           ? 'Invalid name.  Use quotation marks around names that contain punctuation.'
+                           : '' }
   },
 
   {
index 992aa35..623b920 100644 (file)
@@ -29,7 +29,10 @@ sub agent_email {
     RT::ConnectToDatabase();
   }
 
-  my $from = $conf->config('invoice_from');
+  my $from = $conf->config('invoice_from_name') ?
+             $conf->config('invoice_from_name') . ' <' . 
+             $conf->config('invoice_from') . '>' :
+             $conf->config('invoice_from');
 
   my $outbuf = '';;
   my( $fs_interp, $rt_interp ) = mason_interps('standalone', 'outbuf'=>\$outbuf);
index 03ed366..fa1762f 100644 (file)
@@ -508,7 +508,10 @@ sub prepare_report {
 
   (
     to      => $to,
-    from    => $conf->config('invoice_from', $agentnum),
+    from    => $conf->config('invoice_from_name', $agentnum) ?
+               $conf->config('invoice_from_name', $agentnum) . ' <' . 
+               $conf->config('invoice_from', $agentnum) . '>' :
+               $conf->config('invoice_from', $agentnum),
     subject => $subject,
     body    => $body,
   );
index 1fed7f1..9669ac2 100644 (file)
@@ -347,8 +347,8 @@ sub print_generic {
 
   if ( $format eq 'latex' && grep { /^%%Detail/ } @invoice_template ) {
     #change this to a die when the old code is removed
-    # it's been almost ten years, changing it to a die on the next release.
-    warn "old-style invoice template $templatefile; ".
+    # it's been almost ten years, changing it to a die
+    die "old-style invoice template $templatefile; ".
          "patch with conf/invoice_latex.diff or use new conf/invoice_latex*\n";
          #$old_latex = 'true';
          #@invoice_template = _translate_old_latex_format(@invoice_template);
@@ -1172,6 +1172,12 @@ sub print_generic {
            join(', ', map "$_=>".$line_item->{$_}, keys %$line_item). "\n"
         if $DEBUG > 1;
 
+      push @buf, ( [ $line_item->{'description'},
+                     $money_char. sprintf("%10.2f", $line_item->{'amount'}),
+                   ],
+                   map { [ " ". $_, '' ] } @{$line_item->{'ext_description'}},
+                 );
+
       $line_item->{'ref'} = $line_item->{'pkgnum'};
       $line_item->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; # mt()?
       $line_item->{'section'} = $section;
@@ -1184,11 +1190,6 @@ sub print_generic {
       $line_item->{'ext_description'} ||= [];
  
       push @detail_items, $line_item;
-      push @buf, ( [ $line_item->{'description'},
-                     $money_char. sprintf("%10.2f", $line_item->{'amount'}),
-                   ],
-                   map { [ " ". $_, '' ] } @{$line_item->{'ext_description'}},
-                 );
     }
 
     if ( $section->{'description'} ) {
@@ -3448,8 +3449,29 @@ sub _items_cust_bill_pkg {
                 ext_description => \@ext,
               };
               foreach my $cust_bill_pkg_discount (@discounts) {
-                my $def = $cust_bill_pkg_discount->cust_pkg_discount->discount;
-                push @ext, &{$escape_function}( $def->description );
+                my $discount = $cust_bill_pkg_discount->cust_pkg_discount->discount;
+                my $discount_desc = $discount->description_short;
+
+                if ($discount->months) {
+
+                  # calculate months remaining after this invoice
+                  my $used = FS::Record->scalar_sql(
+                    'SELECT SUM(months) FROM cust_bill_pkg_discount
+                      JOIN cust_bill_pkg USING (billpkgnum)
+                      JOIN cust_bill USING (invnum)
+                      WHERE pkgdiscountnum = ? AND _date <= ?',
+                    $cust_bill_pkg_discount->pkgdiscountnum,
+                    $self->_date
+                  );
+                  $used ||= 0;
+                  my $remaining = sprintf('%.2f', $discount->months - $used);
+                  # append "for X months (Y months remaining)"
+                  $discount_desc .= $self->mt(' for [quant,_1,month] ([quant,_2,month] remaining)',
+                    $cust_bill_pkg_discount->months,
+                    $remaining
+                  );
+                } # else it's not time-limited
+                push @ext, &{$escape_function}($discount_desc);
               }
             }
 
index 381fd5f..5d092ed 100644 (file)
@@ -46,6 +46,22 @@ sub upgrade_config {
 
   my $conf = new FS::Conf;
 
+  if ($conf->config('invoice_from') =~ /\<(.*)\>/) {
+    my $realemail = $1;
+    $realemail =~ s/^\s*//; # remove leading spaces
+    $realemail =~ s/\s*$//; # remove trailing spaces
+    my $realname = $conf->config('invoice_from');
+    $realname =~ s/\<.*\>//; # remove email address
+    $realname =~ s/^\s*//; # remove leading spaces
+    $realname =~ s/\s*$//; # remove trailing spaces
+    # properly quote names that contain punctuation
+    if (($realname =~ /[^[:alnum:][:space:]]/) && ($realname !~ /^\".*\"$/)) {
+      $realname = '"' . $realname . '"';
+    }
+    $conf->set('invoice_from_name', $realname);
+    $conf->set('invoice_from', $realemail);
+  }
+
   $conf->touch('payment_receipt')
     if $conf->exists('payment_receipt_email')
     || $conf->config('payment_receipt_msgnum');
index bcfbbc7..6ded57f 100644 (file)
@@ -1085,6 +1085,9 @@ sub email {
 
   # this is where we set the From: address
   $from ||= $self->_agent_invoice_from ||    #XXX should go away
+            $conf->config('invoice_from_name', $self->cust_main->agentnum ) ?
+            $conf->config('invoice_from_name', $self->cust_main->agentnum ) . ' <' .
+            $conf->config('invoice_from', $self->cust_main->agentnum ) . '>' :
             $conf->config('invoice_from', $self->cust_main->agentnum );
 
   my @invoicing_list = $self->cust_main->invoicing_list_emailonly;
index df2a6cc..6f4f720 100644 (file)
@@ -282,7 +282,10 @@ sub delete {
     my $cust_main = $self->cust_main;
 
     my $error = send_email(
-      'from'    => $conf->config('invoice_from', $self->cust_main->agentnum),
+      'from'    => $conf->config('invoice_from_name', $self->cust_main->agentnum) ?
+                   $conf->config('invoice_from_name', $self->cust_main->agentnum) . ' <' .
+                   $conf->config('invoice_from', $self->cust_main->agentnum) . '>' :
+                   $conf->config('invoice_from', $self->cust_main->agentnum),
                                  #invoice_from??? well as good as any
       'to'      => $conf->config('deletecredits'),
       'subject' => 'FREESIDE NOTIFICATION: Credit deleted',
index 30dbc04..1ed1d4a 100644 (file)
@@ -4327,7 +4327,10 @@ sub notify {
 
   return unless $conf->exists($template);
 
-  my $from = $conf->config('invoice_from', $self->agentnum)
+  my $from = $conf->config('invoice_from_name', $self->agentnum) ?
+             $conf->config('invoice_from_name', $self->agentnum) . ' <' .
+             $conf->config('invoice_from', $self->agentnum) . '>' :
+             $conf->config('invoice_from', $self->agentnum)
     if $conf->exists('invoice_from', $self->agentnum);
   $from = $options{from} if exists($options{from});
 
index 330a454..f9f3754 100644 (file)
@@ -1109,7 +1109,10 @@ sub _realtime_bop_result {
         };
 
         my $error = send_email(
-          'from'    => $conf->config('invoice_from', $self->agentnum ),
+          'from'    => $conf->config('invoice_from_name', $self->agentnum ) ?
+                       $conf->config('invoice_from_name', $self->agentnum ) . ' <' .
+                       $conf->config('invoice_from', $self->agentnum ) . '>' :
+                       $conf->config('invoice_from', $self->agentnum ),
           'to'      => [ grep { $_ ne 'POST' } $self->invoicing_list ],
           'subject' => 'Your payment could not be processed',
           'body'    => [ $template->fill_in(HASH => $templ_hash) ],
index df567a5..e44278d 100644 (file)
@@ -694,7 +694,10 @@ sub send_receipt {
         'msgtype' => 'receipt',
       };
       $error = $queue->insert(
-        'from'    => $conf->config('invoice_from', $cust_main->agentnum),
+        'from'    => $conf->config('invoice_from_name', $cust_main->agentnum ) ?
+                     $conf->config('invoice_from_name', $cust_main->agentnum ) . ' <' .
+                     $conf->config('invoice_from', $cust_main->agentnum ) . '>' :
+                     $conf->config('invoice_from', $cust_main->agentnum ),
                                    #invoice_from??? well as good as any
         'to'      => \@invoicing_list,
         'subject' => 'Payment receipt',
index 5c82ad2..ccf63db 100644 (file)
@@ -947,7 +947,10 @@ sub cancel {
     }
     else {
       $error = send_email(
-        'from'    => $conf->config('invoice_from', $self->cust_main->agentnum),
+        'from'    => $conf->config('invoice_from_name', $self->cust_main->agentnum) ?
+                     $conf->config('invoice_from_name', $self->cust_main->agentnum) . ' <' .
+                     $conf->config('invoice_from', $self->cust_main->agentnum) . '>' :
+                     $conf->config('invoice_from', $self->cust_main->agentnum),
         'to'      => \@invoicing_list,
         'subject' => ( $conf->config('cancelsubject') || 'Cancellation Notice' ),
         'body'    => [ map "$_\n", $conf->config('cancelmessage') ],
index 0561f9c..361e0b4 100644 (file)
@@ -173,7 +173,12 @@ sub description_short {
   my $conf = new FS::Conf;
   my $money_char = $conf->config('money_char') || '$';  
 
-  my $desc = $self->name ? $self->name.': ' : '';
+  my $desc;
+  if ( $self->name ) {
+    $desc = $self->name . ': ';
+  } else {
+    $desc = 'Discount of ';
+  }
   $desc .= $money_char. sprintf('%.2f/month', $self->amount)
     if $self->amount > 0;
 
index 65890e1..70a8e49 100644 (file)
@@ -398,8 +398,10 @@ sub prepare {
       $from_addr = scalar( $conf->config($opt{'from_config'}, 
                                          $cust_main->agentnum) );
     }
-    $from_addr ||= scalar( $conf->config('invoice_from',
-                                         $cust_main->agentnum) );
+    $from_addr ||= $conf->config('invoice_from_name', $cust_main->agentnum) ?
+                   $conf->config('invoice_from_name', $cust_main->agentnum) . ' <' .
+                   $conf->config('invoice_from', $cust_main->agentnum) . '>' :
+                   $conf->config('invoice_from', $cust_main->agentnum);
   }
 #  my @cust_msg = ();
 #  if ( $conf->exists('log_sent_mail') and !$opt{'preview'} ) {
index 537a562..9afc254 100644 (file)
@@ -80,7 +80,7 @@ tie my %options, 'Tie::IxHash', (
 );
 
 %info = (
-  'svc'      => [qw( svc_acct svc_broadband svc_phone svc_domain )],
+  'svc'      => [qw( svc_acct svc_hardware svc_broadband svc_phone svc_domain )],
   'desc'     =>
   'Send an email message',
   'options'  => \%options,
index b6b69f3..3079db1 100644 (file)
@@ -749,7 +749,10 @@ sub import_from_gateway {
       my $body = "Import from gateway ".$gateway->label."\n".$error_text;
       send_email(
         to      => $mail_on_error,
-        from    => $conf->config('invoice_from'),
+        from    => $conf->config('invoice_from_name') ?
+                   $conf->config('invoice_from_name') . ' <' .
+                   $conf->config('invoice_from') . '>' :
+                   $conf->config('invoice_from'),
         subject => $subject,
         body    => $body,
       );
index 75a592d..774495a 100644 (file)
@@ -183,8 +183,10 @@ sub email {
 
   # this is where we set the From: address
   $from ||= $conf->config('quotation_from', $self->cust_or_prospect->agentnum )
-         || $conf->config('invoice_from',   $self->cust_or_prospect->agentnum );
-
+        || ($conf->config('invoice_from_name', $self->cust_or_prospect->agentnum ) ?
+            $conf->config('invoice_from_name', $self->cust_or_prospect->agentnum ) . ' <' .
+            $conf->config('invoice_from', $self->cust_or_prospect->agentnum ) . '>' :
+            $conf->config('invoice_from', $self->cust_or_prospect->agentnum ));
   $self->SUPER::email( {
     'from' => $from,
     %$opt,
index 33088cb..73d2a04 100644 (file)
@@ -163,7 +163,10 @@ sub put {
     # (maybe use only the raw content, so that we don't have to supply a 
     # customer for substitutions? ewww.)
     my %message = (
-      'from'          => $conf->config('invoice_from'),
+      'from'          => $conf->config('invoice_from_name') ?
+                         $conf->config('invoice_from_name') . ' <' .
+                         $conf->config('invoice_from') . '>' :
+                         $conf->config('invoice_from'),
       'to'            => $to,
       'subject'       => $self->subject,
       'nobody'        => 1,
index c4db83a..1702a6d 100755 (executable)
@@ -959,7 +959,30 @@ my $html_bottom = sub {
     my @fields = exists($plans{$layer}->{'fieldorder'})
                    ? @{$plans{$layer}->{'fieldorder'}}
                    : keys %{ $href };
-  
+    
+    # hash of dependencies for each of the Pricing Plan fields.
+    # make sure NOT to use double-quotes inside the 'msg' value.
+    my $dependencies = {
+        'unused_credit_suspend' => {
+            'msg'       => q|You must set the 'suspend_credit_type' option in Configuration->Settings to gain access to this option.|,
+            'are_met'   => sub{
+                my $conf = new FS::conf;
+                my @conf_info = qsearch('conf', { 'name' => 'suspend_credit_type' } );
+                return 1 if (exists($conf_info[0]) && $conf_info[0]->{Hash}{value});
+                return 0;
+            }
+        },
+        'unused_credit_cancel' => {
+            'msg'       => q|You must set the 'cancel_credit_type' option in Configuration->Settings to gain access to this option.|,
+            'are_met'   => sub{
+                my $conf = new FS::conf;
+                my @conf_info = qsearch('conf', { 'name' => 'cancel_credit_type' } );
+                return 1 if (exists($conf_info[0]) && $conf_info[0]->{Hash}{value});
+                return 0;
+            }
+        }
+    };
+    
     foreach my $field ( grep $_ !~ /^(setup|recur)_fee$/, @fields ) {
   
       if(!exists($href->{$field})) {
@@ -981,7 +1004,10 @@ my $html_bottom = sub {
       #XXX these should use elements/ fields... (or this whole thing should
       #just use layer_fields instead of layer_callback)
   
-      if ( ! exists($href->{$field}{'type'}) ) {
+      if (exists($dependencies->{$field}) && !$dependencies->{$field}{'are_met'}()) {
+          $html .= q!<span title="!.$dependencies->{$field}{'msg'}.q!">N/A</span>!;
+          
+      } elsif ( ! exists($href->{$field}{'type'}) ) {
   
         $html .= qq!<INPUT TYPE="text" NAME="${layer}__$field" VALUE="!.
                  ( exists($options{$field})
index 5a1fbc6..db486de 100644 (file)
@@ -25,7 +25,7 @@ my $num_avail = $1;
 errorpage("There are only $num_avail available")
   if $end - $start + 1 > $num_avail;
 
-foreach my $phonenum ( $start .. $end ) {
+foreach my $phonenum ( "$start" .. "$end" ) {
 
   my $svc_phone = new FS::svc_phone {
     'phonenum' => $phonenum,
index 1592630..c74c15b 100644 (file)
@@ -103,7 +103,8 @@ Template:
     <& /elements/tr-td-label.html, 'label' => 'From:' &>
       <TD><& /elements/input-text.html,
               'field' => 'from_name',
-              'value' => $conf->config('company_name', $agent_virt_agentnum), #?
+              'value' => $conf->config('invoice_from_name', $agent_virt_agentnum) ||
+                         $conf->config('company_name', $agent_virt_agentnum), #?
               'size'  => 20,
           &>&nbsp;&lt;\
           <& /elements/input-text.html,