diff options
author | Ivan Kohler <ivan@freeside.biz> | 2015-01-18 18:13:16 -0800 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2015-01-18 18:13:16 -0800 |
commit | 03c12b4dabfcaabc218f39ee13557edebc13931d (patch) | |
tree | 0d915cf9e2bbf278a48500b5d125206ab78477a2 | |
parent | 0b6bd6405107c1abf8f2c5e2bc1b569870a02309 (diff) |
email quotations, RT#22232, RT#20688
-rw-r--r-- | FS/FS/Conf.pm | 36 | ||||
-rw-r--r-- | FS/FS/Template_Mixin.pm | 365 | ||||
-rw-r--r-- | FS/FS/cust_bill.pm | 375 | ||||
-rw-r--r-- | FS/FS/cust_event.pm | 7 | ||||
-rw-r--r-- | FS/FS/part_event/Action/cust_bill_send_agent.pm | 4 | ||||
-rw-r--r-- | FS/FS/quotation.pm | 47 | ||||
-rw-r--r-- | httemplate/elements/footer-popup.html | 2 | ||||
-rw-r--r-- | httemplate/misc/email-quotation.html | 71 | ||||
-rwxr-xr-x | httemplate/misc/process/email-quotation.html | 20 | ||||
-rwxr-xr-x | httemplate/view/quotation.html | 13 |
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> |