summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2015-01-18 18:13:16 -0800
committerIvan Kohler <ivan@freeside.biz>2015-01-18 18:13:16 -0800
commit03c12b4dabfcaabc218f39ee13557edebc13931d (patch)
tree0d915cf9e2bbf278a48500b5d125206ab78477a2
parent0b6bd6405107c1abf8f2c5e2bc1b569870a02309 (diff)
email quotations, RT#22232, RT#20688
-rw-r--r--FS/FS/Conf.pm36
-rw-r--r--FS/FS/Template_Mixin.pm365
-rw-r--r--FS/FS/cust_bill.pm375
-rw-r--r--FS/FS/cust_event.pm7
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_agent.pm4
-rw-r--r--FS/FS/quotation.pm47
-rw-r--r--httemplate/elements/footer-popup.html2
-rw-r--r--httemplate/misc/email-quotation.html71
-rwxr-xr-xhttemplate/misc/process/email-quotation.html20
-rwxr-xr-xhttemplate/view/quotation.html13
10 files changed, 582 insertions, 358 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 2a11e5e..e584f00 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -1239,6 +1239,15 @@ sub reason_type_options {
},
{
+ 'key' => 'quotation_from',
+ 'section' => '',
+ 'description' => 'Return address on email quotations',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+
+ {
'key' => 'invoice_subject',
'section' => 'invoicing',
'description' => 'Subject: header on email invoices. Defaults to "Invoice". The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',
@@ -1248,6 +1257,15 @@ sub reason_type_options {
},
{
+ 'key' => 'quotation_subject',
+ 'section' => '',
+ 'description' => 'Subject: header on email quotations. Defaults to "Quotation".', # The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',
+ 'type' => 'text',
+ #'per_agent' => 1,
+ 'per_locale' => 1,
+ },
+
+ {
'key' => 'invoice_usesummary',
'section' => 'invoicing',
'description' => 'Indicates that html and latex invoices should be in summary style and make use of invoice_latexsummary.',
@@ -1502,14 +1520,28 @@ and customer address. Include units.',
{
'key' => 'invoice_email_pdf',
'section' => 'invoicing',
- 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
+ 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the HTML invoice as the email body, unless invoice_email_pdf_note is set.',
+ 'type' => 'checkbox'
+ },
+
+ {
+ 'key' => 'quotation_email_pdf',
+ 'section' => '',
+ 'description' => 'Send PDF quotations as an attachment to emailed quotations. By default, includes the HTML quotation as the email body, unless quotation_email_pdf_note is set.',
'type' => 'checkbox'
},
{
'key' => 'invoice_email_pdf_note',
'section' => 'invoicing',
- 'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF invoices.',
+ 'description' => 'If defined, this text will replace the default HTML invoice as the body of emailed PDF invoices.',
+ 'type' => 'textarea'
+ },
+
+ {
+ 'key' => 'quotation_email_pdf_note',
+ 'section' => '',
+ 'description' => 'If defined, this text will replace the default HTML quotation as the body of emailed PDF quotations.',
'type' => 'textarea'
},
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index ebc977a..1fed7f1 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -16,6 +16,7 @@ use HTML::Entities;
use Locale::Country;
use Cwd;
use FS::UID;
+use FS::Misc qw( send_email );
use FS::Record qw( qsearch qsearchs );
use FS::Conf;
use FS::Misc qw( generate_ps generate_pdf );
@@ -1845,6 +1846,10 @@ sub _translate_old_latex_format {
(@template);
}
+=item terms
+
+=cut
+
sub terms {
my $self = shift;
my $conf = $self->conf;
@@ -1867,6 +1872,10 @@ sub terms {
$conf->config('invoice_default_terms', $agentnum) || '';
}
+=item due_date
+
+=cut
+
sub due_date {
my $self = shift;
my $duedate = '';
@@ -1876,11 +1885,19 @@ sub due_date {
$duedate;
}
+=item due_date2str
+
+=cut
+
sub due_date2str {
my $self = shift;
$self->due_date ? $self->time2str_local(shift, $self->due_date) : '';
}
+=item balance_due_msg
+
+=cut
+
sub balance_due_msg {
my $self = shift;
my $msg = $self->mt('Balance Due');
@@ -1894,6 +1911,10 @@ sub balance_due_msg {
$msg;
}
+=item balance_due_date
+
+=cut
+
sub balance_due_date {
my $self = shift;
my $conf = $self->conf;
@@ -1934,6 +1955,348 @@ sub _date_pretty_unlocalized {
time2str($date_format, $self->_date);
}
+=item email HASHREF
+
+Emails this template.
+
+Options are passed as a hashref. Available options:
+
+=over 4
+
+=item from
+
+If specified, overrides the default From: address.
+
+=item notice_name
+
+If specified, overrides the name of the sent document ("Invoice" or "Quotation")
+
+=item template
+
+(Deprecated) If specified, is the name of a suffix for alternate template files.
+
+=back
+
+Options accepted by generate_email can also be used.
+
+=cut
+
+sub email {
+ my $self = shift;
+ my $opt = shift || {};
+ if ($opt and !ref($opt)) {
+ die ref($self). '->email called with positional parameters';
+ }
+
+ return if $self->hide;
+
+ my $error = send_email(
+ $self->generate_email(
+ 'subject' => $self->email_subject($opt->{template}),
+ %$opt, # template, etc.
+ )
+ );
+
+ die "can't email: $error\n" if $error;
+}
+
+=item generate_email OPTION => VALUE ...
+
+Options:
+
+=over 4
+
+=item from
+
+sender address, required
+
+=item template
+
+alternate template name, optional
+
+=item print_text
+
+text attachment arrayref, optional
+
+=item subject
+
+email subject, optional
+
+=item notice_name
+
+notice name instead of "Invoice", optional
+
+=back
+
+Returns an argument list to be passed to L<FS::Misc::send_email>.
+
+=cut
+
+use MIME::Entity;
+
+sub generate_email {
+
+ my $self = shift;
+ my %args = @_;
+ my $conf = $self->conf;
+
+ my $me = '[FS::Template_Mixin::generate_email]';
+
+ my %return = (
+ 'from' => $args{'from'},
+ 'subject' => ($args{'subject'} || $self->email_subject),
+ 'custnum' => $self->custnum,
+ 'msgtype' => 'invoice',
+ );
+
+ $args{'unsquelch_cdr'} = $conf->exists('voip-cdr_email');
+
+ my $cust_main = $self->cust_main;
+
+ if (ref($args{'to'}) eq 'ARRAY') {
+ $return{'to'} = $args{'to'};
+ } elsif ( $cust_main ) {
+ $return{'to'} = [ $cust_main->invoicing_list_emailonly ];
+ }
+
+ my $tc = $self->template_conf;
+
+ if ( $conf->exists($tc.'html') ) {
+
+ warn "$me creating HTML/text multipart message"
+ if $DEBUG;
+
+ $return{'nobody'} = 1;
+
+ my $alternative = build MIME::Entity
+ 'Type' => 'multipart/alternative',
+ #'Encoding' => '7bit',
+ 'Disposition' => 'inline'
+ ;
+
+ my $data = '';
+ if ( $conf->exists($tc. 'email_pdf')
+ and scalar($conf->config($tc. 'email_pdf_note')) ) {
+
+ warn "$me using '${tc}email_pdf_note' in multipart message"
+ if $DEBUG;
+ $data = [ map { $_ . "\n" }
+ $conf->config($tc.'email_pdf_note')
+ ];
+
+ } else {
+
+ warn "$me not using '${tc}email_pdf_note' in multipart message"
+ if $DEBUG;
+ if ( ref($args{'print_text'}) eq 'ARRAY' ) {
+ $data = $args{'print_text'};
+ } elsif ( $conf->exists($tc.'template') ) { #plaintext invoice_template
+ $data = [ $self->print_text(\%args) ];
+ }
+
+ }
+
+ if ( $data ) {
+ $alternative->attach(
+ 'Type' => 'text/plain',
+ 'Encoding' => 'quoted-printable',
+ 'Charset' => 'UTF-8',
+ #'Encoding' => '7bit',
+ 'Data' => $data,
+ 'Disposition' => 'inline',
+ );
+ }
+
+ my $htmldata;
+ my $image = '';
+ my $barcode = '';
+ if ( $conf->exists($tc.'email_pdf')
+ and scalar($conf->config($tc.'email_pdf_note')) ) {
+
+ $htmldata = join('<BR>', $conf->config($tc.'email_pdf_note') );
+
+ } else {
+
+ $args{'from'} =~ /\@([\w\.\-]+)/;
+ my $from = $1 || 'example.com';
+ my $content_id = join('.', rand()*(2**32), $$, time). "\@$from";
+
+ my $logo;
+ my $agentnum = $cust_main ? $cust_main->agentnum
+ : $self->prospect_main->agentnum;
+ if ( defined($args{'template'}) && length($args{'template'})
+ && $conf->exists( 'logo_'. $args{'template'}. '.png', $agentnum )
+ )
+ {
+ $logo = 'logo_'. $args{'template'}. '.png';
+ } else {
+ $logo = "logo.png";
+ }
+ my $image_data = $conf->config_binary( $logo, $agentnum);
+
+ $image = build MIME::Entity
+ 'Type' => 'image/png',
+ 'Encoding' => 'base64',
+ 'Data' => $image_data,
+ 'Filename' => 'logo.png',
+ 'Content-ID' => "<$content_id>",
+ ;
+
+ if ( ref($self) eq 'FS::cust_bill' && $conf->exists('invoice-barcode') ) {
+ my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from";
+ $barcode = build MIME::Entity
+ 'Type' => 'image/png',
+ 'Encoding' => 'base64',
+ 'Data' => $self->invoice_barcode(0),
+ 'Filename' => 'barcode.png',
+ 'Content-ID' => "<$barcode_content_id>",
+ ;
+ $args{'barcode_cid'} = $barcode_content_id;
+ }
+
+ $htmldata = $self->print_html({ 'cid'=>$content_id, %args });
+ }
+
+ $alternative->attach(
+ 'Type' => 'text/html',
+ 'Encoding' => 'quoted-printable',
+ 'Data' => [ '<html>',
+ ' <head>',
+ ' <title>',
+ ' '. encode_entities($return{'subject'}),
+ ' </title>',
+ ' </head>',
+ ' <body bgcolor="#e8e8e8">',
+ $htmldata,
+ ' </body>',
+ '</html>',
+ ],
+ 'Disposition' => 'inline',
+ #'Filename' => 'invoice.pdf',
+ );
+
+
+ my @otherparts = ();
+ if ( ref($self) eq 'FS::cust_bill' && $cust_main->email_csv_cdr ) {
+
+ push @otherparts, build MIME::Entity
+ 'Type' => 'text/csv',
+ 'Encoding' => '7bit',
+ 'Data' => [ map { "$_\n" }
+ $self->call_details('prepend_billed_number' => 1)
+ ],
+ 'Disposition' => 'attachment',
+ 'Filename' => 'usage-'. $self->invnum. '.csv',
+ ;
+
+ }
+
+ if ( $conf->exists($tc.'email_pdf') ) {
+
+ #attaching pdf too:
+ # multipart/mixed
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+ # image/png
+ # application/pdf
+
+ my $related = build MIME::Entity 'Type' => 'multipart/related',
+ 'Encoding' => '7bit';
+
+ #false laziness w/Misc::send_email
+ $related->head->replace('Content-type',
+ $related->mime_type.
+ '; boundary="'. $related->head->multipart_boundary. '"'.
+ '; type=multipart/alternative'
+ );
+
+ $related->add_part($alternative);
+
+ $related->add_part($image) if $image;
+
+ my $pdf = build MIME::Entity $self->mimebuild_pdf(\%args);
+
+ $return{'mimeparts'} = [ $related, $pdf, @otherparts ];
+
+ } else {
+
+ #no other attachment:
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+ # image/png
+
+ $return{'content-type'} = 'multipart/related';
+ if ($conf->exists('invoice-barcode') && $barcode) {
+ $return{'mimeparts'} = [ $alternative, $image, $barcode, @otherparts ];
+ } else {
+ $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
+ }
+ $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
+ #$return{'disposition'} = 'inline';
+
+ }
+
+ } else {
+
+ if ( $conf->exists($tc.'email_pdf') ) {
+ warn "$me creating PDF attachment"
+ if $DEBUG;
+
+ #mime parts arguments a la MIME::Entity->build().
+ $return{'mimeparts'} = [
+ { $self->mimebuild_pdf(\%args) }
+ ];
+ }
+
+ if ( $conf->exists($tc.'email_pdf')
+ and scalar($conf->config($tc.'email_pdf_note')) ) {
+
+ warn "$me using '${tc}email_pdf_note'"
+ if $DEBUG;
+ $return{'body'} = [ map { $_ . "\n" }
+ $conf->config($tc.'email_pdf_note')
+ ];
+
+ } else {
+
+ warn "$me not using '${tc}email_pdf_note'"
+ if $DEBUG;
+ if ( ref($args{'print_text'}) eq 'ARRAY' ) {
+ $return{'body'} = $args{'print_text'};
+ } else {
+ $return{'body'} = [ $self->print_text(\%args) ];
+ }
+
+ }
+
+ }
+
+ %return;
+
+}
+
+=item mimebuild_pdf
+
+Returns a list suitable for passing to MIME::Entity->build(), representing
+this invoice as PDF attachment.
+
+=cut
+
+sub mimebuild_pdf {
+ my $self = shift;
+ (
+ 'Type' => 'application/pdf',
+ 'Encoding' => 'base64',
+ 'Data' => [ $self->print_pdf(@_) ],
+ 'Disposition' => 'attachment',
+ 'Filename' => 'invoice-'. $self->invnum. '.pdf',
+ );
+}
+
=item _items_sections OPTIONS
Generate section information for all items appearing on this invoice.
@@ -3065,7 +3428,7 @@ sub _items_cust_bill_pkg {
) > 0 ) {
@discounts = ();
}
- if( @discounts ) {
+ if ( @discounts ) {
warn "$me _items_cust_bill_pkg including discounts for ".
$cust_bill_pkg->billpkgnum."\n"
if $DEBUG;
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index d811c1a..ad60b21 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -15,7 +15,7 @@ use HTML::Entities;
use Storable qw( freeze thaw );
use GD::Barcode;
use FS::UID qw( datasrc );
-use FS::Misc qw( send_email send_fax do_print );
+use FS::Misc qw( send_fax do_print );
use FS::Record qw( qsearch qsearchs dbh );
use FS::cust_statement;
use FS::cust_bill_pkg;
@@ -1024,301 +1024,6 @@ sub apply_payments_and_credits {
}
-=item generate_email OPTION => VALUE ...
-
-Options:
-
-=over 4
-
-=item from
-
-sender address, required
-
-=item template
-
-alternate template name, optional
-
-=item print_text
-
-text attachment arrayref, optional
-
-=item subject
-
-email subject, optional
-
-=item notice_name
-
-notice name instead of "Invoice", optional
-
-=back
-
-Returns an argument list to be passed to L<FS::Misc::send_email>.
-
-=cut
-
-use MIME::Entity;
-
-sub generate_email {
-
- my $self = shift;
- my %args = @_;
- my $conf = $self->conf;
-
- my $me = '[FS::cust_bill::generate_email]';
-
- my %return = (
- 'from' => $args{'from'},
- 'subject' => ($args{'subject'} || $self->email_subject),
- 'custnum' => $self->custnum,
- 'msgtype' => 'invoice',
- );
-
- $args{'unsquelch_cdr'} = $conf->exists('voip-cdr_email');
-
- my $cust_main = $self->cust_main;
-
- if (ref($args{'to'}) eq 'ARRAY') {
- $return{'to'} = $args{'to'};
- } else {
- $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ }
- $cust_main->invoicing_list
- ];
- }
-
- if ( $conf->exists('invoice_html') ) {
-
- warn "$me creating HTML/text multipart message"
- if $DEBUG;
-
- $return{'nobody'} = 1;
-
- my $alternative = build MIME::Entity
- 'Type' => 'multipart/alternative',
- #'Encoding' => '7bit',
- 'Disposition' => 'inline'
- ;
-
- my $data;
- if ( $conf->exists('invoice_email_pdf')
- and scalar($conf->config('invoice_email_pdf_note')) ) {
-
- warn "$me using 'invoice_email_pdf_note' in multipart message"
- if $DEBUG;
- $data = [ map { $_ . "\n" }
- $conf->config('invoice_email_pdf_note')
- ];
-
- } else {
-
- warn "$me not using 'invoice_email_pdf_note' in multipart message"
- if $DEBUG;
- if ( ref($args{'print_text'}) eq 'ARRAY' ) {
- $data = $args{'print_text'};
- } else {
- $data = [ $self->print_text(\%args) ];
- }
-
- }
-
- $alternative->attach(
- 'Type' => 'text/plain',
- 'Encoding' => 'quoted-printable',
- 'Charset' => 'UTF-8',
- #'Encoding' => '7bit',
- 'Data' => $data,
- 'Disposition' => 'inline',
- );
-
-
- my $htmldata;
- my $image = '';
- my $barcode = '';
- if ( $conf->exists('invoice_email_pdf')
- and scalar($conf->config('invoice_email_pdf_note')) ) {
-
- $htmldata = join('<BR>', $conf->config('invoice_email_pdf_note') );
-
- } else {
-
- $args{'from'} =~ /\@([\w\.\-]+)/;
- my $from = $1 || 'example.com';
- my $content_id = join('.', rand()*(2**32), $$, time). "\@$from";
-
- my $logo;
- my $agentnum = $cust_main->agentnum;
- if ( defined($args{'template'}) && length($args{'template'})
- && $conf->exists( 'logo_'. $args{'template'}. '.png', $agentnum )
- )
- {
- $logo = 'logo_'. $args{'template'}. '.png';
- } else {
- $logo = "logo.png";
- }
- my $image_data = $conf->config_binary( $logo, $agentnum);
-
- $image = build MIME::Entity
- 'Type' => 'image/png',
- 'Encoding' => 'base64',
- 'Data' => $image_data,
- 'Filename' => 'logo.png',
- 'Content-ID' => "<$content_id>",
- ;
-
- if ($conf->exists('invoice-barcode')) {
- my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from";
- $barcode = build MIME::Entity
- 'Type' => 'image/png',
- 'Encoding' => 'base64',
- 'Data' => $self->invoice_barcode(0),
- 'Filename' => 'barcode.png',
- 'Content-ID' => "<$barcode_content_id>",
- ;
- $args{'barcode_cid'} = $barcode_content_id;
- }
-
- $htmldata = $self->print_html({ 'cid'=>$content_id, %args });
- }
-
- $alternative->attach(
- 'Type' => 'text/html',
- 'Encoding' => 'quoted-printable',
- 'Data' => [ '<html>',
- ' <head>',
- ' <title>',
- ' '. encode_entities($return{'subject'}),
- ' </title>',
- ' </head>',
- ' <body bgcolor="#e8e8e8">',
- $htmldata,
- ' </body>',
- '</html>',
- ],
- 'Disposition' => 'inline',
- #'Filename' => 'invoice.pdf',
- );
-
-
- my @otherparts = ();
- if ( $cust_main->email_csv_cdr ) {
-
- push @otherparts, build MIME::Entity
- 'Type' => 'text/csv',
- 'Encoding' => '7bit',
- 'Data' => [ map { "$_\n" }
- $self->call_details('prepend_billed_number' => 1)
- ],
- 'Disposition' => 'attachment',
- 'Filename' => 'usage-'. $self->invnum. '.csv',
- ;
-
- }
-
- if ( $conf->exists('invoice_email_pdf') ) {
-
- #attaching pdf too:
- # multipart/mixed
- # multipart/related
- # multipart/alternative
- # text/plain
- # text/html
- # image/png
- # application/pdf
-
- my $related = build MIME::Entity 'Type' => 'multipart/related',
- 'Encoding' => '7bit';
-
- #false laziness w/Misc::send_email
- $related->head->replace('Content-type',
- $related->mime_type.
- '; boundary="'. $related->head->multipart_boundary. '"'.
- '; type=multipart/alternative'
- );
-
- $related->add_part($alternative);
-
- $related->add_part($image) if $image;
-
- my $pdf = build MIME::Entity $self->mimebuild_pdf(\%args);
-
- $return{'mimeparts'} = [ $related, $pdf, @otherparts ];
-
- } else {
-
- #no other attachment:
- # multipart/related
- # multipart/alternative
- # text/plain
- # text/html
- # image/png
-
- $return{'content-type'} = 'multipart/related';
- if ($conf->exists('invoice-barcode') && $barcode) {
- $return{'mimeparts'} = [ $alternative, $image, $barcode, @otherparts ];
- } else {
- $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
- }
- $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
- #$return{'disposition'} = 'inline';
-
- }
-
- } else {
-
- if ( $conf->exists('invoice_email_pdf') ) {
- warn "$me creating PDF attachment"
- if $DEBUG;
-
- #mime parts arguments a la MIME::Entity->build().
- $return{'mimeparts'} = [
- { $self->mimebuild_pdf(\%args) }
- ];
- }
-
- if ( $conf->exists('invoice_email_pdf')
- and scalar($conf->config('invoice_email_pdf_note')) ) {
-
- warn "$me using 'invoice_email_pdf_note'"
- if $DEBUG;
- $return{'body'} = [ map { $_ . "\n" }
- $conf->config('invoice_email_pdf_note')
- ];
-
- } else {
-
- warn "$me not using 'invoice_email_pdf_note'"
- if $DEBUG;
- if ( ref($args{'print_text'}) eq 'ARRAY' ) {
- $return{'body'} = $args{'print_text'};
- } else {
- $return{'body'} = [ $self->print_text(\%args) ];
- }
-
- }
-
- }
-
- %return;
-
-}
-
-=item mimebuild_pdf
-
-Returns a list suitable for passing to MIME::Entity->build(), representing
-this invoice as PDF attachment.
-
-=cut
-
-sub mimebuild_pdf {
- my $self = shift;
- (
- 'Type' => 'application/pdf',
- 'Encoding' => 'base64',
- 'Data' => [ $self->print_pdf(@_) ],
- 'Disposition' => 'attachment',
- 'Filename' => 'invoice-'. $self->invnum. '.pdf',
- );
-}
-
=item send HASHREF
Sends this invoice to the destinations configured for this customer: sends
@@ -1331,7 +1036,7 @@ I<template>: a suffix for alternate invoices
I<agentnum>: obsolete, now does nothing.
-I<invoice_from> overrides the default email invoice From: address.
+I<from> overrides the default email invoice From: address.
I<amount>: obsolete, does nothing
@@ -1367,55 +1072,22 @@ sub send {
}
-=item email HASHREF | [ TEMPLATE [ , INVOICE_FROM ] ]
-
-Sends this invoice to the customer's email destination(s).
-
-Options must be passed as a hashref. Positional parameters are no longer
-allowed.
-
-I<template>, if specified, is the name of a suffix for alternate invoices.
-
-I<invoice_from>, if specified, overrides the default email invoice From:
-address.
-
-I<notice_name> is the name of the sent document.
-
-=cut
-
-sub queueable_email {
- my %opt = @_;
-
- my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
- or die "invalid invoice number: " . $opt{invnum};
-
- my %args = map {$_ => $opt{$_}}
- grep { $opt{$_} }
- qw( invoice_from notice_name no_coupon template );
-
- my $error = $self->email( \%args );
- die $error if $error;
-
-}
-
sub email {
my $self = shift;
- return if $self->hide;
- my $conf = $self->conf;
my $opt = shift || {};
if ($opt and !ref($opt)) {
- die "FS::cust_bill::email called with positional parameters";
+ die ref($self). '->email called with positional parameters';
}
- my $template = $opt->{template};
- my $from = delete $opt->{invoice_from};
+ my $conf = $self->conf;
+
+ my $from = delete $opt->{from};
# this is where we set the From: address
$from ||= $self->_agent_invoice_from || #XXX should go away
$conf->config('invoice_from', $self->cust_main->agentnum );
- my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
- $self->cust_main->invoicing_list;
+ my @invoicing_list = $self->cust_main->invoicing_list_emailonly;
if ( ! @invoicing_list ) { #no recipients
if ( $conf->exists('cust_bill-no_recipients-error') ) {
@@ -1426,19 +1098,28 @@ sub email {
}
}
- # this is where we set the Subject:
- my $subject = $self->email_subject($template);
+ $self->SUPER::email( {
+ 'from' => $from,
+ 'to' => \@invoicing_list,
+ %$opt,
+ });
- my $error = send_email(
- $self->generate_email(
- 'from' => $from,
- 'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
- 'subject' => $subject,
- %$opt, # template, etc.
- )
- );
- die "can't email invoice: $error\n" if $error;
- #die "$error\n" if $error;
+}
+
+#this stays here for now because its explicitly used as
+# FS::cust_bill::queueable_email
+sub queueable_email {
+ my %opt = @_;
+
+ my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
+ or die "invalid invoice number: " . $opt{invnum};
+
+ my %args = map {$_ => $opt{$_}}
+ grep { $opt{$_} }
+ qw( from notice_name no_coupon template );
+
+ my $error = $self->email( \%args );
+ die $error if $error;
}
diff --git a/FS/FS/cust_event.pm b/FS/FS/cust_event.pm
index b5436d0..d7a35a7 100644
--- a/FS/FS/cust_event.pm
+++ b/FS/FS/cust_event.pm
@@ -481,9 +481,10 @@ sub re_X {
my $modenum = $part_event->option('modenum') || '';
my $invoice_from = $part_event->option('agent_invoice_from') || '';
$cust_X->set('mode' => $modenum);
- $cust_X->$method( { template => $template,
- modenum => $modenum,
- invoice_from => $invoice_from } );
+ $cust_X->$method( { template => $template,
+ modenum => $modenum,
+ from => $invoice_from,
+ } );
if ( $job ) { #progressbar foo
$num++;
diff --git a/FS/FS/part_event/Action/cust_bill_send_agent.pm b/FS/FS/part_event/Action/cust_bill_send_agent.pm
index bbb757b..cb13b1f 100644
--- a/FS/FS/part_event/Action/cust_bill_send_agent.pm
+++ b/FS/FS/part_event/Action/cust_bill_send_agent.pm
@@ -45,8 +45,8 @@ sub do_action {
$cust_bill->set('mode' => $self->option('modenum'));
$cust_bill->send(
- 'template' => $self->option('agent_templatename'),
- 'invoice_from' => $self->option('agent_invoice_from'),
+ 'template' => $self->option('agent_templatename'),
+ 'from' => $self->option('agent_invoice_from'),
);
}
diff --git a/FS/FS/quotation.pm b/FS/FS/quotation.pm
index 5710b38..44b72f4 100644
--- a/FS/FS/quotation.pm
+++ b/FS/FS/quotation.pm
@@ -167,6 +167,53 @@ sub _total {
}
+sub email {
+ my $self = shift;
+ my $opt = shift || {};
+ if ($opt and !ref($opt)) {
+ die ref($self). '->email called with positional parameters';
+ }
+
+ my $conf = $self->conf;
+
+ my $from = delete $opt->{from};
+
+ # 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 );
+
+ $self->SUPER::email( {
+ 'from' => $from,
+ %$opt,
+ });
+
+}
+
+sub email_subject {
+ my $self = shift;
+
+ my $subject =
+ $self->conf->config('quotation_subject') #, $self->cust_main->agentnum)
+ || 'Quotation';
+
+ #my $cust_main = $self->cust_main;
+ #my $name = $cust_main->name;
+ #my $name_short = $cust_main->name_short;
+ #my $invoice_number = $self->invnum;
+ #my $invoice_date = $self->_date_pretty;
+
+ eval qq("$subject");
+}
+
+=item cust_or_prosect
+
+=cut
+
+sub cust_or_prospect {
+ my $self = shift;
+ $self->custnum ? $self->cust_main : $self->prospect_main;
+}
+
=item cust_or_prospect_label_link P
HTML links to either the customer or prospect.
diff --git a/httemplate/elements/footer-popup.html b/httemplate/elements/footer-popup.html
new file mode 100644
index 0000000..6029d76
--- /dev/null
+++ b/httemplate/elements/footer-popup.html
@@ -0,0 +1,2 @@
+ </BODY>
+</HTML>
diff --git a/httemplate/misc/email-quotation.html b/httemplate/misc/email-quotation.html
new file mode 100644
index 0000000..b93b80b
--- /dev/null
+++ b/httemplate/misc/email-quotation.html
@@ -0,0 +1,71 @@
+<& /elements/header-popup.html, mt('Select recipients') &>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="process/email-quotation.html" onSubmit="document.OneTrueForm.submit.disabled=true; document.OneTrueForm.submit.style.display='none'; document.getElementById('emailingwait').style.display='';">
+<INPUT TYPE="hidden" NAME="quotationnum" VALUE="<% $quotationnum %>">
+
+<% ntable("#cccccc", 2) %>
+
+% my $emails = 0;
+
+% if ( my $cust_main = $quotation->cust_main ) {
+% foreach my $email ( $cust_main->invoicing_list_emailonly ) {
+% $emails++;
+ <& .emailrow, $email &>
+% }
+% }
+
+% my @contact = $quotation->custnum ? $quotation->cust_main->cust_contact
+% : $quotation->prospect_main->contact;
+% foreach my $contact ( @contact ) {
+% foreach my $contact_email ( $contact->contact_email ) {
+% $emails++;
+ <& .emailrow, $contact_email->emailaddress, $contact->firstlast &>
+% }
+% }
+
+<%def .emailrow>
+% my( $email, $name ) = @_;
+% if ( $name ) {
+% $name = "$name <$email>";
+% } else {
+% $name = $email;
+% }
+ <TR>
+ <TD><INPUT TYPE="checkbox" NAME="emailaddress" VALUE="<% $email |h %>"></TD>
+ <TD><% $name |h %></TD>
+ </TR>
+</%def>
+
+</TABLE>
+
+<BR>
+
+<CENTER>
+% if ( $emails ) {
+ <BUTTON TYPE="submit" NAME="submit" ID="submit">Email quotation</BUTTON>
+ <DIV ID="emailingwait" STYLE="display:none">
+ <IMG SRC="<%$p%>images/wait-orange.gif"> <B>Sending...</B>
+ </DIV>
+% } else {
+ <FONT SIZE="+1" COLOR="#ff0000"><% mt('Add a contact email address first') |h %></FONT>
+% }
+</CENTER>
+
+</FORM>
+
+<& /elements/footer-popup.html &>
+<%init>
+
+#die "access denied"
+# unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation'); #separate rights to generate vs send/email?
+
+$cgi->param('quotationnum') =~ /^(\d+)$/ or die "Illegal quotationnum";
+my $quotationnum = $1;
+
+#XXX agent-virt
+my $quotation = qsearchs('quotation', { 'quotationnum'=>$quotationnum })
+ or die "Unknown quotationnum";
+
+</%init>
diff --git a/httemplate/misc/process/email-quotation.html b/httemplate/misc/process/email-quotation.html
new file mode 100755
index 0000000..e7fef4a
--- /dev/null
+++ b/httemplate/misc/process/email-quotation.html
@@ -0,0 +1,20 @@
+<& /elements/header-popup.html, mt('Email sent') &>
+<SCRIPT TYPE="text/javascript">
+ setTimeout("parent.cClick()", 3000);
+</SCRIPT>
+<& /elements/footer-popup.html &>
+<%init>
+
+#die "access denied"
+# unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation'); #separate rights to generate vs send/email?
+
+$cgi->param('quotationnum') =~ /^(\d+)$/ or die "Illegal quotationnum";
+my $quotationnum = $1;
+
+#XXX agent-virt
+my $quotation = qsearchs('quotation', { 'quotationnum'=>$quotationnum })
+ or die "Unknown quotationnum";
+
+$quotation->email({ 'to' => [ $cgi->param('emailaddress') ] });
+
+</%init>
diff --git a/httemplate/view/quotation.html b/httemplate/view/quotation.html
index b8dc1d1..81c7cdd 100755
--- a/httemplate/view/quotation.html
+++ b/httemplate/view/quotation.html
@@ -1,5 +1,7 @@
<& /elements/header.html, mt('Quotation View'), $menubar &>
+<& /elements/init_overlib.html &>
+
<SCRIPT TYPE="text/javascript">
function areyousure(href, message) {
if (confirm(message) == true)
@@ -20,9 +22,14 @@ function areyousure(href, message) {
% if ( 1 ) { #if ( $curuser->access_right('Send quotations') )
-% #if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
-%# <A HREF="<% $p %>misc/email-quotation.html?<% $link %>"><% mt('Email this quotation') |h %></A>
-% #}
+ <& /elements/popup_link.html,
+ 'action' => "${p}misc/email-quotation.html".
+ "?quotationnum=$quotationnum",
+ 'label' => emt('Email this quotation'),
+ 'actionlabel' => emt('Select recipients'),
+ #'width' => 540,
+ #'height' => 336,
+ &>
%# <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Re-print this invoice') |h %></A>