Search code examples
phpajaxcodeigniterprogressinvoices

Batch PDF invoicing/generation with PHP and AJAX progress indication


OK so I'm coming to the stage of a current project where I will be implementing invoicing, and thinking about it I thought I would see how you guys would approach this problem. If it's relevant, I'm using CodeIgniter, jQuery, jQuery UI and I have complete control over the server, running PHP 5.3 and Apache 2.2.16. Production server is running Debian Squeeze.

So my query is this.

  • The app I am developing will handle invoicing
  • Invoices can be produced one by one, or in a batch (from a date range)
  • All invoices should have a PDF generated and saved to the server. I'm using mPDF.
  • Some invoices can be emailed to customers, others will have to be just PDF'd and if possible sent to a printer (lpr -p?), straight from PHP if possible
  • Invoice runs are carried out typically bi-weekly
  • Invoice runs can typically involve generating in excess of 100 invoices
  • Some invoices can be reasonably large, although typical PDF size would probably be 2-5 pages per invoice

Basically I would like some tips. The main areas I am looking for advice on are:

  • Batch invoicing - if this was done through one browser request, it could take several minutes, and PHP could exceed max execution time. I thought perhaps it could typically be done via AJAX, where jQuery retrieves a list of invoices to make, then loops through the list, requesting PHP make a PDF and process the invoice. So instead of one large request, you have lots of smaller ones? Is this recommended? Is there a better approach?
  • Batch printing. The users are anxious about having to manually print tens of invoices every run, and understandably so. What's the best way to handle a printing of batch invoices from a PHP to a local printer? I have read about sending output to a printer using lpr on the box, but I'm anxious that during a batch procedure of say 100 PDFs, a couple could end up getting lost. Is there a better way?
  • Progress reporting. Having a typical spinning wheel could become tedious and I know that the users have requested more detailed progress updates. Any advice on this? I know with the above method of many small requests, you could easily have AJAX report: Building PDF for Cust 1...DONE... Emailing PDF to Cust 1... DONE... Building PDF for Customer 2... and so on.
  • Database ACIDity and error handling. Invoicing is a biggy and obviously sensitive with the amount of money involved... any tips regarding application logic for transactions, handling PDF generation errors/email errors to rollback the invoices, and so on would be great.

I'll leave this open to advice. Any tips, suggestions or comments you could spare would be gratefully received.

Many thanks.


Solution

  • Just accomplished this now and thought I would post the solution I used for others to see.

    1. As per recommendation from datasage, I concentrated on writing a solid procedure to get the database processing done (using MySQL transactions) first.

    2. I then wrote functionality to handle individual invoices (e.g. building one PDF with mPDF, sending one PDF via e-mail) to URLs such as /invoicing/pdf/123 and /invoicing/send/123

    3. For the batch, client-side procedure, firstly I wrote a server side listener which would return a JSON object of the invoice numbers that need to be sent, with the relevant customer name and URI to send:

       {"invoice_number":1,"customer":"Acme Inc.","uri":"/invoicing/send/1"},
       {"invoice_number":2,"customer":"Another Company","uri":"/invoicing/send/2"},
       {"invoice_number":3,"customer":"Yet Another.","uri":"/invoicing/send/3"},
      
    4. You can see where I'm going with this. Finally, I wrote the client-side Javascript to loop through the JSON and make the requests. Problem I found however was by using $.ajax or any other native jQuery call, requests would all fire at once, and Chrome limits server connections to six, so only six invoices would get sent. Further, switching $.ajax to use async:false caused the browser to freeze until all requests were successful. So I stumbled upon this plugin: http://plugins.jquery.com/project/ajaxq which would "queue" up AJAX calls, and would provide the effect I needed.

    5. Finally, the client-side code I had looks a little like this:

          $(batch).each(function(key,value){
      
              // TODO: make this better
              // For now, uses a plugin which enables us to carry out sequential/synchronous AJAX calls to the server
              $.ajaxq("batch_email", {
                  url: value.uri,
                  format: 'json',
                  success: function(response) { 
                      completed++;
                      $('#progress').val(completed);
      
                      if (response.result == "error") {
                          $('#batch_status').append('<p class="notification error">Failed: '+value.customer_name+'</p>');
                      }
      
                      if (completed != num) {
                          $('#text').html('<img src="'+base_url+'static/img/spinner.gif" style="vertical-align:middle;margin-right:10px;" />'+batch[key+1].customer_name+'...');
                      } else {
                          $('#text').html('Finished');
                          $('#progress').after('<div class="tc"><a href="'+base_url+'customer_invoicing" class="blue button">Return to invoicing</a></a>');
                      }
                  }
              });
      
          });
      

    Still needs a little bit of tidying up before deployment, but it certainly works. As you can see I have used a HTML5 progress bar to show progress as the procedure runs.

    Hope this helps anyone who may stumble upon this post in weeks/months to come!

    Best wishes