7.10.3. Invoice Controller Class
Now we move on to writing the controller. The input point of our controller will be the index
method, that is responsible for displaying the JSP page (view). This page contains the layout for displaying the grid and the tool and navigation bars.
Data for displaying invoice headers are loaded asynchronously by the jqGrid
component (the path is /invoice/getdata
). The getData
method is connected with this path, similarly to the primary modules. Invoice items are returned by the getDetailData
method (the path is /invoice/getdetaildata
). The primary key of the invoice whose detail grid is currently open is passed to this method.
The methods implemented are addInvoice
, editInvoice
, deleteInvoice
, payInvoice
for invoice headers and addInvoiceLine
, editInvoiceLine
, deleteInvoiceLine
for invoice line items.
package ru.ibase.fbjavaex.controllers;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.beans.PropertyEditorSupport;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.WebDataBinder;
import ru.ibase.fbjavaex.jqgrid.JqGridInvoice;
import ru.ibase.fbjavaex.jqgrid.JqGridInvoiceLine;
import ru.ibase.fbjavaex.managers.InvoiceManager;
import ru.ibase.fbjavaex.jqgrid.JqGridData;
/**
* Invoice controller
*
* @author Simonov Denis
*/
@Controller
public class InvoiceController {
@Autowired(required = true)
private JqGridInvoice invoiceGrid;
@Autowired(required = true)
private JqGridInvoiceLine invoiceLineGrid;
@Autowired(required = true)
private InvoiceManager invoiceManager;
/**
* Describe how a string is converted to a date
* from the input parameters of the HTTP request
*
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Timestamp.class,
new PropertyEditorSupport() {
@Override
public void setAsText(String value) {
try {
if ((value == null) || (value.isEmpty())) {
setValue(null);
} else {
Date parsedDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.parse(value);
setValue(new Timestamp(parsedDate.getTime()));
}
} catch (ParseException e) {
throw new java.lang.IllegalArgumentException(value);
}
}
});
}
/**
* Default action
* Returns the JSP name of the page (view) to display
*
* @param map
* @return JSP page name
*/
@RequestMapping(value = "/invoice/", method = RequestMethod.GET)
public String index(ModelMap map) {
return "invoice";
}
/**
* Returns a list of invoices in JSON format for jqGrid
*
* @param rows number of entries per page
* @param page current page number
* @param sIdx sort field
* @param sOrd sorting order
* @param search search flag
* @param searchField search field
* @param searchString search value
* @param searchOper comparison operation
* @param filters filter
* @return
*/
@RequestMapping(value = "/invoice/getdata",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public JqGridData getData(
@RequestParam(value = "rows", required = false,
defaultValue = "20") int rows,
@RequestParam(value = "page", required = false,
defaultValue = "1") int page,
@RequestParam(value = "sidx", required = false,
defaultValue = "") String sIdx,
@RequestParam(value = "sord", required = false,
defaultValue = "asc") String sOrd,
@RequestParam(value = "_search", required = false,
defaultValue = "false") Boolean search,
@RequestParam(value = "searchField", required = false,
defaultValue = "") String searchField,
@RequestParam(value = "searchString", required = false,
defaultValue = "") String searchString,
@RequestParam(value = "searchOper", required = false,
defaultValue = "") String searchOper,
@RequestParam(value = "filters", required = false,
defaultValue = "") String filters) {
if (search) {
invoiceGrid.setSearchCondition(searchField, searchString, searchOper);
}
invoiceGrid.setLimit(rows);
invoiceGrid.setPageNo(page);
invoiceGrid.setOrderBy(sIdx, sOrd);
return invoiceGrid.getJqGridData();
}
/**
* Add invoice
*
* @param customerId customer id
* @param invoiceDate invoice date
* @return
*/
@RequestMapping(value = "/invoice/create",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> addInvoice(
@RequestParam(value = "CUSTOMER_ID", required = true,
defaultValue = "0") Integer customerId,
@RequestParam(value = "INVOICE_DATE", required = false,
defaultValue = "") Timestamp invoiceDate) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.create(customerId, invoiceDate);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
/**
* Edit invoice
*
* @param invoiceId invoice id
* @param customerId customer id
* @param invoiceDate invoice date
* @return
*/
@RequestMapping(value = "/invoice/edit",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> editInvoice(
@RequestParam(value = "INVOICE_ID", required = true,
defaultValue = "0") Integer invoiceId,
@RequestParam(value = "CUSTOMER_ID", required = true,
defaultValue = "0") Integer customerId,
@RequestParam(value = "INVOICE_DATE", required = false,
defaultValue = "") Timestamp invoiceDate) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.edit(invoiceId, customerId, invoiceDate);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
/**
* Pays an invoice
*
* @param invoiceId invoice id
* @return
*/
@RequestMapping(value = "/invoice/pay",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> payInvoice(
@RequestParam(value = "INVOICE_ID", required = true,
defaultValue = "0") Integer invoiceId) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.pay(invoiceId);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
/**
* Delete invoice
*
* @param invoiceId invoice id
* @return
*/
@RequestMapping(value = "/invoice/delete",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> deleteInvoice(
@RequestParam(value = "INVOICE_ID", required = true,
defaultValue = "0") Integer invoiceId) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.delete(invoiceId);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
/**
* Returns invoice item
*
* @param invoice_id invoice id
* @return
*/
@RequestMapping(value = "/invoice/getdetaildata",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public JqGridData getDetailData(
@RequestParam(value = "INVOICE_ID", required = true) int invoice_id) {
invoiceLineGrid.setInvoiceId(invoice_id);
return invoiceLineGrid.getJqGridData();
}
/**
* Add invoice item
*
* @param invoiceId invoice id
* @param productId product id
* @param quantity quantity of products
* @return
*/
@RequestMapping(value = "/invoice/createdetail",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> addInvoiceLine(
@RequestParam(value = "INVOICE_ID", required = true,
defaultValue = "0") Integer invoiceId,
@RequestParam(value = "PRODUCT_ID", required = true,
defaultValue = "0") Integer productId,
@RequestParam(value = "QUANTITY", required = true,
defaultValue = "0") Integer quantity) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.addInvoiceLine(invoiceId, productId, quantity);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
/**
* Edit invoice item
*
* @param invoiceLineId invoice item id
* @param quantity quantity of products
* @return
*/
@RequestMapping(value = "/invoice/editdetail",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> editInvoiceLine(
@RequestParam(value = "INVOICE_LINE_ID", required = true,
defaultValue = "0") Integer invoiceLineId,
@RequestParam(value = "QUANTITY", required = true,
defaultValue = "0") Integer quantity) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.editInvoiceLine(invoiceLineId, quantity);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
/**
* Delete invoice item
*
* @param invoiceLineId invoice item id
* @return
*/
@RequestMapping(value = "/invoice/deletedetail",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON)
@ResponseBody
public Map<String, Object> deleteInvoiceLine(
@RequestParam(value = "INVOICE_LINE_ID", required = true,
defaultValue = "0") Integer invoiceLineId) {
Map<String, Object> map = new HashMap<>();
try {
invoiceManager.deleteInvoiceLine(invoiceLineId);
map.put("success", true);
} catch (Exception ex) {
map.put("error", ex.getMessage());
}
return map;
}
}
The invoice controller is very similar to the primary module controllers except for two things:
The controller displays and works with the data of both the main grid and the detail grid
Invoices are filtered by the date field so that only those invoices that are included in the work period are displayed
Working with Dates in Java
Working with dates in Java throws up a few quirks.
The java.sql.Timestamp
type in Java supports precision up to nanoseconds whereas the maximum precision of the TIMESTAMP
type in Firebird is one ten-thousandth of a second. That is not really a significant problem.
Date and time types in Java support working with time zones. Firebird does not currently support the TIMESTAMP WITH TIME ZONE
type. Java works on the assumption that dates in the database are stored in the time zone of the server. However, time will be converted to UTC during serialization into JSON. It must be taken into account when processing time data in JavaScript.
Attention! Java takes the time offset from its own time zone database, not from the operating system. This practice considerably increases the need to keep up with the latest version of JDK. If you have some old version of JDK installed, working with date and time may be incorrect. |
By default, a date is serialized into JSON in as the number of nanoseconds since January 1, 1970, which is not always what is wanted. A date can be serialized into a text representation, by setting to False the date conversion configuration property SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
date conversion in the configureMessageConverters
method of the WebAppConfig
class.
We will return to date processing a little later.
@Configuration
@ComponentScan("ru.ibase.fbjavaex")
@EnableWebMvc
public class WebAppConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> httpMessageConverters) {
MappingJackson2HttpMessageConverter jsonConverter =
new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
false);
jsonConverter.setObjectMapper(objectMapper);
httpMessageConverters.add(jsonConverter);
}
…
}
The initBinder
method of the InvoiceController
controller describes how the text representation of a date sent by the browser is converted into a value of type Timestamp.