Retail:Developers Guide/Main Development Concepts
Contents |
Introduction
This document provides an overview of the Openbravo for Retail main development concepts.
Main development concepts
Component provider
Because openbravo WebPOS is a module of openbravo ERP, it uses a component provider to serve needed resources.
This component provider can be extended by other modules to add or overwrite resources.
In this piece of code, some resources are added to webPOS from other module using a component provider
@ApplicationScoped @ComponentProvider.Qualifier(EPSComponentProvider.QUALIFIER) public class EPSComponentProvider extends BaseComponentProvider { public static final String QUALIFIER = "ELPS_Main"; public static final String MODULE_JAVA_PACKAGE = "org.openbravo.paymentgateway.elementps"; @Override public Component getComponent(String componentId, Map<String, Object> parameters) { throw new IllegalArgumentException("Component id " + componentId + " not supported."); } @Override public List<ComponentResource> getGlobalComponentResources() { final List<ComponentResource> globalResources = new ArrayList<ComponentResource>(); final String prefix = "web/" + MODULE_JAVA_PACKAGE + "/js/"; globalResources.add(createComponentResource(ComponentResourceType.Static, prefix + "hostedpayments.js", POSUtils.APP_NAME)); return globalResources; } } |
Datasources
A datasource is a concept to define several tools which connect the client side with the server side. In the server side, a datasource is a servlet which is able to send data to the client or to process the data sent by client. In the client side, datasource are a javascript tool which facilitates the task of retrieve data from server or send data to server.
Retrieve data
The servlets which extends from class ProcessHQLQuery are able to execute a determined HQL query and send the selected data to the client. In the following case, the selected data of Business Partner will be send to the client side when the client request it.
public class BusinessPartner extends ProcessHQLQuery { @Override protected String getQuery(JSONObject jsonsent) throws JSONException { Organization org = POSUtils.getOrganization(jsonsent.getString("organization")); return "SELECT bpl.businessPartner.id as id, bpl.businessPartner.name as name, bpl.businessPartner.name as _identifier, bpl.businessPartner.searchKey as searchKey, bpl.businessPartner.description as description, bpl.businessPartner.taxID as taxId, bpl.businessPartner.sOBPTaxCategory.id as taxCategory, bpl.businessPartner.paymentMethod.id as paymentMethod, bpl.businessPartner.paymentTerms.id as paymentTerms, bpl.businessPartner.invoiceTerms as invoiceTerms, bpl.id as locId, max(bpl.name) as locName " + "FROM BusinessPartnerLocation AS bpl " + "WHERE (" + "(bpl.id = '" + org.getObretcoCBpLocation().getId() + "')" + " OR " + "(bpl.businessPartner.id <> '" + org.getObretcoCBpartner().getId() + "' AND " + "bpl.invoiceToAddress = true AND " + "bpl.businessPartner.customer = true AND " + "$readableClientCriteria AND " + "$naturalOrgCriteria" + "))" + "GROUP BY bpl.businessPartner.id, bpl.businessPartner.name, bpl.businessPartner.name, bpl.businessPartner.searchKey, bpl.businessPartner.description, bpl.businessPartner.taxID, bpl.businessPartner.sOBPTaxCategory.id, bpl.businessPartner.paymentMethod.id, bpl.businessPartner.paymentTerms.id, bpl.businessPartner.invoiceTerms, bpl.id " + "ORDER BY bpl.businessPartner.name"; } } |
In clients side exist a model which corresponds with business Partner data. Because business parter is master data, the data will be stored locally (WEBSQL) allowing to work with this data when we are working offline. Thanks to client datasource, when this model is required by a window the server will be called automatically and the retrieved data will be inserted in the database (WEBSQL)
var BusinessPartner = Backbone.Model.extend({ modelName: 'BusinessPartner', tableName: 'c_bpartner', entityName: 'BusinessPartner', source: 'org.openbravo.retail.posterminal.master.BusinessPartner', properties: [ 'id', 'searchKey', 'name', 'description', 'taxId', 'taxCategory', 'paymentMethod', 'paymentTerms', 'invoiceTerms', 'locId', 'locName', '_identifier', '_idx' ], propertyMap: { 'id': 'c_bpartner_id', 'searchKey': 'value', 'name': 'name', 'description': 'description', 'taxId': 'taxID', 'taxCategory': 'so_bp_taxcategory_id', 'paymentMethod': 'FIN_Paymentmethod_ID', 'paymentTerms': 'c_paymentterm_id', 'invoiceTerms': 'invoicerule', 'locId': 'c_bpartnerlocation_id', 'locName': 'c_bpartnerlocation_name', '_identifier': '_identifier', '_idx': '_idx' }, createStatement: 'CREATE TABLE IF NOT EXISTS c_bpartner (c_bpartner_id TEXT PRIMARY KEY , value TEXT , name TEXT , description TEXT , taxID TEXT , so_bp_taxcategory_id TEXT, FIN_Paymentmethod_ID TEXT, c_paymentterm_id TEXT, invoicerule TEXT, c_bpartnerlocation_id TEXT , c_bpartnerlocation_name TEXT , _identifier TEXT , _idx NUMERIC)', dropStatement: 'DROP TABLE IF EXISTS c_bpartner', insertStatement: 'INSERT INTO c_bpartner(c_bpartner_id, value, name, description, taxID, so_bp_taxcategory_id, FIN_Paymentmethod_ID, c_paymentterm_id, invoicerule, c_bpartnerlocation_id, c_bpartnerlocation_name, _identifier, _idx) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', updateStatement: '' }); An array of models is defined in each window model. The models included in that array are required by a window to work OB.OBPOSPointOfSale.Model.PointOfSale = OB.Model.WindowModel.extend({ models: [OB.Model.TaxRate, OB.Model.Product, OB.Model.ProductPrice, OB.Model.ProductCategory, OB.Model.BusinessPartner, OB.Model.Order, OB.Model.DocumentSequence], |
In other cases, we want to retrieve online data (just in this moment). But we don't have a model which correspond with this data. To solve this problem a model will be defined. The property source indicates which servlet will serve the data.
OB.OBPOSCashUp.Model.CloseCashPaymentMethod = Backbone.Model.extend({ source: 'org.openbravo.retail.posterminal.term.CloseCashPayments', modelName: 'DataCloseCashPaymentMethod', online: true }); Also, the window should include this model in its array of models OB.OBPOSCashUp.Model.CashUp = OB.Model.WindowModel.extend({ models: [OB.OBPOSCashUp.Model.CloseCashPaymentMethod, OB.OBPOSCashUp.Model.CashCloseReport, OB.Model.Order], Finally, to get the data from a online model getData is used using the name of the model as parameter this.set('paymentList', this.getData('DataCloseCashPaymentMethod')); thanks to datasource tools, the data is retrieved easily from server side where this class is defined: public class CloseCashPayments extends ProcessHQLQuery { @Override protected String getQuery(JSONObject jsonsent) throws JSONException { return "select p.id as id, p.searchKey as _id, p.commercialName as name, f.currentBalance as expected, p.paymentMethod as paymentMethod from OBPOS_App_Payment as p, FIN_Financial_Account as f " + "where p.financialAccount=f and p.obposApplications.id = :pos order by p.commercialName"; } } |
send data to server (process)
To process data which come from client, we need a servlet which extends from JSONProcessSimple. This servlet will receive the data and a response will be send to the client to inform about the result of this process.
This servlet is used to process a order:
public class ProcessOrder extends JSONProcessSimple { private static final Logger log = Logger.getLogger(ProcessOrder.class); @Override public JSONObject exec(JSONObject jsonsent) throws JSONException, ServletException { Object jsonorder = jsonsent.get("order"); JSONArray array = null; if (jsonorder instanceof JSONObject) { array = new JSONArray(); array.put(jsonorder); } else if (jsonorder instanceof String) { JSONObject obj = new JSONObject((String) jsonorder); array = new JSONArray(); array.put(obj); } else if (jsonorder instanceof JSONArray) { array = (JSONArray) jsonorder; } long t1 = System.currentTimeMillis(); JSONObject result = new OrderLoader().saveOrder(array); log.info("Final total time: " + (System.currentTimeMillis() - t1)); return result; } } |
To send the data from client to server OB.DS.Process Object is used.
//set target var server = new OB.DS.Process('org.openbravo.retail.posterminal.ProcessCashClose'); //send data server.exec(objToSend, function(data, message) { if (data && data.exception) { OB.UTIL.showError(OB.I18N.getLabel('OBPOS_MsgFinishCloseError')); } else { me.set("finished", true); } }); |
Enyo Views
Enyo framework is used for the creation of UI Components and their control. The Kinds and Components are built with an object encapsulation, inheritance and ownership hierarchy.
As an example, a general kind button:
enyo.kind({ name: 'OB.UI.Button', kind: 'enyo.Button', handlers: { onmouseover: 'mouseOverOut', onmouseout: 'mouseOverOut' }, mouseOverOut: function(sender, event) { this.addRemoveClass('btn-over', event.type === 'mouseover'); } }); |
Touch actions are supported and as we can see in the example, event management too.
Backbone Models
One Backbone Model is defined for each window in order to manage all the interactive data and the logic surrounding it. These Models provide functionality for managing changes using its own methods and event handlers.
This is an example:
OB.Model.WindowModel = Backbone.Model.extend({ data: {}, load: function() { var me = this, queue = {}; if (!this.models) { this.models = []; } _.extend(this.models, Backbone.Events); this.models.on('ready', function() { if (this.init) { this.init(); } this.trigger('ready'); }, this); OB.Model.Util.loadModels(true, this.models, this.data); } }); |
To create a Model, you extend Backbone.Model. Subclasses created with extend can be further extended and subclassed as far as you like.
Backbone has dependecy on Underscore library and it us used as we can see in the example ( _.extend(this.models, Backbone.Events);).
The use of events is very helpful, we can bind a callback function to an object (this.models.on('ready', function()). The callback will be invoked whenever the event is fired ( this.trigger('ready');).
WebSQL Database & DAL
Web SQL Database is a web page API for storing data in databases. This data will be retrieved from server (thanks to datasource) and stored in Web SQL database.
Web SQL Database is used because Web POS has the ability to work offline. Bussines Partners, Taxes and Product information are loaded in local database at the begining. Sometimes we have the necessity of work offline but we want to be able to sell. In case of working offline, we will work only on the client side, orders will be stored in our Web SQL database and as soon as we go online, the orders will be send to server side to process them.
DAL is an API which comes form mobile core infrastructure module. It has been created to access WEBSQL stored data and it helps to interact with database easily. This Data Access Layer, provide us methods(find, save, delete...) that let us manage local database without using SQL sentences.
You can find information about how Client DAL works here
Back to Concepts