Project:
View Issue Details[ Jump to Notes ] | [ Issue History ] [ Print ] | |||||||
ID | ||||||||
0052052 | ||||||||
Type | Category | Severity | Reproducibility | Date Submitted | Last Update | |||
defect | [POS2] Core | major | have not tried | 2023-04-03 18:18 | 2023-09-26 15:03 | |||
Reporter | AugustoMauch | View Status | public | |||||
Assigned To | AugustoMauch | |||||||
Priority | normal | Resolution | fixed | Fixed in Version | ||||
Status | closed | Fix in branch | Fixed in SCM revision | |||||
Projection | none | ETA | none | Target Version | ||||
OS | Any | Database | Any | Java version | ||||
OS Version | Database version | Ant version | ||||||
Product Version | SCM revision | |||||||
Review Assigned To | ||||||||
Regression level | ||||||||
Regression date | ||||||||
Regression introduced in release | ||||||||
Regression introduced by commit | ||||||||
Triggers an Emergency Pack | No | |||||||
Summary | 0052052: Reduce the number of times the state is persisted on disk | |||||||
Description | The state is being persisted way too often, and that might be a cause of some of the performance problems that have an impact on the 'back to the past” state problem. As part of this issue we will try to reduce the number of times the state is persisted by: Adding a new property to persisted models if true will trigger a throttled persistence just after the state action takes place Increasing the length of redux’s default throttle from 100m to a much longer value (i.e. 30s) This will prevent the state from being persisted on disk every time the user executes any action (log would be generated and TerminalLog is a persisted model state). | |||||||
Steps To Reproduce | - | |||||||
Tags | No tags attached. | |||||||
Attached Files | 52052_mobCore_21Q4.diff [^] (129,079 bytes) 2023-09-26 15:03 [Show Content] [Hide Content]diff --git a/src/org/openbravo/mobile/core/login/MobileCoreLoginUtilsServlet.java b/src/org/openbravo/mobile/core/login/MobileCoreLoginUtilsServlet.java index 7829ea5..335fd83 100644 --- a/src/org/openbravo/mobile/core/login/MobileCoreLoginUtilsServlet.java +++ b/src/org/openbravo/mobile/core/login/MobileCoreLoginUtilsServlet.java @@ -106,6 +106,7 @@ public class MobileCoreLoginUtilsServlet extends WebServiceAbstractServlet { result.put("contextInfo", Context.getContextInfo().get(0)); } + result.put("isTestEnvironment", isTestEnvironment()); result.put("format", getFormat()); // in case of invalid origin then only support this for prerender and report back to the // client @@ -142,6 +143,13 @@ public class MobileCoreLoginUtilsServlet extends WebServiceAbstractServlet { } } + private boolean isTestEnvironment() { + final String testEnvironmentStr = OBPropertiesProvider.getInstance() + .getOpenbravoProperties() + .getProperty("test.environment"); + return testEnvironmentStr != null && "true".equals(testEnvironmentStr); + } + @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { diff --git a/web-test/model/application-state/State.test.js b/web-test/model/application-state/State.test.js index 5006c3a..593b934 100644 --- a/web-test/model/application-state/State.test.js +++ b/web-test/model/application-state/State.test.js @@ -34,7 +34,8 @@ describe('Global State endpoint', () => { persistence = { initialize: jest.fn(), getState: jest.fn(), - dispatch: jest.fn() + dispatch: jest.fn(), + stateStorePersistor: { debouncedFlush: jest.fn() } }; persistence.getState.mockReturnValue({ diff --git a/web/org.openbravo.mobile.core/app/model/application-state/State.js b/web/org.openbravo.mobile.core/app/model/application-state/State.js index 1bb78ba..d69e8e5 100644 --- a/web/org.openbravo.mobile.core/app/model/application-state/State.js +++ b/web/org.openbravo.mobile.core/app/model/application-state/State.js @@ -77,9 +77,22 @@ actionPayload ) => { try { + const previousBackendMsgIds = getBackendMessageIds( + persistence.getState() + ); persistence.dispatch(model, action, actionPayload, { globalState: persistence.getState() }); + if ( + anyNewBackendMessage( + previousBackendMsgIds, + getBackendMessageIds(persistence.getState()) + ) + ) { + persistence.stateStorePersistor.flush(); + } else if (shouldTriggerDebouncedPersistence(persistence, model)) { + persistence.stateStorePersistor.debouncedFlush(); + } } catch (error) { await executeAllActionPreparationRollbacks( OB.App.StateAPI[model][action], @@ -91,6 +104,27 @@ } }; + function getBackendMessageIds(state) { + if (!state || !state.Messages) { + return []; + } + return state.Messages.filter(msg => msg.type === 'backend') + .filter(msg => msg.modelName !== 'OBMOBC_TerminalLog') + .map(msg => msg.id); + } + + function shouldTriggerDebouncedPersistence(persistence, modelName) { + const model = OB.App.StateAPI[modelName]; + + return ( + modelName === 'Global' || (model.isPersisted && model.triggerPersistence) + ); + } + + function anyNewBackendMessage(previous = [], current = []) { + return current.some(msg => !previous.includes(msg)); + } + const getStatePortion = (state, model) => { if (model !== 'Global') { return state[model]; diff --git a/web/org.openbravo.mobile.core/app/model/application-state/StateAPI.js b/web/org.openbravo.mobile.core/app/model/application-state/StateAPI.js index c28cca6..cdc61ce 100644 --- a/web/org.openbravo.mobile.core/app/model/application-state/StateAPI.js +++ b/web/org.openbravo.mobile.core/app/model/application-state/StateAPI.js @@ -40,7 +40,8 @@ 'options', 'bbModel', 'isUndoable', - 'isPersisted' + 'isPersisted', + 'triggerPersistence' ]; const modelRegistry = { @@ -247,6 +248,8 @@ * @param {object} options - An optional object with additional options, which may be: * * isUndoable (boolean): indicates whether the model supports undoing actions * * isPersisted (boolean): indicates whether the model is persisted in the local database or not + * * triggerPersistence (boolean): indicates whether a model update should trigger a debounced (200ms) state persistence. + * If false, the change will be persisted when another model udpate triggers the persistence or when the default state throttling (60s) is triggered * * @throws {Error} - When the model already exists */ @@ -259,6 +262,7 @@ initialState: lodash.cloneDeep(initialState), isUndoable: options.isUndoable || false, isPersisted: options.isPersisted !== false, + triggerPersistence: options.triggerPersistence !== false, ...modelRegistry, ...modelHookRegistry, ...utilitiesRegistry diff --git a/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js b/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js index 600c4e7..9408c85 100644 --- a/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js +++ b/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js @@ -135,7 +135,7 @@ { key: 'root', storage: localforage, - throttle: 100, + throttle: 60000, blacklist: blackList }, rootReducer @@ -154,6 +154,22 @@ // The stateStorePersistor provides operations over the state backup such as forcing flush, removing... this.stateStorePersistor = ReduxPersist.persistStore(this.stateStore); + const { flush } = this.stateStorePersistor; + // function that can be attached to a window unload event to force a state flush + this.stateStorePersistor.persistBeforeUnload = event => { + flush(); + event.preventDefault(); + // eslint-disable-next-line no-param-reassign + event.returnValue = ''; + }; + // to prevent persisting too often in disk (potentially creating a performance problem), + // a debounced version of the persistence flush is provided + const fallbackFunction = () => {}; + this.stateStorePersistor.debouncedFlush = lodash.debounce( + this.stateStorePersistor.flush || fallbackFunction, + 200 + ); + // Ensures store persistence to be reset when localstorage has been cleared up if (!OB.UTIL.localStorage.getItem('stateIsConsistentWithLocalStorage')) { this.stateStorePersistor.purge(); diff --git a/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js.orig b/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js.orig index 42dea94..600c4e7 100644 --- a/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js.orig +++ b/web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js.orig @@ -122,7 +122,7 @@ const rootReducer = this.createRootReducer(globalActionsReducer); localforage.config({ - driver: localforage.INDEXEDDB, + driver: localforage.LOCALSTORAGE, name: `${OB.App.TerminalProperty.get('localDB').name}_state`, storeName: 'State' }); @@ -135,7 +135,7 @@ { key: 'root', storage: localforage, - throttle: 10, + throttle: 100, blacklist: blackList }, rootReducer diff --git a/web/org.openbravo.mobile.core/app/model/business-object/remote-server/RemoteServer.js b/web/org.openbravo.mobile.core/app/model/business-object/remote-server/RemoteServer.js index 5721424..e8347d3 100644 --- a/web/org.openbravo.mobile.core/app/model/business-object/remote-server/RemoteServer.js +++ b/web/org.openbravo.mobile.core/app/model/business-object/remote-server/RemoteServer.js @@ -1,10 +1,15 @@ /* ************************************************************************************ - * Copyright (C) 2021 Openbravo S.L.U. + * Copyright (C) 2021-2023 Openbravo S.L.U. * Licensed under the Openbravo Commercial License version 1.0 * You may obtain a copy of the License at http://www.openbravo.com/legal/obcl.html * or in the legal folder of this module distribution. ************************************************************************************ */ -OB.App.StateAPI.registerModel('RemoteServer', {}); +OB.App.StateAPI.registerModel( + 'RemoteServer', + {}, + // no need to persist changes on this models immediately, it can wait until default persistence throttling (60s) takes place + { triggerPersistence: false } +); diff --git a/web/org.openbravo.mobile.core/app/model/business-object/terminal-log/TerminalLog.js b/web/org.openbravo.mobile.core/app/model/business-object/terminal-log/TerminalLog.js index 1343c61..c1af910 100644 --- a/web/org.openbravo.mobile.core/app/model/business-object/terminal-log/TerminalLog.js +++ b/web/org.openbravo.mobile.core/app/model/business-object/terminal-log/TerminalLog.js @@ -1,6 +1,6 @@ /* ************************************************************************************ - * Copyright (C) 2019-2020 Openbravo S.L.U. + * Copyright (C) 2019-2023 Openbravo S.L.U. * Licensed under the Openbravo Commercial License version 1.0 * You may obtain a copy of the License at http://www.openbravo.com/legal/obcl.html * or in the legal folder of this module distribution. @@ -18,10 +18,15 @@ * events: array containing one item per log line * eventsMaximumNumber: maximun number of log lines per message, rest of log lines are ignored. */ - OB.App.StateAPI.registerModel('TerminalLog', { - context: 'refresh(F5)', - contextPopup: '', - events: [], - eventsMaximumNumber: 10000 - }); + OB.App.StateAPI.registerModel( + 'TerminalLog', + { + context: 'refresh(F5)', + contextPopup: '', + events: [], + eventsMaximumNumber: 10000 + }, + // no need to persist changes on this models immediately, it can wait until default persistence throttling (60s) takes place + { triggerPersistence: false } + ); })(); diff --git a/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js b/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js index e3497a1..0c1466d 100644 --- a/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js +++ b/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js @@ -2028,6 +2028,7 @@ OB.Model.Terminal = Backbone.Model.extend({ return; } me.trigger('initializedCommonComponents'); + me.preLoadContext(function() { if ( !inResponse.contextInfo || @@ -2037,6 +2038,13 @@ OB.Model.Terminal = Backbone.Model.extend({ OB.MobileApp.model.navigate('login'); return; } + + window.OB.UTIL.localStorage.setItem( + 'isTestEnvironment', + inResponse.isTestEnvironment + ); + // when logging in or after refreshing the tab, force a state persistence before closing/refreshing the browser + me.addPersistOnRefreshListener(); OB.MobileApp.model.triggerOnLine(); OB.info('Set isLoggingIn true'); OB.MobileApp.model.set('isLoggingIn', true); @@ -3002,6 +3010,8 @@ OB.Model.Terminal = Backbone.Model.extend({ return; } + // when logging in or after refreshing the tab, force a state persistence before closing/refreshing the browser + this.addPersistOnRefreshListener(); OB.MobileApp.model.set('windowRegistered', undefined); OB.MobileApp.model.set('loggedOffline', true); OB.MobileApp.model.set('datasourceLoadFailed', false); @@ -3046,6 +3056,28 @@ OB.Model.Terminal = Backbone.Model.extend({ OB.debug('next process: none. set your application start point here'); }, + addPersistOnRefreshListener: function() { + // do not do it in test environments as Selenium is not compatible with this feature + if (window.OB.UTIL.localStorage.getItem('isTestEnvironment') === 'true') { + return; + } + window.addEventListener( + 'beforeunload', + OB.App.State.persistence.stateStorePersistor.persistBeforeUnload + ); + }, + + removePersistOnRefreshListener: function() { + // do not do it in test environments as Selenium is not compatible with this feature + if (window.OB.UTIL.localStorage.getItem('isTestEnvironment') === 'true') { + return; + } + window.removeEventListener( + 'beforeunload', + OB.App.State.persistence.stateStorePersistor.persistBeforeUnload + ); + }, + login: function(user, password, mode, command) { OB.info('isLoggingIn ' + this.get('isLoggingIn')); if (this.get('isLoggingIn') === true) { @@ -3320,6 +3352,8 @@ OB.Model.Terminal = Backbone.Model.extend({ } this.preLogoutActions(function() { + // when logged out, no need to force a state persistence before closing/refreshing the browser + me.removePersistOnRefreshListener(); OB.App.SynchronizationBuffer.internalFlush() .then(() => { return OB.App.State.persistence.stateStorePersistor.flush(); diff --git a/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js.orig b/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js.orig deleted file mode 100644 index 8471710..0000000 --- a/web/org.openbravo.mobile.core/source/model/ob-terminal-model.js.orig +++ /dev/null @@ -1,3473 +0,0 @@ -/* - ************************************************************************************ - * Copyright (C) 2012-2021 Openbravo S.L.U. - * Licensed under the Openbravo Commercial License version 1.0 - * You may obtain a copy of the License at http://www.openbravo.com/legal/obcl.html - * or in the legal folder of this module distribution. - ************************************************************************************ - */ - -/* global enyo, ServiceWorkerUtil, CryptoJS */ - -OB.Model = window.OB.Model || {}; -OB.MobileApp = OB.MobileApp || {}; -OB.Data = OB.Data || {}; - -// used for test purposes to keep track that sync dialog was shown -OB.Data.showSynchronizedDialogChangeTime = 0; - -OB.MobileApp.windowRegistry = new (Backbone.Model.extend({ - registeredWindows: [], - - registerWindow: function(window) { - if ( - OB && - OB.MobileApp && - OB.MobileApp.model && - OB.UTIL.isNullOrUndefined(OB.MobileApp.model.get('defaultWindow')) && - window.defaultWindow - ) { - OB.MobileApp.model.set('defaultWindow', window.route); - } else if (window.defaultWindow) { - if (OB && OB.error) { - OB.warn('Default window ignored. ' + window.windowClass); - } else { - OB.warn('Default window ignored. ' + window.windowClass); - } - } - this.registeredWindows.push(window); - } -}))(); - -OB.MobileApp.actionsRegistry = new OB.Actions.Registry(); -OB.MobileApp.statesRegistry = new OB.State.Registry(); - -OB.Model.Collection = Backbone.Collection.extend({ - constructor: function(data) { - this.ds = data.ds; - Backbone.Collection.prototype.constructor.call(this); - }, - inithandler: function(init) { - if (init) { - init.call(this); - } - }, - exec: function(filter) { - var me = this; - if (this.ds) { - this.ds.exec(filter, function(data, info) { - var i; - me.reset(); - me.trigger('info', info); - if (data.exception) { - OB.UTIL.showError(data.exception.message); - } else { - for (i in data) { - if (Object.prototype.hasOwnProperty.call(data, i)) { - me.add(data[i]); - } - } - } - }); - } - } -}); - -// Terminal model. -OB.Model.Terminal = Backbone.Model.extend({ - defaults: { - terminal: null, - context: null, - permissions: null, - businesspartner: null, - location: null, - pricelist: null, - pricelistversion: null, - currency: null, - connectedToERP: null, - terminalLogContextPopUp: [], - loginUtilsUrl: '../../org.openbravo.mobile.core.loginutils', - loginUtilsParams: {}, - supportsOffline: false, - loginHandlerUrl: '../../org.openbravo.mobile.core/LoginHandler', - applicationFormatUrl: - '../../org.openbravo.mobile.core/OBCLKER_Kernel/Application', - logConfiguration: { - deviceIdentifier: '', - logPropertiesExtension: [] - }, - windows: null, - appName: 'OBMOBC', - useBarcode: false, - useBarcodeLayout: null, - useEmbededBarcode: true, - profileOptions: { - showOrganization: true, - showWarehouse: true, - defaultProperties: { - role: null, - organization: null, - warehouse: null, - languagage: null - } - }, - localDB: { - size: OB.UTIL.VersionManagement.current.mobileCore.WebSQLDatabase.size, - name: OB.UTIL.VersionManagement.current.mobileCore.WebSQLDatabase.name, - displayName: - OB.UTIL.VersionManagement.current.mobileCore.WebSQLDatabase.displayName - }, - shouldExecuteBenchmark: false, - propertiesLoaders: [], - dataSyncModels: [] - }, - - initialize: function() { - var me = this; - - // expose terminal globally - window.OB.MobileApp = OB.MobileApp || {}; - window.OB.MobileApp.model = this; - - OB.UTIL.HookManager.executeHooks('OBMOBC_InitActions', {}, function(args) { - if (args && args.cancelOperation && args.cancelOperation === true) { - return; - } else { - me.initActions(function() {}); - } - }); - - // attach objects to model - this.router = new OB.Model.RouterHelper(this); - this.windowRegistry = OB.MobileApp.windowRegistry; - OB.UTIL.VersionManagement.deprecated( - 27349, - function() { - this.hookManager = OB.UTIL.HookManager; - }, - function(deprecationMessage) { - this.hookManager = { - registerHook: function(qualifier, func) { - OB.warn(deprecationMessage + ". Hook: '" + qualifier + "'"); - OB.UTIL.HookManager.registerHook(qualifier, func); - }, - executeHooks: function(qualifier, args, callback) { - OB.warn(deprecationMessage + ". Hook: '" + qualifier + "'"); - OB.UTIL.HookManager.executeHooks(qualifier, args, callback); - }, - callbackExecutor: function(args, callback) { - OB.warn(deprecationMessage); - OB.UTIL.HookManager.callbackExecutor(args, callback); - } - }; - }, - this - ); - - // DEVELOPER: activate this to see all the events that are being fired by the backbone components of the terminal - // this.on('all', function(eventName, object, third) { - // if (eventName.indexOf('change') >= 0) { - // return; - // } - // var enyoName = object ? object.name : ''; - // OB.info('event fired: ' + eventName + ', ' + enyoName + ', ' + third); - // }); - // model events - this.on('window:ready', this.renderContainerWindow); // When the active window is loaded, the window is rendered with the renderContainerWindow function - this.on('terminalInfoLoaded', function() { - OB.debug('next process: processPropertyLoaders'); - OB.UTIL.localStorage.setItem( - 'cacheAvailableForUser:' + OB.MobileApp.model.get('orgUserId'), - true - ); - this.processPropertyLoaders(); - }); - this.on( - 'propertiesLoadersReady', - function() { - OB.debug( - 'next process: loadRegisteredWindows, renderMain, postLoginActions' - ); - - OB.UTIL.completeLoadingStep(); - me.saveBrowserTabId(); - // Now we can start synchronizing pending messages with the backend server - OB.App.RemoteServerController.getRemoteServer( - 'BackendServer' - ).connectSynchronizationEndpoints(); - var callbackAlreadyExecuted = false; - - this.loadRegisteredWindows(function() { - if (!callbackAlreadyExecuted) { - callbackAlreadyExecuted = true; - me.renderMain(); - me.postLoginActions(); - } - }); - }, - this - ); - - // when the page has finished loading the components, load the root webpage - window.addEventListener( - 'load', - function(e) { - OB.MobileApp.model.initializeGlobalState(); - // verifying that all dataSyncModels have modelFunc defined - _.each( - OB.MobileApp.model.get('dataSyncModels'), - function(syncModel) { - if (syncModel && OB.UTIL.isNullOrUndefined(syncModel.modelFunc)) { - if ( - syncModel.model && - syncModel.model.prototype && - syncModel.model.prototype.modelName - ) { - syncModel.modelFunc = - 'OB.Model.' + syncModel.model.prototype.modelName; - } - } - }, - this - ); - // explicitly setting pushState to false tries to ensure... - // that future default modes will not activate the pushState... - // breaking the POS navigation - // setTimeout to move the execution until all 'load' listeners have finished - setTimeout(function() { - Backbone.history.start({ - root: '', - pushState: false - }); - }, 0); - }, - false - ); - - window.addEventListener('beforeunload', function(e) { - OB.MobileApp.model.reloadingApplication = true; - }); - }, - - initializeGlobalState: function() { - const persistence = new OB.App.Class.StatePersistence(); - window.OB.App.State = new OB.App.Class.State(persistence); - - window.OB.App.PersistenceChangeListenerManager = new OB.App.Class.PersistenceChangeListenerManager( - persistence - ); - - OB.App.State.TerminalLog.setContext({ context: 'refresh(F5)' }); - - window.addEventListener('beforeunload', function(e) { - // Try to save the database before unload. - // It is missing the await, but in the 'beforeunload' it doesn't wait for any async code so is no diference to put it. - // Since doesn't wait, it doesn't waranty that will work in 100% of cases, but in many cases will have time to persist into disk - // Note that this code is an extra security mechanism appart of the continous persistence controled by a throttle - OB.App.State.persistence.stateStorePersistor.flush(); - }); - - window.addEventListener( - 'storage', - function(e) { - // Prevent state persistance when application is opened in another tab - // Event will be triggered only when localStorage values are changed in another tab - if ( - e.key === OB.UTIL.localStorage.getAppName() + '.browserTabId' && - e.oldValue !== e.newValue - ) { - OB.App.State.persistence.stateStorePersistor.pause(); - } - }, - false - ); - }, - /** - * Method to be called when there is an error while loading the application - * @param {String} caller a string that identifies the process that failed - */ - loadingErrorsActions: function(caller) { - // arguments check - OB.UTIL.Debug.execute(function() { - if (!caller) { - throw 'missing parameter: caller'; - } - }); - - // remember that the loading has failed. used by the LogClient - OB.MobileApp.model.loadingErrorsActions.executed = true; - - var link = 'NA'; - try { - link = OB.UTIL.getStackLink(3); - } catch (ex) { - OB.error('executeSqlErrorHandler.getStackLink: ' + ex); - } - OB.error( - "loadingErrorsActions: '" + - caller + - "' failed while loading the application. line: " + - link - ); - }, - - cleanTerminalData: function() { - _.each( - this.get('propertiesLoaders'), - function(curPropertiesToLoadProcess) { - _.each( - curPropertiesToLoadProcess.properties, - function(curProperty) { - this.set(curProperty, null); - }, - this - ); - }, - this - ); - }, - - returnToOnline: function() { - //DEVELOPERS: overwrite this function to perform actions when it happens - OB.info('The system has come back to online'); - }, - - //DEVELOPER: This function allow us to execute some code to add properties to to terminal model. Each position of the array - // must identify the properties which are set and needs to define a loadFunction which will be executed to get the data. - // when the data is ready terminalModel.propertiesReady(properties) should be executed. - addPropertiesLoader: function(obj) { - this.get('propertiesLoaders').push(obj); - this.syncAllPropertiesLoaded = _.after( - this.get('propertiesLoaders').length, - this.allPropertiesLoaded - ); - }, - - handlePropertiesLoader: function( - properties, - source, - params, - successCallback - ) { - function handleError() { - var msg = - OB.I18N.getLabel('OBMOBC_TerminalPropertiesErrorBody', [ - properties.join(', ') - ]) + OB.I18N.getLabel('OBMOBC_LoadingErrorBody'), - isErrorPopupShown = - OB.MobileApp.view.$.confirmationContainer.getCurrentPopup() && - OB.MobileApp.view.$.confirmationContainer - .getCurrentPopup() - .getShowing(); - if ( - OB.MobileApp.model.get('isLoggingIn') === true && - !isErrorPopupShown - ) { - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_TerminalPropertiesErrorHeader'), - msg, - [ - { - label: OB.I18N.getLabel('OBMOBC_Reload'), - action: function() { - window.location.reload(); - } - } - ], - { - onShowFunction: function(popup) { - OB.UTIL.localStorage.removeItem( - 'cacheAvailableForUser:' + OB.MobileApp.model.get('orgUserId') - ); - popup.$.headerCloseButton.hide(); - OB.MobileApp.view.$.containerWindow.destroyComponents(); - }, - autoDismiss: false, - showLoading: true - } - ); - } - } - - new OB.DS.Request(source).exec( - params, - function(data) { - if (data.exception) { - handleError(); - } else { - if (typeof successCallback === 'function') { - successCallback(data); - } - } - }, - function(data) { - handleError(); - } - ); - }, - - propertiesReady: function(properties) { - OB.info("[sdrefresh] '" + properties + "' property loaded"); - this.syncAllPropertiesLoaded(); - }, - - processPropertyLoaders: function() { - OB.debug('next process: allPropertiesLoaded'); - var termInfo, - msg, - key, - keysToSkip = ['dataSyncModels', 'connectedToERP']; - if (this.get('loggedOffline')) { - //Load from termInfo - if (this.usermodel.get('terminalinfo')) { - termInfo = JSON.parse(this.usermodel.get('terminalinfo')); - for (key in termInfo) { - if ( - Object.prototype.hasOwnProperty.call(termInfo, key) && - keysToSkip.indexOf(key) === -1 - ) { - //If it is not in the keys to skip - this.set(key, termInfo[key]); - } - } - //overwrite loaded values which was stored into DB when system was online - this.set('loggedOffline', true); - this.set('connectedToERP', false); - //Funtions are not recoverd when obj is loaded from DB. Recover Them - this.set('logConfiguration', { - deviceIdentifier: - OB.UTIL.localStorage.getItem('terminalAuthentication') === 'Y' - ? OB.UTIL.localStorage.getItem('terminalName') - : OB.UTIL.getParameterByName('terminal'), - logPropertiesExtension: [ - function() { - return { - isOnline: OB.MobileApp.model.get('connectedToERP') - }; - } - ] - }); - - _.each(this.get('dataSyncModels'), function(item) { - item.model = eval(item.modelFunc); - }); - //Overwrite offline values - this.set('connectedToERP', false); - this.set('loggedOffline', true); - - this.allPropertiesLoaded(); - } else { - // not enough data in cache - OB.MobileApp.model.loadingErrorsActions( - 'processPropertyLoaders: not enough data in cache' - ); - - msg = - OB.I18N.getLabel('OBMOBC_NotEnoughDataInCache') + - OB.I18N.getLabel('OBMOBC_LoadingErrorBody'); - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_TerminalPropertiesErrorHeader'), - msg, - [ - { - label: OB.I18N.getLabel('OBMOBC_Reload'), - action: function() { - window.location.reload(); - } - } - ], - { - onShowFunction: function(popup) { - OB.UTIL.localStorage.removeItem( - 'cacheAvailableForUser:' + OB.MobileApp.model.get('orgUserId') - ); - popup.$.headerCloseButton.hide(); - }, - autoDismiss: false, - showLoading: true - } - ); - } - return; - } - this.set('loggedOffline', false); - //Loading the properties of the array - OB.info( - '[terminal] Starting to load properties based on properties loaders, count: ' + - this.get('propertiesLoaders').length - ); - OB.UTIL.completeLoadingStep(); - OB.UTIL.showLoadingMessage(OB.I18N.getLabel('OBMOBC_LoadingTerminalProps')); - - if ( - this.get('propertiesLoaders') && - this.get('propertiesLoaders').length > 0 - ) { - _.each( - this.get('propertiesLoaders'), - function(curProperty) { - //each loadFunction will call to propertiesReady function. This function will trigger - //allPropertiesLoaded when all of the loadFunctions are done. - curProperty.loadFunction(this); - }, - this - ); - } else { - this.allPropertiesLoaded(); - } - }, - - //DEVELOPER: this function will be automatically called when all the properties defined in - //this.get('propertiesLoaders') are loaded. To indicate that a property is loaded - //me.propertiesReady(properties) should be executed by loadFunction of each property - allPropertiesLoaded: function() { - var me = this; - OB.debug('next process: propertiesLoadersReady'); - if (!OB.MobileApp.model.get('datasourceLoadFailed')) { - OB.UTIL.HookManager.executeHooks( - 'OBMOBC_TerminalLoaded', - { - terminalInfo: me - }, - function(args) { - OB.info('[terminal] Properties have been successfully loaded'); - // activate this line to see all the attributes (all the key data from the server) - // OB.debug('[terminal] Properties have been successfully loaded', this.attributes); - if (!me.get('loggedOffline')) { - //In online mode, we save the terminal information in the local db - const terminalInfo = JSON.stringify(me); - me.usermodel.set('terminalinfo', terminalInfo); - OB.App.OfflineSession.updateTerminalInfo( - me.usermodel.get('id'), - terminalInfo - ).then( - () => {}, - error => OB.error('allPropertiesLoaded', error, arguments) - ); - } - //renderMain - me.trigger('propertiesLoadersReady'); - } - ); - } - }, - - /** - * This function calls to route function of backbone. - * Replace parameter by default is true (if not passed), it means that the current history point will be replaced with the new one without create a new point. - * If you want to create a new point in the history then pReplace should be passed as false - * http://backbonejs.org/#Router-navigate - */ - navigate: function(route, pReplace, parameters) { - if (OB.MobileApp.model.reloadingApplication) { - OB.info('Navigation cancelled since application is reloading'); - return false; - } - if (route === Backbone.history.getFragment()) { - OB.info( - 'Navigating to same window: ' + route + '. Navigation cancelled.' - ); - return false; - } - if (pReplace !== false) { - pReplace = true; - } - OB.debug("Navigating to '" + route + "' Replace: " + pReplace); - if (route) { - OB.App.State.TerminalLog.setContext({ context: route }); - } - OB.MobileApp.view.keyReceivers.splice( - 0, - OB.MobileApp.view.keyReceivers.length - ); - OB.MobileApp.view.currentWindowParameters = parameters; - this.router.navigate(route, { - trigger: true, - replace: pReplace - }); - return true; - }, - - /** - * Loads registered windows and calls renderMain method implemented by each app - */ - renderTerminalMain: function() { - OB.info('next process: databaseInitialization'); - OB.UTIL.Debug.execute(function() { - if (!this.renderMain) { - throw 'There is no renderMain method in Terminal Model'; - } - }, this); - OB.UTIL.localStorage.setItem('POSSessionActive', true); - this.databaseInitialization(); - }, - - checkAllPermissions: function(permissions) { - // Verify all permissions - var i; - for (i in permissions) { - if (Object.prototype.hasOwnProperty.call(permissions, i)) { - if (this.get('permissions')) { - if (permissions[i] !== this.get('permissions')[i]) { - return true; - } - } - } - } - - return false; - }, - - /** - * Database initialization - */ - databaseInitialization: function() { - OB.info('next process: loadTerminalInfo'); - if (window.openDatabase) { - this.initLocalDB(); - } else { - this.loadTerminalInfo(); - } - }, - - /** - * Loads all needed stuff for the terminal such as permissions, Application... - */ - loadTerminalInfo: function() { - OB.info('next process: terminalInfoLoaded'); - OB.UTIL.completeLoadingStep(); - OB.UTIL.showLoadingMessage(OB.I18N.getLabel('OBMOBC_LoadingTerminalInfo')); - var me = this; - - if (OB.MobileApp.model.get('loggedOffline')) { - OB.Format = JSON.parse(OB.UTIL.localStorage.getItem('AppFormat')); - OB.I18N.labels = JSON.parse( - OB.UTIL.localStorage.getItem( - 'I18NLabels_' + - OB.UTIL.localStorage.getItem( - 'languageForUser_' + me.usermodel.get('id') - ) - ) - ); - } - this.setUserModelOnline(function() { - me.trigger('terminalInfoLoaded'); - }); - }, - - isSafeToResetDatabase: function(callbackIsSafe, callbackIsNotSafe) { - callbackIsSafe(); - }, - - databaseCannotBeResetAction: function() { - //Will never happen in standard mobile core. Should be overwritten in applications if isSafeToResetDatabase is implemented - }, - - openLocalDB: async function() { - if (OB.Data.localDB) { - OB.Data.localDB.transaction( - function() {}, - function() { - OB.Dal.openWebSQL(); - }, - null - ); - } else { - OB.Dal.openWebSQL(); - } - - await OB.App.MasterdataController.openDatabase(); - await OB.App.MessageModelController.openDatabase(); - await OB.App.OfflineSessionController.openDatabase(); - }, - - initLocalDB: function() { - function dropTable(modelObj) { - return new Promise(function(resolve, reject) { - function dropTableFunction(modelObj) { - OB.Dal.dropTable( - modelObj, - function() { - var modelName = modelObj.prototype.modelName; - var modelChecksum = OB.Data.getModelStructureChecksum(modelObj); - //If this table belongs to a masterdata model, the masterdata needs to be fully loaded - if ( - OB.UTIL.localStorage.getItem('lastUpdatedTimestamp' + modelName) - ) { - OB.UTIL.localStorage.removeItem( - 'lastUpdatedTimestamp' + modelName - ); - OB.UTIL.localStorage.removeItem('POSLastTotalRefresh'); - OB.UTIL.localStorage.removeItem('POSLastIncRefresh'); - } - OB.Dal.initCache( - modelObj, - [], - function() { - //Set checksum after the model has been created to prevent inconsistent states (e.g. checksum set but missing model) - OB.debug( - 'Set model ' + modelName + ' checksum to ' + modelChecksum - ); - OB.UTIL.localStorage.setItem( - 'structureChecksum-' + modelName, - modelChecksum - ); - resolve(); - }, - function() { - OB.error("Couldn't initialize table for model: " + modelName); - resolve(); - } - ); - }, - function() { - OB.error(OB.UTIL.argumentsToStringifyed('dropTable', arguments)); - reject(); - } - ); - } - - var syncModel = OB.MobileApp.model.getSyncModel(modelObj); - if (syncModel) { - OB.MobileApp.model.syncModelHasData( - syncModel, - function() { - //Sync model has data. We will try to synchronize, and if we fail then the databaseCannotBeReset action will be executed - OB.MobileApp.model.syncAllModels(function() { - dropTableFunction(modelObj); - }, OB.MobileApp.model.databaseCannotBeResetAction); - }, - function() { - dropTableFunction(modelObj); - } - ); - } else { - dropTableFunction(modelObj); - } - }); - } - - function checkChangedInModels() { - var tablesToDrop = []; - _.each(OB.Model, function(model) { - if ( - !model.prototype || - !model.prototype.modelName || - model.prototype.legacyModel - ) { - return; - } - var modelName = model.prototype.modelName; - var modelChecksum = OB.Data.getModelStructureChecksum(model); - if (modelChecksum === '') { - return; - } - if ( - OB.UTIL.localStorage.getItem('structureChecksum-' + modelName) !== - modelChecksum - ) { - if (!OB.UTIL.localStorage.getItem('structureChecksum-' + modelName)) { - OB.debug( - "Model '" + - modelName + - "' should not exists because there isn't a paired entry in the localStorage. Performning preemptive table dropping..." - ); - } else { - OB.info("Model '" + modelName + "' changed. Rebuilding..."); - } - - tablesToDrop.push(dropTable(model)); - OB.MobileApp.model.get('modelsRebuilded').push(model); - } - }); - - OB.info('Checking database models...'); - - if (tablesToDrop.length === 0) { - OB.info('No models changed.'); - OB.MobileApp.model.loadTerminalInfo(); - return; - } - - Promise.all(tablesToDrop).then( - function() { - OB.MobileApp.model.loadTerminalInfo(); - }, - function() { - OB.MobileApp.model.loadingErrorsActions('initLocalDB.dropTalbles'); - } - ); - } - // check if terminal id has changed - if ( - OB.UTIL.localStorage.getItem('loggedTerminalName') && - OB.UTIL.localStorage.getItem('loggedTerminalName') !== - OB.MobileApp.model.get('terminalName') - ) { - OB.warn( - OB.UTIL.argumentsToStringifyed( - 'terminal changed (' + - OB.UTIL.localStorage.getItem('loggedTerminalName') + - ' -> ' + - OB.MobileApp.model.get('terminalName') + - ')' - ) - ); - OB.info( - 'Terminal has been changed. Resetting database and local storage information.' - ); - OB.UTIL.localStorage.clear(function() { - OB.UTIL.localStorage.setItem( - 'terminalName', - OB.MobileApp.model.get('terminalName') - ); - OB.MobileApp.model.logout(); - return; - }); - } else { - OB.MobileApp.model.set('modelsRebuilded', []); - checkChangedInModels(); - } - }, - - /** - * initActions actions is executed before initializeCommonComponents. - * - * Override this in case you app needs to do something special - * Do not forget to execute callback when overriding the method - */ - initActions: function(callback) { - var params = {}; - var cacheSessionId = null; - if ( - OB.UTIL.localStorage.getItem('cacheSessionId') && - OB.UTIL.localStorage.getItem('cacheSessionId').length === 32 - ) { - cacheSessionId = OB.UTIL.localStorage.getItem('cacheSessionId'); - } - - params.cacheSessionId = cacheSessionId; - params.command = 'initActions'; - new OB.OBPOSLogin.UI.LoginRequest({ - url: this.get('loginUtilsUrl') - ? this.get('loginUtilsUrl') - : '../../org.openbravo.mobile.core.loginutils', - data: params - }) - .response(this, function(inSender, inResponse) { - if ( - !( - OB.UTIL.localStorage.getItem('cacheSessionId') && - OB.UTIL.localStorage.getItem('cacheSessionId').length === 32 - ) - ) { - OB.info( - 'cacheSessionId is not defined and we will set the id generated in the backend: ' + - inResponse.cacheSessionId - ); - OB.UTIL.localStorage.setItem( - 'cacheSessionId', - inResponse.cacheSessionId - ); - OB.UTIL.localStorage.setItem( - 'LastCacheGeneration', - new Date().getTime() - ); - } - // Save available servers and services and initialize Request Router layer and Proccess Controller - _.each(_.keys(inResponse.properties), function(key) { - if (inResponse.properties[key]) { - OB.UTIL.localStorage.setItem( - key, - typeof inResponse.properties[key] === 'string' - ? inResponse.properties[key] - : JSON.stringify(inResponse.properties[key]) - ); - } - }); - OB.RR.RequestRouter.initialize(); - OB.UTIL.ProcessController.initialize(); - - if (callback) { - callback(); - } - }) - .error(function() { - if (callback) { - callback(); - } - }) - .go(params); - }, - - /** - * PreLoadContext actions is executed before loading the context. - * - * Override this in case you app needs to do something special - * Do not forget to execute callback when overriding the method - */ - preLoadContext: function(callback) { - callback(); - }, - - /** - * Returns the corresponding sync model for a data model. Returns null if it's not a sync model - */ - getSyncModel: function(dataModel) { - var theSyncModel = null; - _.each(this.get('dataSyncModels'), function(syncModel) { - if (syncModel.model === dataModel) { - theSyncModel = syncModel; - } - }); - return theSyncModel; - }, - /** - * Returns the corresponding sync model for a data model. Returns null if it's not a sync model - */ - getSyncModelByModelName: function(modelName) { - //iterate models - var modelFound = _.find(this.get('dataSyncModels'), function(curModel) { - return curModel.name === modelName ? curModel : null; - }); - if (modelFound) { - return modelFound; - } - return null; - }, - - /** - * Checks if the corresponding synchronization model has data pending to be synchronized. - * - If data is still present, hasDataCallback will be executed, and the data will be sent - * as a parameter in the callback - * - If there is no data, doesntHaveDataCallback will be executed - */ - syncModelHasData: function( - modelObj, - hasDataCallback, - doesntHaveDataCallback - ) { - var criteria = modelObj.changesPendingCriteria - ? modelObj.changesPendingCriteria - : modelObj.getCriteria - ? modelObj.getCriteria() - : modelObj.criteria - ? modelObj.criteria - : null; - var modelObject = modelObj; - if (modelObj.model) { - modelObject = modelObj.model; - } - if (criteria) { - criteria._limit = -1; - } - - OB.Dal.find( - modelObject, - criteria, - function(dataToSync) { - if (dataToSync.length === 0) { - if (doesntHaveDataCallback) { - doesntHaveDataCallback(modelObject); - } - } else { - if (hasDataCallback) { - hasDataCallback(modelObject, dataToSync); - } - } - }, - function() { - if (doesntHaveDataCallback) { - doesntHaveDataCallback(modelObject); - } - }, - { - doNotShowErrors: true - } - ); - }, - - /** - * @return {Array} The list of the Sync Models (models which data is sent to the server) with a flag indicating if they have data pending to be sent to the server - * - * Howto use it: - * - * OB.MobileApp.model.getSyncModelsWithHasData().then(function(syncModelsWithHasData) { - * OB.info(syncModelsWithHasData); - * }) - * - * Please note that this function is experimental and that can be changed or removed at any time without notice - */ - getSyncModelsWithHasData: function() { - return new Promise(function(resolve, reject) { - var syncModelsWithHasData = []; // list of models that requires synchronization and their data status - var promisesOfModelsToVerify = []; - _.each(OB.Model, function(model) { - if (!model.prototype || !model.prototype.modelName) { - return; - } - var modelName = model.prototype.modelName; - var modelChecksum = OB.Data.getModelStructureChecksum(model); - if (modelChecksum === '') { - return; - } - //TerminalLog synchronization shouldn't be mandatory for tests - if (modelName === 'TerminalLog') { - return; - } - var promiseOfModelToVerify = new Promise(function(resolve, reject) { - var syncModel = OB.MobileApp.model.getSyncModel(model); - if (syncModel) { - // add the Sync Model to the array with a hasData flag - var callbackHasData = function() { - syncModelsWithHasData.push({ - model: model, - hasData: true - }); - resolve(); - }; - var callbackNoData = function() { - syncModelsWithHasData.push({ - model: model, - hasData: false - }); - resolve(); - }; - OB.MobileApp.model.syncModelHasData( - syncModel, - callbackHasData, - callbackNoData - ); - } else { - resolve(); - } - }); - promisesOfModelsToVerify.push(promiseOfModelToVerify); - }); - Promise.all(promisesOfModelsToVerify).then( - function() { - resolve(syncModelsWithHasData); - }, - function() { - OB.error('Could not gather information about the models'); - reject(); - } - ); - }); - }, - - /** - * Is used by the synchronized mode logic to reset tables to their previous state - * before making the synchronized call to the server. So if the user then - * refreshes the browser during the call at least the tables are in their previous - * versions. This is mainly relevant for cashup information which gets updated - * along the way and needs to be reversed if needed. - * - * If the synchronized call fails then the old cashup information still applies. - * - * The flow is as follows: - * # application code calls saveBackupBeforeChange function, it stores all the relevant tables - * for that action - * # application then changes what it wants - * # then calls runsync process - * # runsyncprocess will just before calling the server backup the new version of the - * tables (saveBackupBeforeSyncRestorePreChangeBackup function) and restore the - * before change version - * # when the server encounters an error or something else then nothing is changed as - * all the data is already in the previous version - * # when the server returns correctly the backup tables are restored to their state just - * before the send to the server - * - */ - _synchronizedCheckpointModels: [], - addSyncCheckpointModel: function(model) { - this._synchronizedCheckpointModels.push(model); - }, - _prechangeCheckpoint: null, - _presyncCheckpoint: null, - synchProcessingTransaction: null, - setSynchronizedCheckpoint: function(callback) { - var me = OB.MobileApp.model; - - if (!OB.MobileApp.model.hasPermission('OBMOBC_SynchronizedMode', true)) { - return; - } - - // already show the dialog now - if (!me.showSynchronizedDialog) { - me.showSynchronizingDialog(); - } - OB.Dal.createDataDump(me._synchronizedCheckpointModels, function( - checkPoint - ) { - me._prechangeCheckpoint = checkPoint; - if (callback) { - callback(); - } - }); - }, - synchronizingDialogConditions: new Backbone.Collection(), - showSynchronizingDialog: function(condition) { - var me = OB.MobileApp.model; - if (condition) { - this.synchronizingDialogConditions.push(condition); - } - if (!OB.MobileApp.model.get('avoidSyncronizeModePopup')) { - me.showSynchronizedDialog = OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_ProcessingTransactionTitle'), - OB.I18N.getLabel('OBMOBC_ProcessingTransaction'), - [], - { - hideCloseButton: true, - autoDismiss: false, - closeOnEscKey: false - } - ); - } - OB.Data.showSynchronizedDialogChangeTime = new Date().getTime(); - OB.UTIL.preventRefreshByUser(); - }, - - hideSynchronizingDialog: function(condition) { - var me = OB.MobileApp.model; - if (condition) { - this.synchronizingDialogConditions.pop(condition); - } - if ( - me.showSynchronizedDialog && - this.synchronizingDialogConditions.length === 0 - ) { - me.showSynchronizedDialog.hide(); - me.showSynchronizedDialog = null; - OB.UTIL.resetPreventRefreshByUser(); - } - }, - - createPresyncCheckpoint: function(callback) { - var me = OB.MobileApp.model; - - OB.Dal.createDataDump(me._synchronizedCheckpointModels, function( - checkPoint - ) { - me._presyncCheckpoint = checkPoint; - if (callback) { - callback(); - } - }); - }, - moveToPrechangeCheckpoint: function(callback) { - var me = OB.MobileApp.model, - dataDump = me._prechangeCheckpoint; - if (!dataDump) { - if (callback) { - callback(); - } - return; - } - // be on the save side, set it to null if it fails we won't get it - // back anyway - me._prechangeCheckpoint = null; - OB.Dal.restoreDataDump(dataDump, function() { - me._prechangeCheckpoint = null; - if (callback) { - callback(); - } - }); - }, - resetCheckpointData: function() { - var me = OB.MobileApp.model; - - me._presyncCheckpoint = null; - me._prechangeCheckpoint = null; - }, - moveToPresyncCheckpoint: function(callback) { - var me = OB.MobileApp.model; - // reset this one also - me._prechangeCheckpoint = null; - - if (!me._presyncCheckpoint) { - if (callback) { - callback(); - } - return; - } - OB.Dal.restoreDataDump(me._presyncCheckpoint, function() { - me._presyncCheckpoint = null; - if (callback) { - callback(); - } - }); - }, - - preSyncPromises: [], - doPreSynchronizedCallActions: function(callback, syncData) { - var me = OB.MobileApp.model; - OB.UTIL.localStorage.setItem('synchronizedMessageId', syncData.messageId); - new Promise(function(resolve, reject) { - me.createPresyncCheckpoint(function() { - resolve(); - }); - }) - .then(function() { - return new Promise(function(resolve, reject) { - me.moveToPrechangeCheckpoint(function() { - resolve(); - }); - }); - }) - .then(function() { - return Promise.all(me.preSyncPromises); - }) - .then( - function() { - me.preSyncPromises = []; - if (callback) { - callback(); - } - }, - function(reason) { - OB.error('Error while processing promise ' + reason); - } - ); - }, - doPostSynchronizedCallActions: function(callback, syncData) { - if ( - OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('synchronizedMessageId') - ) || - OB.UTIL.localStorage.getItem('synchronizedMessageId') === - syncData.messageId - ) { - OB.UTIL.localStorage.removeItem('synchronizedMessageId'); - } else { - OB.error( - "Message id of the last request doesn't much with the current messageId" - ); - } - if (callback) { - callback(); - } - }, - /** - * Sets the OBMOBC_SynchronizedMode permission and returns the previous value. - * Can be used to temporarily disable synchronized mode. - */ - setSynchronizedPreference: function(newValue) { - var currentValue = OB.MobileApp.model.hasPermission( - 'OBMOBC_SynchronizedMode', - true - ); - OB.MobileApp.model.get('permissions').OBMOBC_SynchronizedMode = newValue; - return currentValue; - }, - - /** - * Should not be called directly. syncAllModels should be used instead - * - * Recursively syncs models. The recursion will end if: - * - all models have successfully synchronized - * or if - * - an exception is raised - * - */ - syncModelQueue: null, - syncModel: function() { - var me = this; - - // stop the recursion when the queue of syncModels is empty - // the rest of the actions are in the removeSyncedElemsCallback - if (me.syncModelQueue.length === 0) { - //There aren't anymore elements to synchronize - if (OB.MobileApp.model.hasPermission('OBMOBC_SynchronizedMode', true)) { - // clean up in any case after success or error - // note: moveToPresyncCheckpoint must be called before the reset - me.syncModelSuccessCallbacks.unshift(function() { - me.resetCheckpointData(); - }); - me.syncModelErrorCallbacks.push(function() { - me.resetCheckpointData(); - }); - - // nothing to do go away - if (me.synchronizedData.length === 0) { - me.syncModelExecuteSuccessCallbacks(); - return; - } - - // already show the dialog now - if ( - !me.showSynchronizedDialog && - !OB.MobileApp.view.$.containerLoading.showing - ) { - me.showSynchronizingDialog(); - } - //now send all the collected json as one request - var syncProc = new OB.DS.Process( - 'org.openbravo.mobile.core.servercontroller.SynchronizedServerProcessCaller' - ); - var syncData = { - messageId: OB.UTIL.get_UUID(), - _source: 'WEBPOS', - _executeInOneServer: true, - _tryCentralFromStore: true, - posTerminal: OB.MobileApp.model.get('terminal') - ? OB.MobileApp.model.get('terminal').id - : null, - data: me.synchronizedData, - extraParams: { - isSynchronizeModeTransaction: true - } - }; - - // clear some memory - me.synchronizedData = []; - - // and execute the complete request, first do the presync actions - me.doPreSynchronizedCallActions(function() { - syncProc.exec( - syncData, - function() { - var args = arguments; - me.doPostSynchronizedCallActions(function() { - var params; - if (args.length > 0) { - params = args[0]; - if (args[0].result && args[0].result.length > 0) { - params = args[0].result[0]; - } - } - me.syncModelExecuteSuccessCallbacks(params); - }, syncData); - }, - function() { - var args = arguments; - me.syncModelExecuteErrorCallbacks(args); - }, - null, - me.synchronizedTimeOut - ); - }, syncData); - } else { - me.syncModelExecuteSuccessCallbacks(); - } - return; - } - - var modelObj = me.syncModelQueue.shift(); - var model = modelObj.model; - var criteria; - if (modelObj.getCriteria) { - criteria = modelObj.getCriteria(); - } else if (modelObj.criteria) { - criteria = modelObj.criteria; - } else { - criteria = { - hasBeenProcessed: 'Y' - }; - } - criteria._limit = -1; - OB.Dal.find( - model, - criteria, - function(dataToSync) { - me.skipSyncModel = dataToSync.length === 0; - - if (modelObj.preSendModel) { - // preSendModel is like a hook which can be used by custom code - // to influence the logic, for example setting me.skipSyncModel - modelObj.preSendModel(me, dataToSync); - } - - if (me.skipSyncModel) { - me.skipSyncModel = false; - me.syncModel(); - return; - } - - var className = modelObj.className; - - if (model === OB.Model.Order && OB.UTIL.processOrderClass) { - className = OB.UTIL.processOrderClass; - } - - var mdl, - modelIndex = 0, - modelNotFullySynced = false, - dataCorruption = false, - newDataToSync = new Backbone.Collection(); - while (modelIndex < dataToSync.length) { - mdl = dataToSync.at(modelIndex); - if ( - !_.isUndefined(mdl.get('json')) && - (_.isEmpty(mdl.get('json')) || _.isNull(mdl.get('json'))) - ) { - OB.error( - '[syncModel] Wrong model to Synchronize in backend: ' + - mdl.modelName + - ' because json column is undefined.' - ); - dataCorruption = true; - } else { - newDataToSync.add(dataToSync.at(modelIndex)); - } - - modelIndex++; - // partition the data in chunks - if (modelIndex >= 100 && modelIndex < dataToSync.length) { - modelNotFullySynced = true; - // add the model again to the beginning of the queue - me.syncModelQueue.unshift(modelObj); - break; - } - } - if (dataCorruption) { - var criteria = { - whereClause: ' WHERE json is null OR json= "" ' - }; - OB.Dal.removeAll(model, criteria); - } - - var dataToSend = []; - newDataToSync.each(function(record) { - if (record.get('json')) { - dataToSend.push(JSON.parse(record.get('json'))); - } else if (!_.isUndefined(record.get('objToSend'))) { - if (!_.isNull(record.get('objToSend'))) { - dataToSend.push(JSON.parse(record.get('objToSend'))); - } - } else { - dataToSend.push(record); - } - }); - - var timeout = modelObj.timeout || 20000; - var timePerRecord = modelObj.timePerRecord || 1000; - var data = { - messageId: OB.UTIL.get_UUID(), - modelName: modelObj.name, - data: dataToSend, - isSyncModel: true - }; - - // add an additional pre-sync action, remove any non-persistent data - // before really going to the server - if (OB.MobileApp.model.hasPermission('OBMOBC_SynchronizedMode', true)) { - newDataToSync.each(function(record) { - me.preSyncPromises.push( - new Promise(function(resolve, reject) { - if (!modelObj.isPersistent) { - OB.Dal.remove( - record, - function() { - resolve(); - }, - function(tx, err) { - OB.UTIL.showError(err); - reject(); - } - ); - } else { - resolve(); - } - }) - ); - }); - } - - var procErrorCallBack = function() { - // proc.exec mcallbackerror - OB.warn( - "Error while synchronizing model '" + - OB.Dal.getTableName(model) + - "'" - ); - - me.syncModelExecuteErrorCallbacks(); - }; - - var procSuccessCallBack = function( - data, - message, - lastUpdated, - endRow, - tx, - requestCallback - ) { - // error - if (data && data.exception) { - OB.warn( - "The model '" + - OB.Dal.getTableName(model) + - "'' has not been synchronized with the server" - ); - if ( - data.exception.invalidPermission && - !me.get('displayedInvalidPermission') - ) { - // invalid permission message only will be displayed once time - me.set('displayedInvalidPermission', true); - OB.UTIL.showConfirmation.display( - 'Info', - OB.I18N.getLabel('OBMOBC_NoPermissionToSyncModel', [ - OB.Dal.getTableName(model), - OB.Dal.getTableName(model) - ]), - [ - { - label: OB.I18N.getLabel('OBMOBC_LblOk'), - isConfirmButton: true, - action: function() {} - } - ] - ); - } - me.syncModelExecuteErrorCallbacks(); - return; - } - // success. Elements can be now deleted from the database - var removeSyncedElemsCallback = function(tx) { - OB.info("Purging the '" + OB.Dal.getTableName(model) + "' table"); - - var promises = []; - - if (modelObj.successSendModel) { - promises.push( - new Promise(function(resolve, reject) { - modelObj.successSendModel(); - resolve(); - }) - ); - } - - newDataToSync.each(function(record) { - promises.push( - new Promise(function(resolve, reject) { - if (modelObj.isPersistent) { - // Persistent model. Do not delete, just mark it as processed. - OB.Dal.updateRecordColumn( - record, - 'isbeingprocessed', - 'Y', - function() { - if (requestCallback) { - requestCallback(); - } - resolve(); - }, - function(tx, err) { - reject(); - }, - tx - ); - } else { - // no persistent model (Default). - OB.Dal.removeInTransaction( - tx, - record, - function() { - if (requestCallback) { - requestCallback(); - } - resolve(); - }, - function(tx, err) { - OB.UTIL.showError(err); - reject(); - } - ); - } - }) - ); - }); - - Promise.all(promises).then( - function() { - // if the model has been partitioned - if (modelNotFullySynced) { - OB.info( - newDataToSync.length + - " records of the table '" + - OB.Dal.getTableName(model) + - "' have been successfully synchronized with the server. " + - (dataToSync.length - newDataToSync.length) + - ' records remaining to be synchronized.' - ); - me.syncModel(); - return; - } - - OB.info( - "The table '" + - OB.Dal.getTableName(model) + - "' has been fully synchronized with the server" - ); - }, - function(err) { - OB.error( - "Could not purge the '" + - OB.Dal.getTableName(model) + - "' table. Error message: " + - err - ); - } - ); - // not synchronized mode do the next in the success callback - // with synchronized mode the recall syncmodel is below in the code - if ( - !OB.MobileApp.model.hasPermission('OBMOBC_SynchronizedMode', true) - ) { - me.syncModel(); - } - }; - if (modelObj.removeSyncedElemsCallback) { - modelObj.removeSyncedElemsCallback( - newDataToSync, - tx, - requestCallback - ); - } else { - removeSyncedElemsCallback(tx); - } - if (modelObj.postProcessingFunction) { - modelObj.postProcessingFunction( - newDataToSync, - removeSyncedElemsCallback - ); - } - }; - - if (OB.MobileApp.model.hasPermission('OBMOBC_SynchronizedMode', true)) { - me.collectSyncData( - className, - data, - timeout + timePerRecord * newDataToSync.length - ); - me.syncModelSuccessCallbacks.push(procSuccessCallBack); - me.syncModel(); - } else { - var proc = new OB.DS.Process(className); - //if the model of the message requires polling, add it to the list - var tmpMsg = OB.PollingUtils.createMessage(data, proc.source); - data.messageId = tmpMsg.id; - if (OB.PollingUtils.getPollingUrl(tmpMsg)) { - OB.Polling.PollingRequestHandler.planedToSendPollingRequest.add( - new OB.Model.PollingRequest({ - message: tmpMsg - }) - ); - } - proc.exec( - data, - procSuccessCallBack, - procErrorCallBack, - null, - timeout + timePerRecord * newDataToSync.length - ); - } - }, - - function() { - // there is no model in the local database. this is ok. move to the next model - me.syncModel(); - }, - { - // do not show an error when the table is not present in the local database because it could happen when the cache is still clean - doNotShowErrors: true - } - ); - }, - syncModelSuccessCallbacks: [], - syncModelErrorCallbacks: [], - syncModelExecuteSuccessCallbacks: function(result) { - result = result || {}; - OB.UTIL.Debug.execute(function() { - if ( - OB.MobileApp.model.syncModelQueue && - OB.MobileApp.model.syncModelQueue.length > 0 - ) { - throw "The 'syncModelQueue' should be empty at this point"; - } - }); - - // exception occurred but was returned in the json as a valid response - if (result && result.exception) { - OB.MobileApp.model.syncModelExecuteErrorCallbacks(arguments); - return; - } - if (OB.MobileApp.model.showSynchronizedDialog) { - OB.MobileApp.model.hideSynchronizingDialog(); - } - - OB.MobileApp.model.syncModelQueue = []; - OB.MobileApp.model.syncModelErrorCallbacks = []; - - function execCallbacks() { - new Promise(function(resolve, reject) { - // is only doing anything in case of synchronized mode - OB.MobileApp.model.moveToPresyncCheckpoint(function() { - resolve(); - }); - }).then(function() { - OB.UTIL.executeCallbackQueue( - OB.MobileApp.model.syncModelSuccessCallbacks, - result - ); - }); - } - - //Get messages from local database only when polling models exists - if (OB.Polling.PollingRequestHandler.getPollingSyncModels().length > 0) { - //set in result messages pending to synchronize - OB.RR.ServTypeTransaction.getMessages( - function(messages) { - var promises = []; - _.each(messages.models, function(msg) { - //if the model of the message requires polling, add it to the list - if (OB.PollingUtils.getPollingUrl(msg)) { - promises.push( - new Promise(function(resolve, reject) { - OB.Polling.PollingRequestHandler.planedToSendPollingRequest.add( - new OB.Model.PollingRequest({ - message: msg - }) - ); - resolve(); - }) - ); - } - }); - Promise.all(promises).then(function() { - result.messages = - OB.Polling.PollingRequestHandler.planedToSendPollingRequest; - execCallbacks(); - }); - }, - function() { - execCallbacks(); - } - ); - } else { - execCallbacks(); - } - }, - syncModelExecuteErrorCallbacks: function() { - OB.MobileApp.model.syncModelQueue = []; - OB.MobileApp.model.syncModelSuccessCallbacks = []; - - if (OB.MobileApp.model.hasPermission('OBMOBC_SynchronizedMode', true)) { - var msg = - arguments[0][0] && arguments[0][0].exception - ? arguments[0][0].exception.message - : 'No Details'; - - if (OB.MobileApp.model.showSynchronizedDialog) { - OB.MobileApp.model.hideSynchronizingDialog(); - } - - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_TransactionFailedTitle'), - OB.I18N.getLabel('OBMOBC_TransactionFailed', [msg]), - [ - { - isConfirmButton: true, - label: OB.I18N.getLabel('OBMOBC_LblOk'), - action: function() { - if (OB.MobileApp.view.currentWindow !== 'retail.pointofsale') { - OB.POS.navigate('retail.pointofsale'); - return true; - } - } - } - ] - ); - } - - OB.UTIL.executeCallbackQueue(OB.MobileApp.model.syncModelErrorCallbacks); - }, - - // collect the data to sync - collectSyncData: function(source, params, timeout) { - var data = OB.DS.Process.prepareData(params); - - data._serviceName = source; - - this.synchronizedTimeOut = this.synchronizedTimeOut + timeout; - - this.synchronizedData.push(data); - }, - - /** - * Synchronizes all dataSyncModels (models which data is kept localy until flushed to the server) - * - * If this method is called while a previous data synchronization is in place: - * - the data synchronization of the model being synchronized at the time this method is called, will be finished - * - the remaining models will not be synchronized - * - the synchronization will start over with the first model - * - all the callbacks (success or error) are gathered in a queue, all of them will be executed when the synchronization ends - */ - syncAllModels: function(successCallback, errorCallback) { - this.syncAllModelsImpl(successCallback, errorCallback); - }, - - syncAllModelsImpl: function(successCallback, errorCallback) { - var me = this; - - // if the user is not authenticated, the server will reject all the connections - if (!OB.MobileApp.model.isUserAuthenticated()) { - OB.UTIL.Debug.execute(function() { - OB.warn( - "'OB.MobileApp.model.syncAllModels' cannot be executed because it requires an autheticated user" - ); - }); - if (errorCallback) { - errorCallback(); - } - return; - } - // if there is no model to be synchronized - if (!me.get('dataSyncModels') || me.get('dataSyncModels').length === 0) { - if (successCallback) { - successCallback(); - } - return; - } - // flow note: this line must be located before the queues are reset. Check models queue and callbacks queues, the process finishes when the callback are called - var isCurrentlySynchronizing = - (me.syncModelQueue && me.syncModelQueue.length > 0) || - (me.syncModelSuccessCallbacks && - me.syncModelSuccessCallbacks.length > 0) || - (me.syncModelErrorCallbacks && me.syncModelErrorCallbacks.length > 0); - // start the synchronization if it was not already in progress - if (!isCurrentlySynchronizing) { - me.syncModelSuccessCallbacks = []; - me.syncModelErrorCallbacks = []; - - // remember the callbacks in the callbacks queue - me.syncModelSuccessCallbacks.push(successCallback); - me.syncModelErrorCallbacks.push(errorCallback); - - // all models must restart the synchronization when syncAllModels is called. this is attained reseting the queue - me.syncModelQueue = []; - // add all the models to the queue - var i; - for (i = 0; i < this.get('dataSyncModels').length; i++) { - var modelObj = this.get('dataSyncModels')[i]; - me.syncModelQueue.push(modelObj); - } - - // reset some members used in synchronized comms - me.synchronizedData = []; - me.synchronizedTimeOut = 0; - - me.syncModel( - this.syncModelExecuteSuccessCallbacks, - this.syncModelExecuteErrorCallbacks - ); - } - }, - - /** - * Returns true if a user is authenticated - */ - isUserAuthenticated: function() { - return !!OB.MobileApp.model.get('context'); - }, - - /** - * If any module needs to perform operations with properties coming from the server - * this function must be overwritten - */ - isUserCacheAvailable: function() { - return false; - }, - - /** - * Invoked from initComponents in terminal view - */ - initializeCommonComponents: function() { - this.openLocalDB().finally(() => { - this.initializeCommonComponentsImp(); - }); - }, - - initializeCommonComponentsImp: function() { - OB.info( - "next process: navigate 'login' or renderTerminalMain or loginUsingCache" - ); - - var me = this, - params = {}; - OB.UTIL.localStorage.setItem('LOGINTIMER', new Date().getTime()); - - params = this.get('loginUtilsParams') || {}; - params.command = 'preRenderActions'; - params.appModuleId = this.get('appModuleId'); - params.supportsExternalBusinessPartnerIntegration = this.get( - 'supportsExternalBusinessPartnerIntegration' - ); - // initialize labels and other common stuff - var rr, - ajaxRequest = new enyo.Ajax({ - url: this.get('loginUtilsUrl'), - cacheBust: false, - timeout: 5000, - // don't do retries as this gives side effects as re-showing login page - maxNumOfRequestRetries: 0, - method: 'GET', - handleAs: 'json', - contentType: 'application/json;charset=utf-8', - data: params, - success: function(inSender, inResponse) { - OB.info('loginUtilsUrl success'); - if (inResponse.csrfToken) { - OB.MobileApp.model.set('csrfToken', inResponse.csrfToken); - } - if (inResponse.externalBpIntegration) { - OB.MobileApp.model.set( - 'externalBpIntegration', - inResponse.externalBpIntegration - ); - } - - if ( - _.isEmpty(inResponse.labels) || - _.isNull(inResponse.labels) || - _.isUndefined(inResponse.labels) - ) { - OB.I18N.labels = JSON.parse( - OB.UTIL.localStorage.getItem('I18NLabels') - ); - } else { - OB.appCaption = inResponse.appCaption; - OB.UTIL.localStorage.setItem( - 'POSlanguageId', - inResponse.labels.languageId - ); - OB.UTIL.localStorage.setItem( - 'POSlanguageSK', - inResponse.labels.languageSk - ); - OB.I18N.labels = inResponse.labels; - if (inResponse.activeSession) { - // The labels will only be saved in the localStorage if they come from an active session - // We do this to prevent the labels loaded in the login page from being stored here and overwritting - // the labels in the correct language (see issue 23613) - OB.UTIL.localStorage.setItem( - 'languageForUser_' + OB.MobileApp.model.get('orgUserId'), - inResponse.labels.languageId - ); - OB.UTIL.localStorage.setItem( - 'I18NLabels', - JSON.stringify(OB.I18N.labels) - ); - OB.UTIL.localStorage.setItem( - 'I18NLabels_' + inResponse.labels.languageId, - JSON.stringify(OB.I18N.labels) - ); - } - } - if ( - _.isEmpty(inResponse.lists) || - _.isNull(inResponse.lists) || - _.isUndefined(inResponse.lists) - ) { - OB.I18N.lists = JSON.parse( - OB.UTIL.localStorage.getItem('I18NLists') - ); - } else { - OB.I18N.lists = inResponse.lists; - if (inResponse.activeSession) { - // The reference list will only be saved in the localStorage if they come from an active session - // We do this to prevent the reference list loaded in the login page from being stored here and overwritting - // the reference list in the correct language - OB.UTIL.localStorage.setItem( - 'I18NLists', - JSON.stringify(OB.I18N.lists) - ); - OB.UTIL.localStorage.setItem( - 'I18NLists_' + inResponse.lists.languageId, - JSON.stringify(OB.I18N.lists) - ); - } - } - if ( - _.isEmpty(inResponse.dateFormats) || - _.isNull(inResponse.dateFormats) || - _.isUndefined(inResponse.dateFormats) - ) { - OB.Format = JSON.parse(OB.UTIL.localStorage.getItem('AppFormat')); - } else { - OB.Format = inResponse.dateFormats; - } - - var permissions = {}, - i, - separator; - if (inResponse.permissions) { - for (i = 0; i < inResponse.permissions.length; i++) { - permissions[inResponse.permissions[i].key] = - inResponse.permissions[i].value; // Add the permission value. - separator = inResponse.permissions[i].key.indexOf('_'); - if (separator >= 0) { - permissions[ - inResponse.permissions[i].key.substring(separator + 1) - ] = inResponse.permissions[i].value; // if key has a DB prefix, add also the permission value without this prefix - } - } - } else { - permissions = null; - } - - me.set('permissions', permissions); - - if ('invalidRequestOrigin' === inResponse.exception) { - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_InvalidOriginTitle'), - OB.I18N.getLabel('OBMOBC_InvalidOriginMsg') - ); - return; - } - me.trigger('initializedCommonComponents'); - me.preLoadContext(function() { - if ( - !inResponse.contextInfo || - OB.UTIL.localStorage.getItem('POSSessionActive') === 'false' - ) { - //Not authenticated, need to navigate to login page - OB.MobileApp.model.navigate('login'); - return; - } - OB.MobileApp.model.triggerOnLine(); - OB.info('Set isLoggingIn true'); - OB.MobileApp.model.set('isLoggingIn', true); - OB.MobileApp.model.set('orgUserId', inResponse.contextInfo.user.id); - me.set('context', inResponse.contextInfo); // fires the 'change:context' event - window.OB.Format = inResponse.format; - window.OB.UTIL.localStorage.setItem( - 'AppFormat', - JSON.stringify(OB.Format) - ); - OB.UTIL.startLoadingSteps(); - me.renderTerminalMain(); - return; - }); - }, - fail: function(inSender, inResponse) { - OB.info('loginUtilsUrl fail'); - if (this.alreadyProcessed) { - return; - } - this.alreadyProcessed = true; - // we are likely offline. Attempt to navigate to the login page - OB.I18N.labels = JSON.parse( - OB.UTIL.localStorage.getItem('I18NLabels') - ); - OB.Format = JSON.parse(OB.UTIL.localStorage.getItem('AppFormat')); - me.trigger('initializedCommonComponents'); - if (!OB.I18N.labels || !OB.Format) { - // These 2 objects are needed for the application to show i18n messages and date and number formats - OB.UTIL.showLoading(false); - var errorMsg = - 'The server is not available and the cache has not enough data. Connect to the server and try again.'; - OB.error('initializeCommonComponents', errorMsg); - OB.UTIL.showConfirmation.display( - 'Error', - errorMsg, - [ - { - label: 'Retry', - isConfirmButton: true, - action: function() { - // Retry. If the server is not available, the message will open again - me.initializeCommonComponents(); - } - } - ], - { - onHideFunction: function() { - // Retry. If the server is not available, the message will open again - me.initializeCommonComponents(); - } - } - ); - return; - } - me.attemptToLoginOffline(); - } - }); - - rr = new OB.RR.Request({ - ajaxRequest: ajaxRequest - }); - OB.info('loginUtilsUrl request'); - rr.exec(ajaxRequest.url); - }, - - renderLogin: function() { - if (!OB.MobileApp.view) { - // the terminal view has yet to be created - OB.UTIL.Debug.execute(function() { - throw 'OB.MobileApp.view must be defined'; - }); - OB.MobileApp.model.navigate(''); - return; - } - if (!OB.MobileApp.view.$.containerWindow) { - // this can happen if the webpage is reloaded - // OB.UTIL.Debug.execute(function () { - // OB.error("OB.MobileApp.view.$.containerWindow must be defined"); - // }); - OB.MobileApp.model.navigate(''); - return; - } - - this.initApplicationTasks(function() { - OB.MobileApp.view.$.containerWindow.destroyComponents(); - OB.MobileApp.view.$.containerWindow - .createComponent({ - kind: OB.OBPOSLogin.UI.Login, - classes: 'obUiTerminal-containerWindow-obObposLoginUiLogin' - }) - .render(); - OB.UTIL.showLoading(false); - }); - }, - - initApplicationTasks: async function(callback) { - const me = this; - // ensure exists message table, to check if there are messages pending to sync, - // thats needs to be synchonized before update the sources - const stateMessages = OB.App.State.getState().Messages; - const syncBufferMessages = await OB.App.SynchronizationBuffer.getPendingMessages(); - const hasNotSyncedMessages = - stateMessages.length > 0 || syncBufferMessages.length > 0; - - if (hasNotSyncedMessages) { - // do not update sources - me.checkBenchMark(callback); - OB.warn( - 'On preparing login page, but cannot check for new sources becasue there are messages to be sync. Needed to be logged in to sync them' - ); - } else { - // update sources - this.loadApplicationSources(callback); - } - }, - - checkBenchMark: function(finalCallback) { - const browserInfo = OB.UTIL.getSupportedBrowserInfo(); - OB.MobileApp.model.set('ApplicationSourcesLoaded', true); - if (browserInfo.isSupported) { - if ( - !OB.UTIL.Debug.getDebugCauses() || - (!OB.UTIL.Debug.getDebugCauses().isInDevelopment && - !OB.UTIL.Debug.getDebugCauses().isTestEnvironment) - ) { - if ( - OB.MobileApp.model.get('shouldExecuteBenchmark') && - OB.UTIL.localStorage.getItem('doNotExecuteBenchmark') === null && - OB.UTIL.localStorage.getItem('executePerformanceTest') !== 'N' - ) { - this.dialog = OB.MobileApp.view.$.confirmationContainer.createComponent( - { - kind: 'OB.UI.ModalBenchmark', - name: 'modalBenchmark', - classes: 'obUiTerminal-confirmationContainer-modalBenchmark', - context: this - } - ); - OB.UTIL.showLoading(false); - this.dialog.show({ - callback: finalCallback - }); - return; - } - } - } - finalCallback(); - }, - - loadApplicationSources: function(callback) { - var finalCallback, permissionSW; - const me = this; - finalCallback = function() { - if (callback instanceof Function) { - callback(); - } - }; - - if (typeof ServiceWorkerUtil !== 'undefined') { - permissionSW = ServiceWorkerUtil.useServiceWorkers; - } else { - permissionSW = false; - } - if (permissionSW === true) { - OB.UTIL.fetchApplicationSources( - function() { - me.checkBenchMark(finalCallback); - }, - function() { - OB.error('Error in fetching the application sources.'); - OB.MobileApp.model.set('loadManifeststatus', { - type: 'error', - reason: 'fail' - }); - finalCallback(); - } - ); - } else { - //If preference is not set, we will use AppCache - me.checkBenchMark(finalCallback); - } - }, - - loadModels: function(windows, incremental, parentCallback, background) { - var i, - j, - windowName, - windowClass, - windowDatasources, - datasources = [], - w, - c, - path; - - const initLogParamsForBackgroundProcesses = function() { - OB.UTIL.localStorage.setItem('totalRecordsBackgroundRequest', 0); - OB.UTIL.localStorage.setItem('totalTimeBackgroundRequest', 0); - OB.UTIL.localStorage.setItem('totalRecordsBackgroundSave', 0); - OB.UTIL.localStorage.setItem('totalTimeBackgroundSave', 0); - }; - - const showLogsForBackgroundProcesses = function() { - let processDetails = incremental ? 'inc' : 'full'; - let totalRecordsBackgroundRequest = OB.UTIL.localStorage.getItem( - 'totalRecordsBackgroundRequest' - ); - let totalTimeBackgroundRequest = OB.UTIL.localStorage.getItem( - 'totalTimeBackgroundRequest' - ); - let totalRecordsBackgroundSave = OB.UTIL.localStorage.getItem( - 'totalRecordsBackgroundSave' - ); - let totalTimeBackgroundSave = OB.UTIL.localStorage.getItem( - 'totalTimeBackgroundSave' - ); - if (Number(totalTimeBackgroundRequest) > 0) { - OB.info( - '[background-request-' + - processDetails + - '] Loaded ' + - totalRecordsBackgroundRequest + - ' records in the process. Finished after ' + - totalTimeBackgroundRequest + - ' miliseconds.' - ); - } - if (Number(totalTimeBackgroundSave) > 0) { - OB.info( - '[background-save-' + - processDetails + - '] Saved ' + - totalRecordsBackgroundSave + - ' records in the process. Finished after ' + - totalTimeBackgroundSave + - ' miliseconds.' - ); - } - }; - - const callback = function() { - showLogsForBackgroundProcesses(); - OB.info( - '[sdrefresh] Finished loading models ' + - (incremental ? 'incrementally' : 'full') + - '.' + - (background ? ' (' + background + ' )' : '') - ); - parentCallback(); - }; - - if (OB.MobileApp.model.get('loggedOffline')) { - if (parentCallback instanceof Function) { - parentCallback(); - } - return; - } - - if (OB.UTIL.isNullOrUndefined(windows) || windows.length === 0) { - windows = []; - _.each(this.windowRegistry.registeredWindows, function(windowp) { - windows.push(windowp); - }); - } - - OB.info( - '[sdrefresh] Load models ' + - (incremental ? 'incrementally' : 'full') + - '.' + - (background ? ' (' + background + ' )' : '') - ); - initLogParamsForBackgroundProcesses(); - - for (i = 0; i < windows.length; i++) { - windowClass = windows[i].windowClass; - windowName = windows[i].route; - if (OB && OB.DATA && OB.DATA[windowName]) { - // old way of defining datasources... - windowDatasources = OB.DATA[windowName]; - } else if ( - windowClass.prototype && - windowClass.prototype.windowmodel && - windowClass.prototype.windowmodel.prototype && - windowClass.prototype.windowmodel.prototype.models - ) { - windowDatasources = windowClass.prototype.windowmodel.prototype.models; - } else if (typeof windowClass === 'string') { - w = window; // global window - path = windowClass.split('.'); - for (c = 0; c < path.length; c++) { - w = w[path[c]]; - } - - if ( - w.prototype && - w.prototype.windowmodel && - w.prototype.windowmodel.prototype && - w.prototype.windowmodel.prototype.models - ) { - windowDatasources = w.prototype.windowmodel.prototype.models; - } - } - - for (j = 0; j < windowDatasources.length; j++) { - if (datasources.indexOf(windowDatasources[j]) === -1) { - datasources.push(windowDatasources[j]); - } - } - } - - _.extend(datasources, Backbone.Events); - OB.Dal.Datasources = datasources; - - OB.debug( - '[sdrefresh] window: ' + - windowName + - (background ? '. (' + background + ' )' : '') - ); - - OB.Dal.loadModels( - false, - datasources, - null, - incremental, - callback, - background - ); - - this.postLoadModels(); - }, - postLoadModels: function() {}, - - cleanWindows: function() { - this.windows = new (Backbone.Collection.extend({ - comparator: function(window) { - // sorts by menu position, 0 if not defined - var position = window.get('menuPosition'); - return position ? position : 0; - } - }))(); - }, - - registerWindow: function(window) { - this.windowRegistry.registerWindow(window); - }, - - /** - * Iterates over all registered windows loading their models - */ - loadRegisteredWindows: function(callback) { - var me = this; - var func_handleRouting = function(windowName, params) { - //TODO default route - var defaultRoute = OB.MobileApp.model.get('defaultWindow'); - //Find dest window - var destinationWindow = _.find( - OB.MobileApp.windowRegistry.registeredWindows, - function(win) { - return win.route === windowName; - } - ); - //Check if destination window is valid. If not navigate to main window - if (OB.UTIL.isNullOrUndefined(destinationWindow)) { - OB.MobileApp.model.navigate(defaultRoute); - } - //If destination window has permission, check if user is allowed to navigate - //if not, navigate to main window - if (destinationWindow.permission) { - if (!OB.MobileApp.model.hasPermission(destinationWindow.permission)) { - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_NotAllowed'), - OB.I18N.getLabel('OBMOBC_AccessNotAllowed', [ - destinationWindow.permission - ]), - null, - { - onHideFunction: function() { - OB.MobileApp.model.navigate(defaultRoute); - } - } - ); - return; - } - } - - //Execute hook -> OBMOBC_PreWindowNavigate - OB.UTIL.HookManager.executeHooks( - 'OBMOBC_PreWindowNavigate', - { - window: destinationWindow, - route: destinationWindow.route, - params: params - }, - function(args) { - if (args && args.cancellation && args.cancellation === true) { - if (args.forceLogout) { - OB.MobileApp.model.logout(); - } else { - OB.MobileApp.model.navigate(defaultRoute); - } - return; - } - - if (args.window && args.window.navigateTo) { - args.window.navigateTo( - args, - function() { - me.renderGenericWindow(args.route, params); - }, - function() { - OB.MobileApp.model.navigate(defaultRoute); - } - ); - } else if (args.route) { - me.renderGenericWindow(args.route, params); - } - } - ); - }; - - OB.debug('next process: renderMain'); - - this.cleanWindows(); - OB.MobileApp.model.set('modelsToLoad', []); - - if ( - OB.MobileApp.model.get('permissions') && - (OB.MobileApp.model.hasPermission( - 'OBMOBC_EnableTerminalLogUserActions', - true - ) || - OB.MobileApp.model.hasPermission( - 'OBMOBC_EnableTerminalLogProcess', - true - )) - ) { - OB.UTIL.TerminalLog.activateTerminalLogSync(); - } else { - OB.info( - 'Terminal log is disabled because both preferences (OBMOBC_EnableTerminalLogUserActions and OBMOBC_EnableTerminalLogProcess) are set to false.' - ); - } - - var countOfLoadedWindows = 0; - var registeredWindows = this.windowRegistry.registeredWindows; - _.each( - registeredWindows, - function(windowp) { - var windowName = windowp.route; - - this.windows.add(windowp); - - this.router.route(windowName, windowName, function() { - func_handleRouting(windowName); - }); - - this.router.route(windowName + '/:params', windowName, function( - params - ) { - func_handleRouting(windowName, params); - }); - countOfLoadedWindows += 1; - }, - this - ); - // checks if the windows loaded are more than the windows registered, which never should happen - var countOfRegisteredWindows = this.windowRegistry.registeredWindows.length; - OB.assert( - countOfRegisteredWindows === countOfLoadedWindows, - 'DEVELOPER: There are ' + - countOfRegisteredWindows + - ' registered windows but ' + - countOfLoadedWindows + - ' are being loaded' - ); - - if (OB.MobileApp.model.get('loggedOffline')) { - // If logged offline we skip the load of masterdata in the login - callback(); - return; - } - - const validateIncrementalRefreshModelsLoaded = function(isValidCallback) { - const failedModels = [ - ...OB.App.MasterdataController.getFailedMasterdataModels(), - ...(OB.Dal.Datasources._failedModels || []) - ]; - if (failedModels.length === 0) { - // All models have finished the incremental load process. - // We can safely set the POSLastIncRefresh timestamp now. - OB.UTIL.localStorage.setItem('POSLastIncRefresh', new Date().getTime()); - } - isValidCallback(); - }; - - const validateModelsLoaded = function(isValidCallback) { - const failedModels = [ - ...OB.App.MasterdataController.getFailedMasterdataModels(), - ...(OB.Dal.Datasources._failedModels || []) - ]; - if (failedModels.length > 0) { - // Remove all timestamp to force a full refresh - OB.UTIL.localStorage.removeItem('POSLastTotalRefresh'); - OB.UTIL.localStorage.removeItem('POSLastIncRefresh'); - _.forEach(failedModels, function(model) { - OB.UTIL.localStorage.removeItem('lastUpdatedTimestamp' + model); - }); - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_MasterDataErrorHeader'), - OB.I18N.getLabel('OBMOBC_MasterDataErrorBody', [ - failedModels.join(', ') - ]) + OB.I18N.getLabel('OBMOBC_LoadingErrorBody'), - [ - { - label: OB.I18N.getLabel('OBMOBC_Reload'), - action: function() { - window.location.reload(); - } - } - ], - { - onShowFunction: function(popup) { - popup.$.headerCloseButton.hide(); - }, - autoDismiss: false, - showLoading: true - } - ); - } else { - // At this point we can ensure that all models have loaded correctly. - // If we are performing a full refresh of the data, now we can set the POSLastTotalRefresh timestamp. - OB.UTIL.localStorage.setItem( - 'POSLastTotalRefresh', - new Date().getTime() - ); - //Data might be old, but it should be enough to work - isValidCallback(); - } - }; - - const statusCallback = function statusFunction(model, offset, limit) { - if (offset === 0) { - OB.UTIL.completeLoadingStep(); - OB.UTIL.showLoadingMessage( - OB.I18N.getLabel('OBMOBC_LoadingMessageModel', [model]) - ); - } else { - OB.UTIL.showLoadingMessage( - OB.I18N.getLabel('OBMOBC_LoadingMessageModelPage', [ - model, - offset / limit + 1 - ]) - ); - } - }; - - var terminalType = - OB.MobileApp.model.get('terminal') && - OB.MobileApp.model.get('terminal').terminalType; - var minTotalRefresh, - minIncRefresh, - lastTotalRefresh, - lastIncRefresh, - now, - intervalTotal, - intervalInc; - if (terminalType) { - minTotalRefresh = - OB.MobileApp.model.get('terminal').terminalType - .minutestorefreshdatatotal * - 60 * - 1000; - minIncRefresh = OB.MobileApp.model.get('terminal').terminalType - .minutestorefreshdatainc; - lastTotalRefresh = OB.UTIL.localStorage.getItem('POSLastTotalRefresh'); - lastIncRefresh = OB.UTIL.localStorage.getItem('POSLastIncRefresh'); - - // lastTotalRefresh should be used to set lastIncRefresh when it is null or minor. - if (lastIncRefresh === null) { - lastIncRefresh = lastTotalRefresh; - } else { - if (lastTotalRefresh > lastIncRefresh) { - lastIncRefresh = lastTotalRefresh; - } - } - minIncRefresh = - (minIncRefresh > 99999 ? 99999 : minIncRefresh) * 60 * 1000; - } - const doFullRefresh = function() { - me.loadModels(registeredWindows, false, () => - OB.App.MasterdataController.fullMasterdataRefresh(statusCallback).then( - () => { - OB.UTIL.showLoadingMessage( - OB.I18N.getLabel('OBMOBC_ForcingRefreshOfLocalDatabase') - ); - validateModelsLoaded(callback); - } - ) - ); - }; - const doIncrementalRefresh = function() { - OB.App.MasterdataController.setReloadAppConfiguration(true); - me.loadModels(registeredWindows, true, () => - OB.App.MasterdataController.incrementalMasterdataRefresh( - statusCallback - ).then(() => { - OB.UTIL.showLoadingMessage( - OB.I18N.getLabel('OBMOBC_ForcingRefreshOfLocalDatabase') - ); - validateIncrementalRefreshModelsLoaded(callback); - OB.App.MasterdataController.setReloadAppConfiguration(false); - }) - ); - }; - if ( - (!minTotalRefresh && !minIncRefresh) || - (!lastTotalRefresh && !lastIncRefresh) - ) { - // If no configuration of the masterdata loading has been done, - // or an initial load has not been done, then always do - // a total refresh during the login - OB.UTIL.showLoadingTitleMessage( - true, - 'OBMOBC_LblLoadingFull', - 'OBMOBC_LblLoadingFullMsg' - ); - //It's time to do a full refresh - doFullRefresh(); - } else { - now = new Date().getTime(); - intervalTotal = lastTotalRefresh - ? now - lastTotalRefresh - minTotalRefresh - : 0; - intervalInc = lastIncRefresh ? now - lastIncRefresh - minIncRefresh : 0; - if (intervalTotal > 0) { - this.set('FullRefreshWasDone', true); - - //It's time to do a full refresh - doFullRefresh(); - } else { - if ( - OB.MobileApp.model.hasPermission( - 'OBMOBC_NotAutoLoadIncrementalAtLogin', - true - ) && - OB.UTIL.isNullOrUndefined(this.get('forceLoadModelsOnLogin')) - ) { - if ( - intervalInc >= 0 || - OB.UTIL.localStorage.getItem('POSForceIncrementalRefresh') - ) { - if (OB.UTIL.localStorage.getItem('POSForceIncrementalRefresh')) { - OB.UTIL.localStorage.removeItem('POSForceIncrementalRefresh'); - } - // incremental refresh - doIncrementalRefresh(); - } else { - if (callback instanceof Function) { - // Call the callback directly in case incremental refresh is not required. - callback(); - } - } - } else { - if (OB.UTIL.isNullOrUndefined(lastTotalRefresh)) { - this.set('FullRefreshWasDone', true); - //It's time to do a full refresh - doFullRefresh(); - } else { - // incremental refresh - doIncrementalRefresh(); - } - } - } - if (this.get('FullRefreshWasDone')) { - OB.UTIL.showLoadingTitleMessage( - true, - 'OBMOBC_LblLoadingFull', - 'OBMOBC_LblLoadingFullMsg' - ); - } else { - OB.UTIL.showLoadingTitleMessage(true, 'OBMOBC_LblLoadingInc'); - } - } - }, - - showFrozenPopUp: function() { - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_BrowserTabHeader', [ - OB.MobileApp.model.get('appName') - ]), - OB.I18N.getLabel('OBMOBC_BrowserTabBody', [ - OB.MobileApp.model.get('appName') - ]), - [], - { - autoDismiss: false, - hideCloseButton: true, - closeOnEscKey: false - } - ); - }, - - saveBrowserTabId: function() { - var me = this; - var browserTabId = OB.UTIL.get_UUID(); - if (OB.MobileApp.model.get('terminal')) { - OB.MobileApp.model.get('terminal').browserTabId = browserTabId; - } else { - OB.MobileApp.model.browserTabId = browserTabId; - } - OB.UTIL.localStorage.setItem('browserTabId', browserTabId); - - // When focus the browser tab - window.onfocus = function() { - var actualBrowserTabId = OB.MobileApp.model.get('terminal') - ? OB.MobileApp.model.get('terminal').browserTabId - : OB.MobileApp.model.browserTabId; - if (OB.UTIL.localStorage.getItem('browserTabId') !== actualBrowserTabId) { - OB.MobileApp.view.applicationLocked = true; - me.showFrozenPopUp(); - } - }; - }, - - isWindowOnline: function(route) { - var i, windows; - windows = this.windows.toArray(); - for (i = 0; i < windows.length; i++) { - if (windows[i].get('route') === route) { - return windows[i].get('online'); - } - } - return false; - }, - - renderGenericWindow: function(windowName, params) { - if (!OB.MobileApp.view.$.containerWindow) { - OB.UTIL.Debug.execute(function() { - OB.error('OB.MobileApp.view.$.containerWindow must be defined'); - }); - OB.MobileApp.model.loadingErrorsActions('renderGenericWindow I'); - return; - } - - OB.MobileApp.view.currentWindow = 'unknown'; - OB.MobileApp.model.set('currentWindow', 'unknown'); - OB.MobileApp.view.currentWindowState = 'unknown'; - OB.MobileApp.model.set('currentWindowState', 'unknown'); - OB.UTIL.showLoading(true); - - var windowObject = this.windows.where({ - route: windowName - })[0]; - var windowClass = windowObject.get('windowClass'); - OB.MobileApp.view.$.containerWindow.destroyComponents(); - OB.MobileApp.view.$.containerWindow.createComponent({ - kind: windowClass, - windowName: windowName, - classes: 'obUiTerminal-containerWindow-windowClass-generic', - params: params - }); - - //remove possible external classes - _.each(this.windows.models, function(win) { - if (win && win.get('containerCssClass')) { - if (OB.MobileApp.view.hasClass(win.get('containerCssClass'))) { - OB.MobileApp.view.removeClass(win.get('containerCssClass')); - } - } - }); - //Add original css-class - if (!OB.MobileApp.view.hasClass('pos-container')) { - OB.MobileApp.view.addClass('pos-container'); - } - //If window is overwritting container, remove original and put new one - if (windowObject && windowObject.get('containerCssClass')) { - if (windowObject.get('overwriteOriginalContainerCssClass')) { - OB.MobileApp.view.removeClass('pos-container'); - } - OB.MobileApp.view.addClass(windowObject.get('containerCssClass')); - } - - OB.MobileApp.view.currentWindow = windowName; - OB.MobileApp.model.set('currentWindow', windowName); - }, - - renderContainerWindow: function() { - OB.MobileApp.view.$.containerWindow.render(); - OB.UTIL.showLoading(false); - OB.MobileApp.view.$.containerWindow.resized(); - - OB.UTIL.HookManager.executeHooks( - 'OBPOS_AfterRenderContainerWindow', - { - context: OB.MobileApp.view.$.containerWindow, - window: OB.MobileApp.view.currentWindow, - parameters: OB.MobileApp.view.currentWindowParameters - }, - function() {} - ); - }, - - updateSession: async function(user, callback) { - // argument check - OB.UTIL.Debug.execute(function() { - if (!callback) { - throw 'This method requires a callback'; - } - }); - try { - const session = await OB.App.OfflineSession.activateSession( - user.get('id'), - OB.MobileApp.model.get('terminalName') - ); - - OB.MobileApp.model.set('session', session.id); - callback(); - } catch (e) { - OB.error( - 'updateSession: failed to save the existing session', - e, - arguments - ); - } - }, - - /** - * This method is invoked when closing session, it is in charge - * of dealing with all stuff needed to close session. After it is - * done it MUST do triggerLogout - */ - postCloseSession: function(session) { - OB.MobileApp.model.triggerLogout(); - }, - - closeSession: async function() { - const sessionId = OB.MobileApp.model.get('session'); - if (!this.get('supportsOffline') || !sessionId) { - OB.MobileApp.model.triggerLogout(); - return; - } - - try { - await OB.App.OfflineSession.closeSession(sessionId); - } catch (e) { - OB.error('closeSession', e); - } - OB.MobileApp.model.triggerLogout(); - }, - - /** - * @deprecated Use PasswordHash.generateHash instead - */ - generate_sha1: function(theString) { - return CryptoJS.enc.Hex.stringify(CryptoJS.SHA1(theString)); - }, - - attemptToLoginOffline: async function() { - if ( - !OB.MobileApp.model.get('isLoggingIn') && - OB.UTIL.localStorage.getItem('POSSessionActive') === 'false' - ) { - OB.MobileApp.model.navigate('login'); - return; - } - // If the user tries to login offline and exceeded the maximum of minutes offline, don't allow login - if ( - !OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('maxTimeInOffline') - ) - ) { - if ( - !OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('lastSuccessfulOnlineRequest') - ) && - new Date().getTime() - - OB.UTIL.localStorage.getItem('lastSuccessfulOnlineRequest') >= - OB.UTIL.localStorage.getItem('maxTimeInOffline') * 60 * 1000 - ) { - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_MaximumOfflineTimeExceeded_Title'), - OB.I18N.getLabel('OBMOBC_MaximumOfflineTimeExceeded_Message'), - [ - { - isConfirmButton: true, - label: OB.I18N.getLabel('OBMOBC_LblOk'), - action: function() { - window.location.reload(); - } - } - ], - { - onHideFunction: function() { - window.location.reload(); - } - } - ); - return; - } - } - if (!this.user) { - // Reloading page - // If the user tries to login offline and exceeded the maximum of minutes offline, don't allow login - if ( - !OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('offlineSessionTimeExpiration') - ) && - !OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('lastUserIdLogin') - ) - ) { - if ( - !OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('lastSuccessfulOnlineRequest') - ) && - new Date().getTime() - - OB.UTIL.localStorage.getItem('lastSuccessfulOnlineRequest') < - OB.UTIL.localStorage.getItem('offlineSessionTimeExpiration') * - 60 * - 1000 - ) { - OB.MobileApp.model.set('windowRegistered', undefined); - OB.MobileApp.model.set('loggedOffline', true); - - try { - const session = await OB.App.OfflineSession.getActiveSessionForUser( - OB.UTIL.localStorage.getItem('lastUserIdLogin') - ); - - if (!session) { - OB.warn( - 'No active session for user with id ' + - OB.UTIL.localStorage.getItem('lastUserIdLogin') + - '. Navigating to login page' - ); - OB.MobileApp.model.navigate('login'); - return; - } - - const user = await OB.App.OfflineSession.withId(session.userId); - if (user) { - this.usermodel = new OB.Model.User(user); - this.set('orgUserId', user.id); - this.renderTerminalMain(); - return; - } else { - OB.UTIL.showConfirmation.display( - '', - OB.I18N.getLabel('OBMOBC_OfflinePasswordNotCorrect'), - null, - { - onHideFunction: () => OB.MobileApp.model.navigate('login') - } - ); - return; - } - } catch (e) { - OB.error('Error getting active sessions', e); - } - } - } - OB.MobileApp.model.navigate('login'); - return; - } - - OB.MobileApp.model.set('windowRegistered', undefined); - OB.MobileApp.model.set('loggedOffline', true); - OB.MobileApp.model.set('datasourceLoadFailed', false); - - const user = await OB.App.OfflineSession.login(this.user, this.password); - - if (user) { - // TODO: replace backbone model with new app state - this.usermodel = new OB.Model.User(user); - this.set('orgUserId', user.id); - this.updateSession(this.usermodel, () => { - OB.UTIL.localStorage.setItem('lastUserIdLogin', user.id); - OB.UTIL.localStorage.setItem('lastUserNameLogin', user.name); - OB.UTIL.localStorage.setItem('lastLogInDate', new Date().getTime()); - this.renderTerminalMain(); - }); - } else { - OB.UTIL.showConfirmation.display( - '', - OB.I18N.getLabel('OBMOBC_OfflinePasswordNotCorrect'), - null, - { - onHideFunction: function() { - window.location.reload(); - } - } - ); - } - }, - - postLoginActions: function() { - OB.debug('next process: none'); - }, - - /** - * Prelogin actions is executed before login, it should take care of - * removing all session values specific by application. - * - * Override this in case you app needs to do something special - */ - preLoginActions: function() { - OB.debug('next process: none. set your application start point here'); - }, - - login: function(user, password, mode, command) { - OB.info('isLoggingIn ' + this.get('isLoggingIn')); - if (this.get('isLoggingIn') === true) { - OB.UTIL.Debug.execute(function() { - throw 'There is already a logging process in progress'; - }); - return; - } - this.set('isLoggingIn', true); - - var params; - OB.info('Login loading'); - OB.UTIL.showLoading(true); - var me = this; - me.user = user; - me.password = password; - - // invoking app specific actions - this.preLoginActions(); - - params = JSON.parse(JSON.stringify(this.get('loginUtilsParams'))) || {}; - params.user = user; - params.password = password; - params.Command = command ? command : 'DEFAULT'; - if (params.Command === 'FORCE_RESET_PASSWORD') { - params.resetPassword = true; - } - params.IsAjaxCall = 1; - params.appName = this.get('appName'); - - var rr, - ajaxRequest = new enyo.Ajax({ - url: this.get('loginHandlerUrl'), - cacheBust: false, - method: 'POST', - timeout: 5000, - // don't do retries as this gives side effects as re-showing login page - maxNumOfRequestRetries: 0, - contentType: 'application/x-www-form-urlencoded; charset=UTF-8', - data: params, - success: function(inSender, inResponse) { - OB.info('loginHandlerUrl success'); - if (this.alreadyProcessed) { - OB.info('loginHandlerUrl alreadyProcessed'); - return; - } - this.alreadyProcessed = true; - if (inResponse && inResponse.sourceVersion) { - OB.UTIL.sourceVersionFromResponse = inResponse.sourceVersion; - OB.UTIL.checkSourceVersion(inResponse.sourceVersion, true, false); - } - if (OB.MobileApp.model.get('timeoutWhileLogin')) { - OB.info('loginHandlerUrl timeoutWhileLogin'); - OB.MobileApp.model.set('timeoutWhileLogin', false); - return; - } - if (inResponse && inResponse.showMessage) { - if (inResponse.command === 'FORCE_NAMED_USER') { - OB.UTIL.showConfirmation.display( - inResponse.messageTitle, - inResponse.messageText, - [ - { - label: OB.I18N.getLabel('OBMOBC_LblOk'), - action: function() { - OB.MobileApp.model.set('isLoggingIn', false); - OB.MobileApp.model.login( - user, - password, - mode, - 'FORCE_NAMED_USER' - ); - return; - } - }, - { - label: OB.I18N.getLabel('OBMOBC_LblCancel'), - action: function() { - OB.MobileApp.model.set('isLoggingIn', false); - return; - } - } - ], - { - autoDismiss: false - } - ); - return; - } else if (inResponse.resetPassword) { - OB.UTIL.showLoading(false); - this.dialog = OB.MobileApp.view.$.confirmationContainer.createComponent( - { - kind: 'OB.UI.ExpirationPassword', - classes: - 'obUiTerminal-confirmationContainer-obUiExpirationPassword', - context: this - } - ); - this.dialog.show(); - - if (inResponse.attemptedChange) { - this.dialog.$.body.$.error.setContent(inResponse.messageText); - } else { - this.dialog.$.body.$.newheader.setContent( - inResponse.messageTitle - ); - } - - return; - } else { - me.triggerLoginFail(401, mode, inResponse); - return; - } - } - if ( - OB.MobileApp.model.get('terminalName') && - !OB.UTIL.localStorage.getItem('loggedTerminalName') - ) { - OB.UTIL.localStorage.setItem( - 'loggedTerminalName', - OB.MobileApp.model.get('terminalName') - ); - } - if ( - (!OB.UTIL.isNullOrUndefined( - OB.MobileApp.model.get('terminalName') - ) || - !OB.UTIL.isNullOrUndefined( - OB.UTIL.localStorage.getItem('loggedTerminalName') - )) && - OB.MobileApp.model.get('terminalName') !== - OB.UTIL.localStorage.getItem('loggedTerminalName') - ) { - OB.info( - 'Terminal has been changed. Resetting database and local storage information.' - ); - OB.UTIL.localStorage.clear(function() { - OB.MobileApp.model.logout(); - return; - }); - } else { - OB.MobileApp.model.set('orgUserId', inResponse.userId); - OB.UTIL.localStorage.setItem('lastUserIdLogin', inResponse.userId); - OB.UTIL.localStorage.setItem('lastUserNameLogin', me.user); - OB.UTIL.localStorage.setItem('lastLogInDate', new Date().getTime()); - OB.UTIL.localStorage.setItem('POSSessionActive', true); - me.set('loggedOffline', false); - var setUserModelOnlineCallback = function() { - me.initializeCommonComponents(); - }; - if (me.get('supportsOffline')) { - me.setUserModelOnline(setUserModelOnlineCallback); - } else { - setUserModelOnlineCallback(); - } - } - }, - fail: function(inSender, inResponse) { - OB.info('loginHandlerUrl fail'); - var lastTotalRefresh, lastIncRefresh; - if (this.alreadyProcessed) { - return; - } - this.alreadyProcessed = true; - lastTotalRefresh = OB.UTIL.localStorage.getItem( - 'POSLastTotalRefresh' - ); - lastIncRefresh = OB.UTIL.localStorage.getItem('POSLastIncRefresh'); - if (!lastTotalRefresh && !lastIncRefresh) { - //There hasn't been a complete login yet, so we can't allow to login offline. - var msg = - OB.I18N.getLabel('OBMOBC_OfflineModeNoLogin') + - OB.I18N.getLabel('OBMOBC_LoadingErrorBody'); - OB.UTIL.showConfirmation.display( - OB.I18N.getLabel('OBMOBC_Error'), - msg, - [ - { - label: OB.I18N.getLabel('OBMOBC_Reload'), - action: function() { - window.location.reload(); - } - } - ], - { - onShowFunction: function(popup) { - OB.UTIL.localStorage.removeItem( - 'cacheAvailableForUser:' + - OB.MobileApp.model.get('orgUserId') - ); - popup.$.headerCloseButton.hide(); - }, - autoDismiss: false, - showLoading: true - } - ); - } else { - me.attemptToLoginOffline(); - } - } - }); - rr = new OB.RR.Request({ - ajaxRequest: ajaxRequest - }); - OB.info('loginHandlerUrl request'); - rr.exec(this.get('loginHandlerUrl')); - }, - - setUserModelOnline: function(callback) { - OB.debug('next process:', callback); - // argument checks - OB.UTIL.Debug.execute(function() { - if (!callback) { - throw 'This method should not be executed asynchronously. Please provide a callback'; - } - }); - - const user = { - id: OB.MobileApp.model.get('orgUserId'), - name: this.user, - formatInfo: JSON.stringify(OB.Format) - }; - - OB.App.OfflineSession.upsertUser(user, this.password).then( - dbUser => { - // TODO: replace backbone model with new app state - const backboneUser = new OB.Model.User(dbUser); - this.usermodel = backboneUser; - this.updateSession(backboneUser, callback); - }, - - error => { - OB.error('setUserModelOnline', error, arguments); - } - ); - }, - - /** - * Set of app specific actions executed before loging out. - * - * Override this method if your app needs to do something special - */ - preLogoutActions: function(callback) { - callback(); - }, - - logout: function() { - var me = this; - - function callback() { - OB.UTIL.showLoggingOut(true); - - var rr, - ajaxRequest = new enyo.Ajax({ - url: '../../org.openbravo.mobile.core.logout', - cacheBust: false, - method: 'GET', - handleAs: 'json', - timeout: 20000, - contentType: 'application/json;charset=utf-8', - success: function(inSender, inResponse) { - me.closeSession(); - }, - fail: function(inSender, inResponse) { - me.closeSession(); - } - }); - rr = new OB.RR.Request({ - ajaxRequest: ajaxRequest - }); - rr.exec(ajaxRequest.url); - } - - this.preLogoutActions(function() { - OB.App.SynchronizationBuffer.internalFlush() - .then(() => { - return OB.App.State.persistence.stateStorePersistor.flush(); - }) - .then(() => { - callback(); - }); - }); - }, - - lock: function() { - var me = this; - - if (OB && OB.POS && OB.POS.hwserver !== undefined) { - OB.POS.hwserver.print( - new OB.DS.HWResource(OB.OBPOSPointOfSale.Print.GoodByeTemplate), - {} - ); - } - - function callback() { - var rr, - ajaxRequest = new enyo.Ajax({ - url: '../../org.openbravo.mobile.core.logout', - cacheBust: false, - method: 'GET', - handleAs: 'json', - timeout: 20000, - contentType: 'application/json;charset=utf-8', - success: function(inSender, inResponse) { - me.triggerLogout(); - }, - fail: function(inSender, inResponse) { - me.triggerLogout(); - } - }); - rr = new OB.RR.Request({ - ajaxRequest: ajaxRequest - }); - rr.exec(ajaxRequest.url); - } - - OB.App.SynchronizationBuffer.internalFlush() - .then(() => { - return OB.App.State.persistence.stateStorePersistor.flush(); - }) - .then(() => { - callback(); - }); - }, - - keepWorking: function() { - this.set('keepWorking', true); - }, - - isKeepWorking: function() { - return this.get('keepWorking'); - }, - - triggerLogout: function() { - // deactivate the loginfail trigger - this.off('loginfail'); - - // trigger for custom applications - this.trigger('logout'); - - OB.UTIL.localStorage.setItem('POSSessionActive', false); - - window.location.reload(); - }, - - triggerLoginSuccess: function() { - this.trigger('loginsuccess'); - }, - - triggerOnLine: function() { - // using "internal" setOnline function of backend server - // we are doing it to keep calls to "OB.MobileApp.model.triggerOnLine" working - OB.App.RemoteServerController.getRemoteServer('BackendServer').setOnline(); - }, - - triggerOffLine: function() { - // using "internal" setOffline function of backend server - // we are doing it to keep calls to "OB.MobileApp.model.triggerOffLine" working - OB.App.RemoteServerController.getRemoteServer('BackendServer').setOffline(); - }, - - triggerLoginFail: function(e, mode, data) { - OB.UTIL.showLoading(false); - if (mode === 'userImgPress') { - this.trigger('loginUserImgPressfail', e); - } else { - this.trigger('loginfail', e, data); - } - this.set('isLoggingIn', false); - }, - - hasPermission: function(p, checkForAutomaticRoles, smartDbPrefix) { - var permission; - var dbPrefix; - var separator = '_'; - var resolvedPreference; - permission = p.approval ? p.approval : p; - - //This code will get the preference based on the app - if (smartDbPrefix === true) { - dbPrefix = OB.MobileApp.model.get('appModulePrefix'); - if (dbPrefix) { - resolvedPreference = dbPrefix + separator + permission; - if ( - this.get('permissions') && - this.get('permissions')[resolvedPreference] !== null && - this.get('permissions')[resolvedPreference] !== undefined - ) { - permission = resolvedPreference; - } else { - resolvedPreference = dbPrefix + permission; - if ( - this.get('permissions') && - this.get('permissions')[resolvedPreference] !== null && - this.get('permissions')[resolvedPreference] !== undefined - ) { - permission = resolvedPreference; - } else { - return false; - } - } - } else { - return false; - } - } - - return ( - (!checkForAutomaticRoles && - !(this.get('context') && this.get('context').role.manual)) || - (this.get('permissions') && this.get('permissions')[permission]) - ); - }, - - supportLogClient: function() { - var supported = true; - if ( - (OB.MobileApp && - OB.MobileApp.model && - OB.MobileApp.model.get('supportsOffline')) === false - ) { - supported = false; - } - return supported; - } -}); | |||||||
Relationships [ Relation Graph ] [ Dependency Graph ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Notes | |
(0148216) hgbot (developer) 2023-04-03 18:20 |
Merge Request created: https://gitlab.com/openbravo/product/pmods/org.openbravo.mobile.core/-/merge_requests/492 [^] |
(0148321) hgbot (developer) 2023-04-06 00:28 |
Merge Request created: https://gitlab.com/openbravo/product/pmods/org.openbravo.core2/-/merge_requests/1042 [^] |
(0148559) hgbot (developer) 2023-04-18 18:18 |
Merge Request created: https://gitlab.com/openbravo/product/pmods/org.openbravo.retail.posterminal/-/merge_requests/1142 [^] |
(0148602) hgbot (developer) 2023-04-19 16:02 |
Merge request closed: https://gitlab.com/openbravo/product/pmods/org.openbravo.retail.posterminal/-/merge_requests/1142 [^] |
(0148743) hgbot (developer) 2023-04-21 14:24 |
Directly closing issue as related merge request is already approved. Repository: https://gitlab.com/openbravo/product/pmods/org.openbravo.mobile.core [^] Changeset: b13853bdbd3df4e89ba982b185b593c5c38259a2 Author: Augusto Mauch <augusto.mauch@openbravo.com> Date: 21-04-2023 12:24:12 URL: https://gitlab.com/openbravo/product/pmods/org.openbravo.mobile.core/-/commit/b13853bdbd3df4e89ba982b185b593c5c38259a2 [^] Fixes ISSUE-52052: Reduce the number of times the state will be persisted This fix means to reduce the number of times the application state is persisted (in localStorage, as of now). Previous behaviour: * state actions that generate entries in the Messages model are persisted immediately * other state actions that belong to persisted models are persisted withing a 100ms throttle * a persistence is forced when logging out New behaviour: * state actions that generate entries in the Messages model are persisted immediately (same as before) * other state actions that belong to persisted models: a) If they its triggerPersistence is set, they are persisted within a 200ms throttle a) If they its triggerPersistence is not set, they are persisted within a 60s throttle (or persisted when any other persistence with higher priority is triggered) * a persistence is forced when logging out (same as before) * a persistence is forced when the window is about to be closed/refreshed when the user is logged in --- M src/org/openbravo/mobile/core/login/MobileCoreLoginUtilsServlet.java M web-test/model/application-state/State.test.js M web/org.openbravo.mobile.core/app/model/application-state/State.js M web/org.openbravo.mobile.core/app/model/application-state/StateAPI.js M web/org.openbravo.mobile.core/app/model/application-state/StatePersistence.js M web/org.openbravo.mobile.core/app/model/business-object/remote-server/RemoteServer.js M web/org.openbravo.mobile.core/app/model/business-object/terminal-log/TerminalLog.js M web/org.openbravo.mobile.core/source/model/ob-terminal-model.js --- |
(0148744) hgbot (developer) 2023-04-21 14:24 |
Merge request merged: https://gitlab.com/openbravo/product/pmods/org.openbravo.mobile.core/-/merge_requests/492 [^] |
(0148745) hgbot (developer) 2023-04-21 14:24 |
Repository: https://gitlab.com/openbravo/product/pmods/org.openbravo.core2 [^] Changeset: e98c725846817e2155b02d9419a650beb94a30a9 Author: Augusto Mauch <augusto.mauch@openbravo.com> Date: 21-04-2023 12:24:33 URL: https://gitlab.com/openbravo/product/pmods/org.openbravo.core2/-/commit/e98c725846817e2155b02d9419a650beb94a30a9 [^] Related to ISSUE-52052: Force the state to be flushed on browser close/refresh if user is logged in This MR makes sure state is flushed before the user closes/refreshes the POS tab while being logged in. A popup will be displayed asking for conformation, and while the user clicks on it the state will be flushed. Changes summary: * `web-jspack/org.openbravo.core2/src/authentication/loginInitActions.js`: Registers the beforeUnload listener when logging in * `web-jspack/org.openbravo.core2/src/model/session/user-actions/Logout.js`: Unregisters the beforeUnload listener when logging out * `web-jspack/org.openbravo.core2/src/model/session/__test__/Logout.test.js`: Adapts test --- M web-jspack/org.openbravo.core2/src/authentication/loginInitActions.js M web-jspack/org.openbravo.core2/src/model/session/__test__/Logout.test.js M web-jspack/org.openbravo.core2/src/model/session/user-actions/Logout.js --- |
(0148746) hgbot (developer) 2023-04-21 14:24 |
Merge request merged: https://gitlab.com/openbravo/product/pmods/org.openbravo.core2/-/merge_requests/1042 [^] |
(0148950) hgbot (developer) 2023-04-26 16:45 |
Repository: https://gitlab.com/openbravo/product/pmods/org.openbravo.core2 [^] Changeset: 6a667d00eba17ca3ed4a23a57c74432ec8785204 Author: Augusto Mauch <augusto.mauch@openbravo.com> Date: 26-04-2023 16:27:16 URL: https://gitlab.com/openbravo/product/pmods/org.openbravo.core2/-/commit/6a667d00eba17ca3ed4a23a57c74432ec8785204 [^] Related to ISSUE-52052: Force the state to be flushed on browser close/refresh if user is logged in This MR makes sure state is flushed before the user closes/refreshes the POS tab while being logged in. A popup will be displayed asking for conformation, and while the user clicks on it the state will be flushed. Changes summary: * `web-jspack/org.openbravo.core2/src/authentication/loginInitActions.js`: Registers the beforeUnload listener when logging in * `web-jspack/org.openbravo.core2/src/model/session/user-actions/Logout.js`: Unregisters the beforeUnload listener when logging out * `web-jspack/org.openbravo.core2/src/model/session/__test__/Logout.test.js`: Adapts test --- M web-jspack/org.openbravo.core2/src/authentication/loginInitActions.js M web-jspack/org.openbravo.core2/src/model/session/__test__/Logout.test.js M web-jspack/org.openbravo.core2/src/model/session/user-actions/Logout.js --- |
Issue History | |||
Date Modified | Username | Field | Change |
2023-04-03 18:18 | AugustoMauch | New Issue | |
2023-04-03 18:18 | AugustoMauch | Assigned To | => AugustoMauch |
2023-04-03 18:18 | AugustoMauch | Triggers an Emergency Pack | => No |
2023-04-03 18:20 | hgbot | Note Added: 0148216 | |
2023-04-06 00:28 | hgbot | Note Added: 0148321 | |
2023-04-18 18:18 | hgbot | Note Added: 0148559 | |
2023-04-19 16:02 | hgbot | Note Added: 0148602 | |
2023-04-20 16:00 | AugustoMauch | Status | new => scheduled |
2023-04-20 16:29 | malsasua | Relationship added | related to 0050361 |
2023-04-20 16:32 | malsasua | Relationship added | related to 0050925 |
2023-04-20 16:33 | malsasua | Relationship added | related to 0051445 |
2023-04-20 17:44 | malsasua | Issue Monitored: malsasua | |
2023-04-20 17:45 | Practics | Issue Monitored: Practics | |
2023-04-21 14:24 | hgbot | Resolution | open => fixed |
2023-04-21 14:24 | hgbot | Status | scheduled => closed |
2023-04-21 14:24 | hgbot | Fixed in Version | => RR23Q3 |
2023-04-21 14:24 | hgbot | Note Added: 0148743 | |
2023-04-21 14:24 | hgbot | Note Added: 0148744 | |
2023-04-21 14:24 | hgbot | Note Added: 0148745 | |
2023-04-21 14:24 | hgbot | Note Added: 0148746 | |
2023-04-26 11:38 | AugustoMauch | Status | closed => new |
2023-04-26 11:38 | AugustoMauch | Resolution | fixed => open |
2023-04-26 11:38 | AugustoMauch | Fixed in Version | RR23Q3 => |
2023-04-26 11:39 | AugustoMauch | Status | new => scheduled |
2023-04-26 11:39 | AugustoMauch | Status | scheduled => resolved |
2023-04-26 11:39 | AugustoMauch | Resolution | open => fixed |
2023-04-26 11:39 | AugustoMauch | Status | resolved => closed |
2023-04-26 16:45 | hgbot | Note Added: 0148950 | |
2023-05-11 14:21 | rafaroda | Relationship added | related to 0052092 |
2023-06-02 12:53 | meriem_azaf | Status | closed => new |
2023-06-02 12:53 | meriem_azaf | Resolution | fixed => open |
2023-06-02 12:54 | meriem_azaf | Status | new => scheduled |
2023-06-02 12:57 | meriem_azaf | Relationship added | depends on 0052656 |
2023-06-06 09:07 | meriem_azaf | Relationship added | depends on 0052675 |
2023-06-07 16:23 | AugustoMauch | Status | scheduled => resolved |
2023-06-07 16:23 | AugustoMauch | Resolution | open => fixed |
2023-06-07 16:23 | AugustoMauch | Status | resolved => closed |
2023-06-20 11:20 | meriem_azaf | Relationship added | depends on 0052786 |
2023-06-20 11:23 | meriem_azaf | Relationship deleted | depends on 0052786 |
2023-09-26 15:03 | malsasua | File Added: 52052_mobCore_21Q4.diff |
Copyright © 2000 - 2009 MantisBT Group |