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.

  1. package ru.ibase.fbjavaex.controllers;
  2. import java.sql.Timestamp;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import java.util.Date;
  6. import java.text.ParseException;
  7. import java.text.SimpleDateFormat;
  8. import java.beans.PropertyEditorSupport;
  9. import javax.ws.rs.core.MediaType;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.stereotype.Controller;
  12. import org.springframework.ui.ModelMap;
  13. import org.springframework.web.bind.annotation.RequestMapping;
  14. import org.springframework.web.bind.annotation.RequestMethod;
  15. import org.springframework.web.bind.annotation.RequestParam;
  16. import org.springframework.web.bind.annotation.ResponseBody;
  17. import org.springframework.web.bind.annotation.InitBinder;
  18. import org.springframework.web.bind.WebDataBinder;
  19. import ru.ibase.fbjavaex.jqgrid.JqGridInvoice;
  20. import ru.ibase.fbjavaex.jqgrid.JqGridInvoiceLine;
  21. import ru.ibase.fbjavaex.managers.InvoiceManager;
  22. import ru.ibase.fbjavaex.jqgrid.JqGridData;
  23. /**
  24. * Invoice controller
  25. *
  26. * @author Simonov Denis
  27. */
  28. @Controller
  29. public class InvoiceController {
  30. @Autowired(required = true)
  31. private JqGridInvoice invoiceGrid;
  32. @Autowired(required = true)
  33. private JqGridInvoiceLine invoiceLineGrid;
  34. @Autowired(required = true)
  35. private InvoiceManager invoiceManager;
  36. /**
  37. * Describe how a string is converted to a date
  38. * from the input parameters of the HTTP request
  39. *
  40. * @param binder
  41. */
  42. @InitBinder
  43. public void initBinder(WebDataBinder binder) {
  44. binder.registerCustomEditor(Timestamp.class,
  45. new PropertyEditorSupport() {
  46. @Override
  47. public void setAsText(String value) {
  48. try {
  49. if ((value == null) || (value.isEmpty())) {
  50. setValue(null);
  51. } else {
  52. Date parsedDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
  53. .parse(value);
  54. setValue(new Timestamp(parsedDate.getTime()));
  55. }
  56. } catch (ParseException e) {
  57. throw new java.lang.IllegalArgumentException(value);
  58. }
  59. }
  60. });
  61. }
  62. /**
  63. * Default action
  64. * Returns the JSP name of the page (view) to display
  65. *
  66. * @param map
  67. * @return JSP page name
  68. */
  69. @RequestMapping(value = "/invoice/", method = RequestMethod.GET)
  70. public String index(ModelMap map) {
  71. return "invoice";
  72. }
  73. /**
  74. * Returns a list of invoices in JSON format for jqGrid
  75. *
  76. * @param rows number of entries per page
  77. * @param page current page number
  78. * @param sIdx sort field
  79. * @param sOrd sorting order
  80. * @param search search flag
  81. * @param searchField search field
  82. * @param searchString search value
  83. * @param searchOper comparison operation
  84. * @param filters filter
  85. * @return
  86. */
  87. @RequestMapping(value = "/invoice/getdata",
  88. method = RequestMethod.GET,
  89. produces = MediaType.APPLICATION_JSON)
  90. @ResponseBody
  91. public JqGridData getData(
  92. @RequestParam(value = "rows", required = false,
  93. defaultValue = "20") int rows,
  94. @RequestParam(value = "page", required = false,
  95. defaultValue = "1") int page,
  96. @RequestParam(value = "sidx", required = false,
  97. defaultValue = "") String sIdx,
  98. @RequestParam(value = "sord", required = false,
  99. defaultValue = "asc") String sOrd,
  100. @RequestParam(value = "_search", required = false,
  101. defaultValue = "false") Boolean search,
  102. @RequestParam(value = "searchField", required = false,
  103. defaultValue = "") String searchField,
  104. @RequestParam(value = "searchString", required = false,
  105. defaultValue = "") String searchString,
  106. @RequestParam(value = "searchOper", required = false,
  107. defaultValue = "") String searchOper,
  108. @RequestParam(value = "filters", required = false,
  109. defaultValue = "") String filters) {
  110. if (search) {
  111. invoiceGrid.setSearchCondition(searchField, searchString, searchOper);
  112. }
  113. invoiceGrid.setLimit(rows);
  114. invoiceGrid.setPageNo(page);
  115. invoiceGrid.setOrderBy(sIdx, sOrd);
  116. return invoiceGrid.getJqGridData();
  117. }
  118. /**
  119. * Add invoice
  120. *
  121. * @param customerId customer id
  122. * @param invoiceDate invoice date
  123. * @return
  124. */
  125. @RequestMapping(value = "/invoice/create",
  126. method = RequestMethod.POST,
  127. produces = MediaType.APPLICATION_JSON)
  128. @ResponseBody
  129. public Map<String, Object> addInvoice(
  130. @RequestParam(value = "CUSTOMER_ID", required = true,
  131. defaultValue = "0") Integer customerId,
  132. @RequestParam(value = "INVOICE_DATE", required = false,
  133. defaultValue = "") Timestamp invoiceDate) {
  134. Map<String, Object> map = new HashMap<>();
  135. try {
  136. invoiceManager.create(customerId, invoiceDate);
  137. map.put("success", true);
  138. } catch (Exception ex) {
  139. map.put("error", ex.getMessage());
  140. }
  141. return map;
  142. }
  143. /**
  144. * Edit invoice
  145. *
  146. * @param invoiceId invoice id
  147. * @param customerId customer id
  148. * @param invoiceDate invoice date
  149. * @return
  150. */
  151. @RequestMapping(value = "/invoice/edit",
  152. method = RequestMethod.POST,
  153. produces = MediaType.APPLICATION_JSON)
  154. @ResponseBody
  155. public Map<String, Object> editInvoice(
  156. @RequestParam(value = "INVOICE_ID", required = true,
  157. defaultValue = "0") Integer invoiceId,
  158. @RequestParam(value = "CUSTOMER_ID", required = true,
  159. defaultValue = "0") Integer customerId,
  160. @RequestParam(value = "INVOICE_DATE", required = false,
  161. defaultValue = "") Timestamp invoiceDate) {
  162. Map<String, Object> map = new HashMap<>();
  163. try {
  164. invoiceManager.edit(invoiceId, customerId, invoiceDate);
  165. map.put("success", true);
  166. } catch (Exception ex) {
  167. map.put("error", ex.getMessage());
  168. }
  169. return map;
  170. }
  171. /**
  172. * Pays an invoice
  173. *
  174. * @param invoiceId invoice id
  175. * @return
  176. */
  177. @RequestMapping(value = "/invoice/pay",
  178. method = RequestMethod.POST,
  179. produces = MediaType.APPLICATION_JSON)
  180. @ResponseBody
  181. public Map<String, Object> payInvoice(
  182. @RequestParam(value = "INVOICE_ID", required = true,
  183. defaultValue = "0") Integer invoiceId) {
  184. Map<String, Object> map = new HashMap<>();
  185. try {
  186. invoiceManager.pay(invoiceId);
  187. map.put("success", true);
  188. } catch (Exception ex) {
  189. map.put("error", ex.getMessage());
  190. }
  191. return map;
  192. }
  193. /**
  194. * Delete invoice
  195. *
  196. * @param invoiceId invoice id
  197. * @return
  198. */
  199. @RequestMapping(value = "/invoice/delete",
  200. method = RequestMethod.POST,
  201. produces = MediaType.APPLICATION_JSON)
  202. @ResponseBody
  203. public Map<String, Object> deleteInvoice(
  204. @RequestParam(value = "INVOICE_ID", required = true,
  205. defaultValue = "0") Integer invoiceId) {
  206. Map<String, Object> map = new HashMap<>();
  207. try {
  208. invoiceManager.delete(invoiceId);
  209. map.put("success", true);
  210. } catch (Exception ex) {
  211. map.put("error", ex.getMessage());
  212. }
  213. return map;
  214. }
  215. /**
  216. * Returns invoice item
  217. *
  218. * @param invoice_id invoice id
  219. * @return
  220. */
  221. @RequestMapping(value = "/invoice/getdetaildata",
  222. method = RequestMethod.GET,
  223. produces = MediaType.APPLICATION_JSON)
  224. @ResponseBody
  225. public JqGridData getDetailData(
  226. @RequestParam(value = "INVOICE_ID", required = true) int invoice_id) {
  227. invoiceLineGrid.setInvoiceId(invoice_id);
  228. return invoiceLineGrid.getJqGridData();
  229. }
  230. /**
  231. * Add invoice item
  232. *
  233. * @param invoiceId invoice id
  234. * @param productId product id
  235. * @param quantity quantity of products
  236. * @return
  237. */
  238. @RequestMapping(value = "/invoice/createdetail",
  239. method = RequestMethod.POST,
  240. produces = MediaType.APPLICATION_JSON)
  241. @ResponseBody
  242. public Map<String, Object> addInvoiceLine(
  243. @RequestParam(value = "INVOICE_ID", required = true,
  244. defaultValue = "0") Integer invoiceId,
  245. @RequestParam(value = "PRODUCT_ID", required = true,
  246. defaultValue = "0") Integer productId,
  247. @RequestParam(value = "QUANTITY", required = true,
  248. defaultValue = "0") Integer quantity) {
  249. Map<String, Object> map = new HashMap<>();
  250. try {
  251. invoiceManager.addInvoiceLine(invoiceId, productId, quantity);
  252. map.put("success", true);
  253. } catch (Exception ex) {
  254. map.put("error", ex.getMessage());
  255. }
  256. return map;
  257. }
  258. /**
  259. * Edit invoice item
  260. *
  261. * @param invoiceLineId invoice item id
  262. * @param quantity quantity of products
  263. * @return
  264. */
  265. @RequestMapping(value = "/invoice/editdetail",
  266. method = RequestMethod.POST,
  267. produces = MediaType.APPLICATION_JSON)
  268. @ResponseBody
  269. public Map<String, Object> editInvoiceLine(
  270. @RequestParam(value = "INVOICE_LINE_ID", required = true,
  271. defaultValue = "0") Integer invoiceLineId,
  272. @RequestParam(value = "QUANTITY", required = true,
  273. defaultValue = "0") Integer quantity) {
  274. Map<String, Object> map = new HashMap<>();
  275. try {
  276. invoiceManager.editInvoiceLine(invoiceLineId, quantity);
  277. map.put("success", true);
  278. } catch (Exception ex) {
  279. map.put("error", ex.getMessage());
  280. }
  281. return map;
  282. }
  283. /**
  284. * Delete invoice item
  285. *
  286. * @param invoiceLineId invoice item id
  287. * @return
  288. */
  289. @RequestMapping(value = "/invoice/deletedetail",
  290. method = RequestMethod.POST,
  291. produces = MediaType.APPLICATION_JSON)
  292. @ResponseBody
  293. public Map<String, Object> deleteInvoiceLine(
  294. @RequestParam(value = "INVOICE_LINE_ID", required = true,
  295. defaultValue = "0") Integer invoiceLineId) {
  296. Map<String, Object> map = new HashMap<>();
  297. try {
  298. invoiceManager.deleteInvoiceLine(invoiceLineId);
  299. map.put("success", true);
  300. } catch (Exception ex) {
  301. map.put("error", ex.getMessage());
  302. }
  303. return map;
  304. }
  305. }

The invoice controller is very similar to the primary module controllers except for two things:

  1. The controller displays and works with the data of both the main grid and the detail grid

  2. 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.

  1. @Configuration
  2. @ComponentScan("ru.ibase.fbjavaex")
  3. @EnableWebMvc
  4. public class WebAppConfig extends WebMvcConfigurerAdapter {
  5. @Override
  6. public void configureMessageConverters(
  7. List<HttpMessageConverter<?>> httpMessageConverters) {
  8. MappingJackson2HttpMessageConverter jsonConverter =
  9. new MappingJackson2HttpMessageConverter();
  10. ObjectMapper objectMapper = new ObjectMapper();
  11. objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
  12. false);
  13. jsonConverter.setObjectMapper(objectMapper);
  14. httpMessageConverters.add(jsonConverter);
  15. }
  16. }

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.