HTML templates for printable form letters, #17349
authorMark Wells <mark@freeside.biz>
Wed, 10 Oct 2012 19:24:09 +0000 (12:24 -0700)
committerMark Wells <mark@freeside.biz>
Wed, 10 Oct 2012 19:24:09 +0000 (12:24 -0700)
FS/FS/Misc.pm
FS/FS/Schema.pm
FS/FS/msg_template.pm
FS/FS/part_event/Action/letter.pm [new file with mode: 0644]
FS/FS/part_tag.pm
FS/bin/freeside-wkhtmltopdf [new file with mode: 0755]
httemplate/edit/cust_main.cgi
httemplate/edit/part_tag.html
httemplate/edit/process/msg_template.html
httemplate/elements/tr-select-cust_tag.html

index 297e39f..a1c15fd 100644 (file)
@@ -799,7 +799,7 @@ sub _pslatex {
 
 }
 
-=item print ARRAYREF
+=item do_print ARRAYREF
 
 Sends the lines in ARRAYREF to the printer.
 
index 6ad4b74..fb1f1d6 100644 (file)
@@ -1349,6 +1349,7 @@ sub tables_hashref {
         'tagname',  'varchar',     '', $char_d, '', '',
         'tagdesc',  'varchar', 'NULL', $char_d, '', '',
         'tagcolor', 'varchar', 'NULL',       6, '', '',
+        'by_default',  'char', 'NULL',       1, '', '',
         'disabled',    'char', 'NULL',       1, '', '', 
       ],
       'primary_key' => 'tagnum',
index cac7fe5..e38346a 100644 (file)
@@ -16,6 +16,9 @@ use Date::Format qw( time2str );
 use HTML::Entities qw( decode_entities encode_entities ) ;
 use HTML::FormatText;
 use HTML::TreeBuilder;
+
+use File::Temp;
+use IPC::Run qw(run);
 use vars qw( $DEBUG $conf );
 
 FS::UID->install_callback( sub { $conf = new FS::Conf; } );
@@ -273,8 +276,8 @@ A hash reference of additional substitutions
 sub prepare {
   my( $self, %opt ) = @_;
 
-  my $cust_main = $opt{'cust_main'};
-  my $object = $opt{'object'};
+  my $cust_main = $opt{'cust_main'} or die 'cust_main required';
+  my $object = $opt{'object'} or die 'object required';
 
   # localization
   my $locale = $cust_main->locale || '';
@@ -435,9 +438,65 @@ sub send {
   send_email(generate_email($self->prepare(@_)));
 }
 
+=item render OPTION => VALUE ...
+
+Fills in the template and renders it to a PDF document.  Returns the 
+name of the PDF file.
+
+Options are as for 'prepare', but 'from' and 'to' are meaningless.
+
+=cut
+
+# will also have options to set paper size, margins, etc.
+
+sub render {
+  my $self = shift;
+  eval "use PDF::WebKit";
+  die $@ if $@;
+  my %opt = @_;
+  my %hash = $self->prepare(%opt);
+  my $html = $hash{'html_body'};
+
+  my $tmp = 'msg'.$self->msgnum.'-'.time2str('%Y%m%d', time).'-XXXXXXXX';
+  my $dir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc";
+
+  # Graphics/stylesheets should probably go in /var/www on the Freeside 
+  # machine.
+  my $kit = PDF::WebKit->new(\$html); #%options
+  # hack to use our wrapper script
+  $kit->configure(sub { shift->wkhtmltopdf('freeside-wkhtmltopdf') });
+  my $fh = File::Temp->new(
+    TEMPLATE  => $tmp,
+    DIR       => $dir,
+    UNLINK    => 0,
+    SUFFIX    => '.pdf'
+  );
+
+  print $fh $kit->to_pdf;
+  close $fh;
+  return $fh->filename;
+}
+
+=item print OPTIONS
+
+Render a PDF and send it to the printer.  OPTIONS are as for 'render'.
+
+=cut
+
+sub print {
+  my $file = render(@_);
+  my @lpr = $conf->config('lpr');
+  run ([@lpr, '-r'], '<', $file)
+    or die "lpr error:\n$?\n";
+}
+
+
 # helper sub for package dates
 my $ymd = sub { $_[0] ? time2str('%Y-%m-%d', $_[0]) : '' };
 
+# helper sub for money amounts
+my $money = sub { ($conf->money_char || '$') . sprintf('%.2f', $_[0] || 0) };
+
 # helper sub for usage-related messages
 my $usage_warning = sub {
   my $svc = shift;
@@ -483,6 +542,7 @@ sub substitutions {
       signupdate dundate
       packages recurdates
       ),
+      [ invoicing_email => sub { shift->invoicing_list_emailonly_scalar } ],
       #compatibility: obsolete ship_ fields - use the non-ship versions
       map (
         { my $field = $_;
@@ -520,6 +580,8 @@ sub substitutions {
       labels_short
       ),
       [ pkg               => sub { shift->part_pkg->pkg } ],
+      [ pkg_category      => sub { shift->part_pkg->categoryname } ],
+      [ pkg_class         => sub { shift->part_pkg->classname } ],
       [ cancel            => sub { shift->getfield('cancel') } ], # grrr...
       [ start_ymd         => sub { $ymd->(shift->getfield('start_date')) } ],
       [ setup_ymd         => sub { $ymd->(shift->getfield('setup')) } ],
@@ -529,6 +591,13 @@ sub substitutions {
       [ susp_ymd          => sub { $ymd->(shift->getfield('susp')) } ],
       [ expire_ymd        => sub { $ymd->(shift->getfield('expire')) } ],
       [ cancel_ymd        => sub { $ymd->(shift->getfield('cancel')) } ],
+
+      # not necessarily correct for non-flat packages
+      [ setup_fee         => sub { shift->part_pkg->option('setup_fee') } ],
+      [ recur_fee         => sub { shift->part_pkg->option('recur_fee') } ],
+
+      [ freq_pretty       => sub { shift->part_pkg->freq_pretty } ],
+
     ],
     'cust_bill' => [qw(
       invnum
diff --git a/FS/FS/part_event/Action/letter.pm b/FS/FS/part_event/Action/letter.pm
new file mode 100644 (file)
index 0000000..57b7b77
--- /dev/null
@@ -0,0 +1,47 @@
+package FS::part_event::Action::letter;
+
+use strict;
+use base qw( FS::part_event::Action );
+use FS::Record qw( qsearchs );
+use FS::msg_template;
+
+sub description { 'Print a form letter to the customer' }
+
+#sub eventtable_hashref {
+#    { 'cust_main' => 1,
+#      'cust_bill' => 1,
+#      'cust_pkg'  => 1,
+#    };
+#}
+
+sub option_fields {
+  (
+    'msgnum' => { 'label'    => 'Template',
+                  'type'     => 'select-table',
+                  'table'    => 'msg_template',
+                  'name_col' => 'msgname',
+                  'disable_empty' => 1,
+                },
+  );
+}
+
+sub default_weight { 56; } #?
+
+sub do_action {
+  my( $self, $object ) = @_;
+
+  my $cust_main = $self->cust_main($object);
+
+  my $msgnum = $self->option('msgnum');
+
+  my $msg_template = qsearchs('msg_template', { 'msgnum' => $msgnum } )
+      or die "Template $msgnum not found";
+
+  $msg_template->print(
+    'cust_main' => $cust_main,
+    'object'    => $object,
+  );
+
+}
+
+1;
index 0229e3a..ed31929 100644 (file)
@@ -30,22 +30,17 @@ FS::Record.  The following fields are currently supported:
 
 =over 4
 
-=item tagnum
+=item tagnum - primary key
 
-primary key
+=item tagname - tag name
 
-=item tagname
+=item tagdesc - description (can be longer than name)
 
-tagname
+=item tagcolor - HTML-style color to display this tag
 
-=item tagdesc
-
-tagdesc
-
-=item tagcolor
-
-tagcolor
+=item by_default - 'Y' to enable this tag on new customers
 
+=item disabled
 
 =back
 
@@ -111,6 +106,7 @@ sub check {
     || $self->ut_text('tagname')
     || $self->ut_textn('tagdesc')
     || $self->ut_textn('tagcolor')
+    || $self->ut_enum('by_default', [ '', 'Y' ] )
     || $self->ut_enum('disabled', [ '', 'Y' ] )
   ;
   return $error if $error;
@@ -120,6 +116,21 @@ sub check {
 
 =back
 
+=head1 CLASS METHODS
+
+=over 4
+
+=item default_tags
+
+Returns the tagnums of all tags that have 'by_default' enabled.
+
+=cut
+
+sub default_tags {
+  my $class = shift;
+  map { $_->tagnum } qsearch('part_tag', { disabled => '', by_default => 'Y' });
+}
+
 =head1 BUGS
 
 =head1 SEE ALSO
diff --git a/FS/bin/freeside-wkhtmltopdf b/FS/bin/freeside-wkhtmltopdf
new file mode 100755 (executable)
index 0000000..c6c5531
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+if [ $DISPLAY ] ; then
+  wkhtmltopdf $@
+else
+  xvfb-run -- wkhtmltopdf $@
+fi
index e3e812f..2628b4e 100755 (executable)
@@ -315,6 +315,8 @@ if ( $cgi->param('error') ) {
   $stateid = '';
   $payinfo = '';
 
+  $cgi->param('tagnum', FS::part_tag->default_tags);
+
   if ( $cgi->param('qualnum') =~ /^(\d+)$/ ) {
     my $qualnum = $1;
     my $qual = qsearchs('qual', { 'qualnum' => $qualnum } )
index 2caeb27..5712560 100644 (file)
@@ -5,12 +5,14 @@
                 { field=>'tagname',  type=>'text', size=>10 },
                 { field=>'disabled', type=>'checkbox', value=>'Y' },
                 { field=>'tagdesc',  type=>'text', size=>60 },
+                { field=>'by_default',  type=>'checkbox', value=>'Y' },
                 $tagcolor,
               ],
               'labels'        => { 'tagnum'   => 'Tag #',
                                    'tagname'  => 'Tag',
                                    'tagdesc'  => 'Message',
                                    'tagcolor' => 'Highlight Color',
+                                   'by_default' => 'On by default',
                                    'disabled' => 'Disabled',
                                  },
               'viewall_dir' => 'browse',
index b19f5c5..e146adf 100644 (file)
@@ -29,6 +29,8 @@ sub args_callback {
   # no validation of these; they can contain just about anything
   $content{'subject'} = $cgi->param('subject') || '';
   $content{'body'} = $cgi->param('body') || '';
+  $object->subject('');
+  $object->body('');
   return %content;
 }
 
index 5312644..76b1b71 100644 (file)
@@ -28,7 +28,7 @@ my $cgi = $opt{'cgi'};
 my $is_report = $opt{'is_report'};
 
 my @curr_tagnum = ();
-if ( $cgi && $cgi->param('error') ) {
+if ( $cgi && $cgi->param('tagnum') ) {
   @curr_tagnum = $cgi->param('tagnum');
 } elsif ( $opt{'custnum'} ) {
   @curr_tagnum = map $_->tagnum,