Current functionality:
It is only possible to download the PDF version of an invoice in an invoice-per-invoice basis, by clicking on the 'Download PDF' link on the invoice details screen.
Desired functionality:
A user should be able to download many invoices in one single PDF file, following any of these filters:
1.All the invoices belonging to a particular customer
2.All the invoices between an ID range, for example all the invoices with ID equal or grater than 234 and equal or smaller than 432.
3.All the invoices generated for a billing process.
To provide this functionality, there is going to be a new screen under the menu option 'Invoices', with the new sub-option 'Download' (along with the existing 'Numbering', 'Reports', etc).
This new screen will have a help text, just like any other screen in jbilling, and then a form to display each of the filters:
Customer
ID Range (two fields)
Billing Process ID
In all cases the form has to validate that the input is a positive integer. For the range, the second field has to be >= than the first. If one of the fields for the range is entered, the other one is required. There will be one 'Generate' button to the right of each filter, so only one filter can be run at a time. Pressing any of these buttons will result in a message with a link to the generated file for downloading.
Technical overview:
jbilling already has the capability to generate a PDF version of an invoice. Also, there is a batch generation in the billing process. When the billing process finishes creating all the invoices, it compiles all the individual PDF invoice files into one and sends it attached to an email. The idea is to reuse the basic logic of this function by making it more generic to support these new conditions.
Once that is done, there is the GUI to modify, adding the new menu option and new page. This should not be problematic, just be sure to do it following the same structure as the rest of the system, with struts tiles and validation.
Client tier modifications:
For the menu option, take a look to the 'Client Tier' documentation, where that is explained. The new page is more simple than the standard form page that is very common in jbilling. The main thing is that the data you put in the fields is not stored in the database, so you don't need to initialize the fields with anything. You still need to handle the tiles the same way the other pages, because you have to integrate it with the rest of the GUI: menu options, title bar, etc. A page that you can use as an example is the Item Types, since it is quite simple.
You will need a new struts form (only one, not one per filter), with all the fields. Some of the validation can be done using struts validation, for example to check that values are positive integers. Other will have to be done in the Action class because it requires some logic, like the second range value is >= than the first one.
When the user clicks on any of the 'Generate' buttons, the PDF file will be generated and then the user gets a message: 'File generated, click here to download'. Then when clicking on the link she gets to download the actual file. In other words, to get the file is a two steps process: generation, then download. This is to avoid having in memory a potentially huge amount of data to transfer to the client. It is better to put the results in a file, and then use the web server to get the file to the user.
Server tier modifications:
The first new method to create will be InvoiceSessionBean.generatePDFFile. This is a remote method and will be the only one exposed to the client. It takes a Map as the first argument to accommodate for the three different filters in just one method. So, the client will put the parameters in this map, and this method will realize which filter is actually being requested by checking the contents of this map. It will then call the business logic methods in InvoiceBL:
The business logic methods will find the invoices that will be included in the PDF. These methods will be in InvoiceBL, and use direct SQL through JDBC to find the invoices in the database. We need: InvoiceBL.getInvoicesByUserId(Integer userId) and InvoiceBL.getInvoicesByIdRange(Integer from, Integer to, Integer entityId). The third one, to find by the billing process, is already there (getInvoicesByProcessId). Take a look to this method to build the other two, as they should follow the same mechanics. They should return a CachedRowSet with the ID of the invoice as a first (and only) column, ordered by ID.
You have to do a final validation step at this point. Basically, whatever the user is requesting has to belong to the entity (company) that the user belongs to. For example, if the user is requesting all the invoices for customer id 1234, then make sure that customer 1234 belongs to the same entity id of the executing user. For this, the easiest way is to pass the entity id directly from the client to the server tier (see multiple examples of this in many *Actions.java files). Then, fetch the customer record with user = UserBL(userid) and validate with user.getEntity().getEntity().getId().equals(entityid);
Now we need the method PaperInvoiceBatchBL.generateFile(CachedRowSet). This method will:
1 Generate a unique id, probably a hash id with the time involved for uniqueness.
2 Loop over the row set, assuming that the first row has the id of an invoice. For each row:
2.1 Fetch the invoice from the db with InvoiceBL(invoiceid)
2.2 Call another method that generates the PDF file for this single file
3 Now that all the files are created, call another method that takes the unique id and compiles all the files into a single one that has this unique id in the name.
For the method mentioned in 2.2: This will be NotificationBL.generatePaperInvoiceAsFile(invoiceid). The code in this method is basically the lines 425-432 from NotificationBL. Make sure your replace those lines by a call to this method as well.
For the method mentioned in 3, you can (again) take code already present and modify it to better fit this requirements. This time the code to refractor is PaperInvoiceBatch.compileInvoiceFiles. It now takes the entity id, just to put it in the final file name. It should take a generic String so it can take the entityId as it does now to serve the billing process, but also the unique id (of point 1) to uniquely identify this file. One thing to change is the location where the compiled file is generated. It now has to be located in a directory visible to the web server so a link can be created and the user can download the file directly. That is why we need a unique string as part of the file name. It is clear that these generated files will not be deleted in any of these steps, and that is OK. The files will be deleted as part of a daily maintenance cron (not part of this development), so don't worry about them.
The following is a sequence diagram that illustrates the main steps that the server tier will follow:

Database tier modifications:
No tables or columns have to be changed/added. You do have to write two SQL statements one for the invoice ID range filter and one for the billing process filter. These have to go into InvoiceSQL.
Changes mostly to be done in these files:
InvoiceBL
InvoiceSQL
InvoiceSessionBean
PaperInvoiceBatchBL
NotificationBL
/invoice/downloadBatch.jsp (new file)
/client/invoice/DownloadAction.java (new file)
struts-config.xml (new struts action configuration)
tiles-defs.xml (new tile definition for new page)
ApplicationResources.properties (for new field names and messages in the new page)
This task has been completed. See how
here
I have implemented the
I have implemented the NotificationBL.generatePaperInvoiceAsFile like this:
public String generatePaperInvoiceAsFile(Integer invoiceId) throws SessionInternalError{
try {
JNDILookup EJBFactory = JNDILookup.getFactory(false);
InvoiceEntityLocalHome invoiceHome = (InvoiceEntityLocalHome) EJBFactory.lookUpLocalHome(
InvoiceEntityLocalHome.class, InvoiceEntityLocalHome.JNDI_NAME);
InvoiceEntityLocal invoice = invoiceHome.findByPrimaryKey(invoiceId);
InvoiceBL invoiceOb = new InvoiceBL(invoiceId);
UserBL user = new UserBL(invoiceOb.getEntity().getUser());
Integer entityId = user.getEntity().getEntity().getId();
MessageDTO paperMsg = getInvoicePaperMessage(
entityId,
null,
invoice.getUser().getLanguageIdField(), invoice);
PaperInvoiceNotificationTask task =
new PaperInvoiceNotificationTask();
PluggableTaskBL taskBL = new PluggableTaskBL();
taskBL.set(entityId, Constants.PLUGGABLE_TASK_T_PAPER_INVOICE);
task.initializeParamters(taskBL.getEntity());
String filename = task.getPDFFile(invoice.getUser(), paperMsg);
return filename;
} catch (Exception e) {
throw new SessionInternalError(e);
}
}
Anyone has any ideea?
Marius
but I get an error when I run it:
00:48:35,468 ERROR [NotificationBL] Exception generating paper invoice
dori.jasper.engine.JRException: No input source supplied to the exporter.
at dori.jasper.engine.JRAbstractExporter.setInput(JRAbstractExporter.java:182)
at dori.jasper.engine.export.JRPdfExporter.exportReport(JRPdfExporter.java:201)
at dori.jasper.engine.JasperExportManager.exportReportToPdfFile(JasperExportManager.java:143)
at com.sapienter.jbilling.server.notification.NotificationBL.generatePaperInvoiceAsFile(NotificationBL.java:747)
at com.sapienter.jbilling.server.pluggableTask.PaperInvoiceNotificationTask.getPDFFile(PaperInvoiceNotificationTask.java:122)
at com.sapienter.jbilling.server.notification.NotificationBL.generatePaperInvoiceAsFile(NotificationBL.java:1092)
at com.sapienter.jbilling.server.invoice.PaperInvoiceBatchBL.generateFile(PaperInvoiceBatchBL.java:207)
at com.sapienter.jbilling.server.invoice.InvoiceSessionBean.generatePDFFile(InvoiceSessionBean.java:339)