From 1331a852782027a0c03744b030e4e6ff2cfdb44d Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 15 Jul 2016 15:34:37 +0530 Subject: [PATCH 01/91] Bug #176 | Adding repeated treatments [Services] It doesn't add any other treatments, after tried to add one which already exists --- .../services/services-add.controller.js | 34 ++++++++++++++----- .../services/services-edit.controller.js | 26 ++++++++++---- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 529d6b2a..7cf17bd0 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -222,6 +222,8 @@ } function IsProblemFocusIndex(index) { + if (vm.problemFocusIndex == index) + console.log('true'); return (vm.problemFocusIndex == index); } @@ -555,12 +557,16 @@ function finalizeNewInventory(isFromAutocomplete) { vm.inventory.name = vm.inventory.name.trim(); + vm.inventoryFocusIndex = -1; if (vm.inventory.name != '') { if (isFromAutocomplete) updateInventoryDetails(); var found = $filter('filter')(vm.inventories, { name: vm.inventory.name }, true); + var foundExisting = $filter('filter')(vm.selectedInventories, { + name: vm.inventory.name + }, true); if (found.length == 1) { found[0].checked = true; found[0].rate = vm.inventory.rate; @@ -568,7 +574,10 @@ found[0].qty = vm.inventory.qty; found[0].amount = vm.inventory.amount; found[0].total = vm.inventory.total; - vm.selectedInventories.push(found[0]); + if (foundExisting.length == 0) + vm.selectedInventories.push(found[0]); + else + foundExisting[0] = found[0]; } else { vm.inventories.push({ name: vm.inventory.name, @@ -588,8 +597,8 @@ vm.inventory.qty = 1; vm.inventory.total = ''; calculateCost(); - if (isFromAutocomplete) - vm.inventoryFocusIndex = vm.selectedInventories.length - 1; + if (isFromAutocomplete || foundExisting.length != 0) + vm.inventoryFocusIndex = (foundExisting.length == 0) ? vm.selectedInventories.length - 1 : vm.selectedInventories.indexOf(foundExisting[0]); else setTimeout(focusNewInventoryName, 300); } @@ -1642,18 +1651,25 @@ function finalizeNewProblem(isFromAutocomplete) { vm.problem.details = vm.problem.details.trim(); + vm.problemFocusIndex = -1; if (vm.problem.details != '') { if (isFromAutocomplete) updateTreatmentDetails(); var found = $filter('filter')(vm.service.problems, { details: vm.problem.details }, true); + var foundExisting = $filter('filter')(vm.selectedProblems, { + details: vm.problem.details + }, true); if (found.length == 1) { found[0].checked = true; found[0].rate = vm.problem.rate; found[0].tax = vm.problem.tax; found[0].amount = vm.problem.amount; - vm.selectedProblems.push(found[0]); + if (foundExisting.length == 0) + vm.selectedProblems.push(found[0]); + else + foundExisting[0] = found[0]; } else { vm.service.problems.push({ details: vm.problem.details, @@ -1669,8 +1685,8 @@ vm.problem.rate = ''; vm.problem.tax = ''; calculateCost(); - if (isFromAutocomplete) - vm.problemFocusIndex = vm.selectedProblems.length - 1; + if (isFromAutocomplete || foundExisting.length != 0) + vm.problemFocusIndex = (foundExisting.length == 0) ? vm.selectedProblems.length - 1 : vm.selectedProblems.indexOf(foundExisting[0]); else setTimeout(focusNewProblemDetails, 300); } @@ -1758,6 +1774,8 @@ } break; } + if (vm.problem.details) + finalizeNewProblem(); vm.service.problems = vm.selectedProblems; var options = { isLastInvoiceNoChanged: (vm.service.invoiceno == olInvoiceNo), @@ -1786,8 +1804,6 @@ if (vm.isRoundOffVal) { vm.service['roundoff'] = vm.roundedOffVal; } - if (vm.problem.details) - vm.service.problems.push(vm.problem); vm.service.problems.forEach(iterateProblems); if (vm.sTaxSettings != undefined) { vm.service.serviceTax = { @@ -1804,7 +1820,7 @@ } } if (vm.inventory.name) - vm.selectedInventories.push(vm.inventory); + finalizeNewInventory(); vm.selectedInventories.forEach(iterateInventories); vm.service.inventories = vm.selectedInventories; switch (vm.service.state) { diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index fcc6709f..0cc1f326 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -555,12 +555,16 @@ function finalizeNewInventory(isFromAutocomplete) { vm.inventory.name = vm.inventory.name.trim(); + vm.inventoryFocusIndex = -1; if (vm.inventory.name != '') { if (isFromAutocomplete) updateInventoryDetails(); var found = $filter('filter')(vm.inventories, { name: vm.inventory.name }, true); + var foundExisting = $filter('filter')(vm.selectedInventories, { + name: vm.inventory.name + }, true); if (found.length == 1) { found[0].checked = true; found[0].rate = vm.inventory.rate; @@ -568,7 +572,10 @@ found[0].qty = vm.inventory.qty; found[0].amount = vm.inventory.amount; found[0].total = vm.inventory.total; - vm.selectedInventories.push(found[0]); + if (foundExisting.length == 0) + vm.selectedInventories.push(found[0]); + else + foundExisting[0] = found[0]; } else { vm.inventories.push({ name: vm.inventory.name, @@ -588,8 +595,8 @@ vm.inventory.tax = ''; vm.inventory.qty = 1; vm.inventory.total = ''; - if (isFromAutocomplete) - vm.inventoryFocusIndex = vm.selectedInventories.length - 1; + if (isFromAutocomplete || foundExisting.length != 0) + vm.inventoryFocusIndex = (foundExisting.length == 0) ? vm.selectedInventories.length - 1 : vm.selectedInventories.indexOf(foundExisting[0]); else setTimeout(focusNewInventoryName, 300); } @@ -1652,18 +1659,25 @@ function finalizeNewProblem(isFromAutocomplete) { vm.problem.details = vm.problem.details.trim(); + vm.problemFocusIndex = -1; if (vm.problem.details != '') { if (isFromAutocomplete) updateTreatmentDetails(); var found = $filter('filter')(vm.service.problems, { details: vm.problem.details }, true); + var foundExisting = $filter('filter')(vm.selectedProblems, { + details: vm.problem.details + }, true); if (found.length == 1) { found[0].checked = true; found[0].rate = (vm.sTaxSettings && vm.sTaxSettings.applyTax) ? vm.problem.rate : vm.problem.amount; found[0].tax = vm.problem.tax; found[0].amount = vm.problem.amount; - vm.selectedProblems.push(found[0]); + if (foundExisting.length == 0) + vm.selectedProblems.push(found[0]); + else + foundExisting[0] = found[0]; } else { vm.service.problems.push({ details: vm.problem.details, @@ -1678,8 +1692,8 @@ vm.problem.details = ''; vm.problem.amount = ''; vm.problem.rate = ''; - if (isFromAutocomplete) - vm.problemFocusIndex = vm.selectedProblems.length - 1; + if (isFromAutocomplete || foundExisting.length != 0) + vm.problemFocusIndex = (foundExisting.length == 0) ? vm.selectedProblems.length - 1 : vm.selectedProblems.indexOf(foundExisting[0]); else setTimeout(focusNewProblemDetails, 300); } From 06f675b451bfdf761f4106c52913ec37692b32e5 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 17 Jul 2016 12:03:33 +0530 Subject: [PATCH 02/91] Enhancement #157 | Restart on version update --- src/app/app.controller.js | 41 +++++++++++++++++++++++++++++---- src/app/app.theme.js | 3 ++- src/app/appbar/sidebarView.html | 31 ++++++++++++++----------- src/index.html | 6 ++--- src/main.js | 12 +++++++++- 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 7a1eef11..19941767 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -9,6 +9,8 @@ (function() { const ammHelp = require('./automint_modules/am-help.js'); + const ipcRenderer = require("electron").ipcRenderer; + const BrowserWindow = require('electron').remote.BrowserWindow; angular.module('automintApp') .controller('lockScreenCtrl', LockScreenController) @@ -16,7 +18,7 @@ .controller('appSideBarCtrl', SidebarController); HeaderbarController.$inject = ['$rootScope', '$scope', '$state', '$timeout', '$mdSidenav', 'amRootFactory']; - SidebarController.$inject = ['$state', '$mdSidenav']; + SidebarController.$inject = ['$scope', '$state', '$http', '$mdSidenav']; LockScreenController.$inject = ['$rootScope', '$state', '$window', 'amRootFactory', 'utils']; function LockScreenController($rootScope, $state, $window, amRootFactory, utils) { @@ -125,12 +127,9 @@ } } - function SidebarController($state, $mdSidenav) { + function SidebarController($scope, $state, $http, $mdSidenav) { var vm = this; - // map functions to view model - vm.openState = openState; - // objects passed to view model vm.items = [{ name: 'Dashboard', @@ -153,6 +152,38 @@ icon: 'settings', state: 'restricted.settings' }]; + vm.isAutomintUpdateAvailable = undefined; + + // map functions to view model + vm.openState = openState; + vm.doUpdate = doUpdate; + + // default execution steps + ipcRenderer.on('automint-updated', listenToAutomintUpdates); + getPackageFile(); + + // function definitions + + function listenToAutomintUpdates(event, arg) { + vm.isAutomintUpdateAvailable = arg; + $scope.$apply(); + } + + function doUpdate() { + BrowserWindow.getFocusedWindow().reload(); + } + + function getPackageFile() { + $http.get('package.json').success(success).catch(failure); + + function success(res) { + vm.automintVersion = res.version; + } + + function failure(err) { + console.warn(err); + } + } function openState(state) { $mdSidenav('main-nav-left').close() diff --git a/src/app/app.theme.js b/src/app/app.theme.js index 38cf6cce..2428ef88 100644 --- a/src/app/app.theme.js +++ b/src/app/app.theme.js @@ -24,7 +24,8 @@ 'hue-1': '50' }) .accentPalette('light-blue', { - 'default': '700' + 'default': '700', + 'hue-1': '500' }) .warnPalette('green', { 'default': '600' diff --git a/src/app/appbar/sidebarView.html b/src/app/appbar/sidebarView.html index b3704930..cfdde584 100644 --- a/src/app/appbar/sidebarView.html +++ b/src/app/appbar/sidebarView.html @@ -1,15 +1,20 @@ -
- - - - - -
-
-
{{ item.icon }}
-
{{ item.name }}
-
+ + + + + +
+
+
{{ item.icon }}
+
{{ item.name }}
- - +
+
+
+
+
+ + Apply Update + + v{{sidebarVm.automintVersion}}
\ No newline at end of file diff --git a/src/index.html b/src/index.html index 57167ecd..ae458efd 100644 --- a/src/index.html +++ b/src/index.html @@ -22,10 +22,12 @@ + + @@ -57,9 +59,7 @@
- -
-
+
diff --git a/src/main.js b/src/main.js index e130ff03..0da482a9 100644 --- a/src/main.js +++ b/src/main.js @@ -13,7 +13,8 @@ const app = electron.app; // Module to create native browser window. const BrowserWindow = electron.BrowserWindow; - + // Module for IPC + const ipcMain = electron.ipcMain; // Importing Eelctron Squirrel Startup if(require('electron-squirrel-startup')) return; // Module to Auto Update app @@ -21,7 +22,11 @@ var os = require('os'); // var feedURL = 'http://updates.automint.in/releases/' + (os.platform()) + '/' + (os.arch()); var feedURL = 'http://updates.automint.in/releases/win32/ia32'; + autoUpdater.addListener("error", function(error) {}); + + autoUpdater.addListener("update-downloaded", OnAutomintUpdated); + autoUpdater.setFeedURL(feedURL); if (process.argv[1] == '--squirrel-firstrun') { setTimeout(()=> { @@ -63,6 +68,10 @@ } }); + function OnAutomintUpdated(event, releaseNotes, releaseName, releaseDate, updateURL) { + mainWindow.webContents.send('automint-updated', true); + } + function createWindow() { // Create the browser window. mainWindow = new BrowserWindow({ @@ -71,6 +80,7 @@ }); mainWindow.maximize(); // and load the index.html of the app. + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Emitted when the window is closed. From eac32f8ee413ec184531874e8b1a87754491c00e Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 18 Jul 2016 11:43:34 +0530 Subject: [PATCH 03/91] Enhancements #143 & #157 forwared to Win Platform --- src/app/app.controller.js | 18 ++--- src/app/appbar/sidebarView.html | 3 +- .../settings/settings.controller.js | 21 +++++- src/app/components/settings/settings.html | 65 ++++++++++++------- src/index.html | 2 - 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 19941767..e54c37ca 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -8,9 +8,11 @@ /// (function() { + let electron = require('electron').remote; const ammHelp = require('./automint_modules/am-help.js'); const ipcRenderer = require("electron").ipcRenderer; - const BrowserWindow = require('electron').remote.BrowserWindow; + const amApp = electron.app; + const BrowserWindow = electron.BrowserWindow; angular.module('automintApp') .controller('lockScreenCtrl', LockScreenController) @@ -170,19 +172,13 @@ } function doUpdate() { - BrowserWindow.getFocusedWindow().reload(); + console.log(require('electron').remote.app); + amApp.relaunch({args: process.argv.slice(1) + ['--relaunch']}) + amApp.exit(0); } function getPackageFile() { - $http.get('package.json').success(success).catch(failure); - - function success(res) { - vm.automintVersion = res.version; - } - - function failure(err) { - console.warn(err); - } + vm.automintVersion = amApp.getVersion(); } function openState(state) { diff --git a/src/app/appbar/sidebarView.html b/src/app/appbar/sidebarView.html index cfdde584..eae1f7e1 100644 --- a/src/app/appbar/sidebarView.html +++ b/src/app/appbar/sidebarView.html @@ -13,7 +13,8 @@
- + + Apply Update v{{sidebarVm.automintVersion}} diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 0377f9f3..83aec942 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -8,8 +8,11 @@ /// (function() { - angular.module('automintApp') - .controller('amCtrlSettings', SettingsController); + let electron = require('electron').remote; + const amApp = electron.app; + const dialog = electron.dialog; + + angular.module('automintApp').controller('amCtrlSettings', SettingsController); SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', 'utils', 'amBackup', 'amLogin', 'amImportdata', 'amIvSettings', 'amSeTaxSettings', 'amSettings']; @@ -37,6 +40,7 @@ vm.label_password = 'Enter Password:'; vm.serviceStateList = ['Job Card', 'Estimate', 'Bill']; vm.serviceState = vm.serviceStateList[2]; + vm.amAppPath = 'Default'; // invoice settings vm.workshop = { name: '', @@ -77,6 +81,7 @@ vm.handleUploadedCoverPic = handleUploadedCoverPic; vm.handlePasscodeVisibility = handlePasscodeVisibility; vm.saveDefaultServiceType = saveDefaultServiceType; + vm.setAmAppDataPath = setAmAppDataPath; // invoice settings vm.changeWorkshopNameLabel = changeWorkshopNameLabel; vm.changeWorkshopPhoneLabel = changeWorkshopPhoneLabel; @@ -127,6 +132,7 @@ checkLogin(); getPasscode(); getDefaultServiceType(); + getAmAppDataPath(); // invoice settings getWorkshopDetails(); getInvoiceSettings(); @@ -140,6 +146,17 @@ // function definitions + function setAmAppDataPath() { + var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); + amApp.setPath('userData', newPath); + console.log(newPath); + // /Users/ndkcha/Library/Application Support/Automint + } + + function getAmAppDataPath() { + vm.amAppPath = amApp.getPath('userData'); + } + function getDefaultServiceType() { amSettings.getDefaultServiceType().then(success).catch(failure); diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 07cc8577..6ab4f705 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -14,7 +14,7 @@ border-bottom: 1px solid black; width: 97%; font-weight: 400; - padding: 0.7rem; + padding: 6px; outline: none; background: transparent; color: rgba(30, 30, 30, 0.7); @@ -126,8 +126,8 @@ - - + + lock_openSee Passcode @@ -139,11 +139,11 @@ - +
Default Service Type
- + {{state}}
@@ -151,7 +151,7 @@
- + Backup Your Data
@@ -214,7 +214,7 @@
Exclusive - + Inclusive
@@ -237,17 +237,32 @@
Exclusive - + Inclusive
-
+
VAT (%):
+ + +
+ App Data +
+
+ {{vm.amAppPath}} + + + Change + folder_open + +
+
+
@@ -256,8 +271,8 @@
- - Workshop Details + + Workshop Details
domain @@ -286,18 +301,18 @@
- - + + Display in Invoice
-
+
Social Links
- + Show Links
@@ -321,10 +336,10 @@
- +
- Last Invoice Number: + Last Invoice Number:
@@ -334,7 +349,7 @@
- Last Estimate Number: + Last Estimate Number:
@@ -344,7 +359,7 @@
- Last Job Card Number: + Last Job Card Number:
@@ -355,17 +370,17 @@ - + Workshop Logo - + - + Display in Invoice @@ -388,16 +403,16 @@
Printing Margin (in cm)
- + Enable Margins
- + vertical_align_top - + vertical_align_bottom diff --git a/src/index.html b/src/index.html index ae458efd..30968b2d 100644 --- a/src/index.html +++ b/src/index.html @@ -22,12 +22,10 @@ - - From 828ffbd268e4595594b52c54b876bde60ac316cc Mon Sep 17 00:00:00 2001 From: Viral Kacha Date: Mon, 18 Jul 2016 18:53:01 +0530 Subject: [PATCH 04/91] Enhancement #157 | Restart when App updates > It shows up the update button, but clicking on that it doesn't restart the app, so update is not applied. --- src/app/app.controller.js | 10 ++++------ src/app/appbar/sidebarView.html | 3 +-- src/app/components/settings/settings.controller.js | 4 ++-- src/main.js | 8 +++++++- src/package.json | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/app/app.controller.js b/src/app/app.controller.js index e54c37ca..5692b9da 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -2,13 +2,13 @@ * Closure for root level controllers * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// (function() { - let electron = require('electron').remote; + const electron = require('electron').remote; const ammHelp = require('./automint_modules/am-help.js'); const ipcRenderer = require("electron").ipcRenderer; const amApp = electron.app; @@ -155,7 +155,7 @@ state: 'restricted.settings' }]; vm.isAutomintUpdateAvailable = undefined; - + // map functions to view model vm.openState = openState; vm.doUpdate = doUpdate; @@ -172,9 +172,7 @@ } function doUpdate() { - console.log(require('electron').remote.app); - amApp.relaunch({args: process.argv.slice(1) + ['--relaunch']}) - amApp.exit(0); + ipcRenderer.send('am-quit-update', true); } function getPackageFile() { diff --git a/src/app/appbar/sidebarView.html b/src/app/appbar/sidebarView.html index eae1f7e1..cfdde584 100644 --- a/src/app/appbar/sidebarView.html +++ b/src/app/appbar/sidebarView.html @@ -13,8 +13,7 @@
- - + Apply Update v{{sidebarVm.automintVersion}} diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 83aec942..52287db4 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -2,13 +2,13 @@ * Controller for Settings component * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// (function() { - let electron = require('electron').remote; + const electron = require('electron').remote; const amApp = electron.app; const dialog = electron.dialog; diff --git a/src/main.js b/src/main.js index 0da482a9..cae676c5 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,7 @@ * Entrance file for Atom Electron App * @author ndkcha * @since 0.1.0 - * @version 0.6.4 by @vrlkacha + * @version 0.7.0 */ 'use strict'; @@ -68,6 +68,12 @@ } }); + ipcMain.on('am-quit-update', updateAndRestartApp); + + function updateAndRestartApp(event, args) { + autoUpdater.quitAndInstall(); + } + function OnAutomintUpdated(event, releaseNotes, releaseName, releaseDate, updateURL) { mainWindow.webContents.send('automint-updated', true); } diff --git a/src/package.json b/src/package.json index ffffd125..9bc4a64b 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "Automint", - "version": "0.6.5", + "version": "0.7.0", "description": "CRM by Automint", "main": "main.js", "scripts": { From 28f6bd1f44340f10ce88b72d580bcd424534ce1f Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 19 Jul 2016 15:12:13 +0530 Subject: [PATCH 05/91] Enhancement #143 | Store userData somewhere else --- .../services/services-add.controller.js | 4 +- .../settings/settings.controller.js | 16 ++++++- src/automint_modules/am-preferences.js | 12 +++++- src/main.js | 43 ++++++++++++++++++- src/package.json | 1 + 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 7cf17bd0..c58f5c38 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -2,7 +2,7 @@ * Controller for Add Service module * @author ndkcha * @since 0.4.1 - * @version 0.6.5 + * @version 0.7.0 */ /// @@ -222,8 +222,6 @@ } function IsProblemFocusIndex(index) { - if (vm.problemFocusIndex == index) - console.log('true'); return (vm.problemFocusIndex == index); } diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 52287db4..5c62a6b4 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -9,8 +9,10 @@ (function() { const electron = require('electron').remote; + const fse = require('fs-extra'); const amApp = electron.app; const dialog = electron.dialog; + const ammPreferences = require('./automint_modules/am-preferences.js'); angular.module('automintApp').controller('amCtrlSettings', SettingsController); @@ -148,8 +150,18 @@ function setAmAppDataPath() { var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); - amApp.setPath('userData', newPath); - console.log(newPath); + if (newPath) { + ammPreferences.storePreference('automint.userDataPath', newPath[0]); + fse.move(amApp.getPath('userData'), newPath[0], { + clobber: true + }, success); + } + + function success(res) { + var exec = require('child_process').exec; + exec(process.argv.join(' ')); + amApp.quit(); + } // /Users/ndkcha/Library/Application Support/Automint } diff --git a/src/automint_modules/am-preferences.js b/src/automint_modules/am-preferences.js index 6546af46..a6fcf4b7 100644 --- a/src/automint_modules/am-preferences.js +++ b/src/automint_modules/am-preferences.js @@ -2,7 +2,7 @@ * Module for storing local usage preferences * @author ndkcha * @since 0.6.4 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -23,6 +23,7 @@ // export as module module.exports = { + getUserData: getUserData, storePreference: storePreference, getPreference: getPreference, getAllPreferences: getAllPreferences @@ -30,6 +31,15 @@ // function definitions + function getUserData() { + try { + var data = JSON.parse(fs.readFileSync(PREF_FILE), 'utf-8'); + return (data['automint.userDataPath']); + } catch(e) { + return e404; + } + } + function storePreference(key, value) { fs.readFile(PREF_FILE, changePreferences); diff --git a/src/main.js b/src/main.js index cae676c5..6144d809 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ const electron = require('electron'); // Module to control application life. const app = electron.app; + const dialog = electron.dialog; // Module to create native browser window. const BrowserWindow = electron.BrowserWindow; // Module for IPC @@ -22,6 +23,11 @@ var os = require('os'); // var feedURL = 'http://updates.automint.in/releases/' + (os.platform()) + '/' + (os.arch()); var feedURL = 'http://updates.automint.in/releases/win32/ia32'; + // Module to check preferences + const ammPreferences = require('./automint_modules/am-preferences.js'); + const fs = require('fs'); + // Keep track of path whether it exists + var isUserDataPathExists = false; autoUpdater.addListener("error", function(error) {}); @@ -47,9 +53,22 @@ return; } + // setUserDataPath(); + var amUserDataPath = ammPreferences.getUserData(); + if ((typeof amUserDataPath) == "string") { + try { + fs.accessSync(amUserDataPath); + app.setPath('userData', amUserDataPath); + isUserDataPathExists = true; + } catch(e) { + isUserDataPathExists = false; + } + } + + // This method will be called when Electron has finished // initialization and is ready to create browser windows. - app.on('ready', createWindow); + app.on('ready', OnAppReady); // Quit when all windows are closed. app.on('window-all-closed', function() { @@ -78,6 +97,28 @@ mainWindow.webContents.send('automint-updated', true); } + function OnAppReady() { + if (isUserDataPathExists) + createWindow(); + else { + var msgbox = dialog.showMessageBox({ + type: 'info', + message: 'Select Path of User Data', + buttons: ['OK'], + defaultId: 0, + icon: undefined + }, openCorrectFileUrl); + } + + function openCorrectFileUrl() { + var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); + ammPreferences.storePreference('automint.userDataPath', newPath[0]); + var exec = require('child_process').exec; + exec(process.argv.join(' ')); + app.quit(); + } + } + function createWindow() { // Create the browser window. mainWindow = new BrowserWindow({ diff --git a/src/package.json b/src/package.json index 9bc4a64b..0c8c684b 100644 --- a/src/package.json +++ b/src/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "base64url": "^1.0.6", + "fs-extra": "^0.30.0", "google-auth-library": "^0.9.8", "googleapis": "^5.2.1", "premailer-api": "^1.0.4", From 955d63ace34193fa1186590793e7834d44dde384 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 19 Jul 2016 15:24:12 +0530 Subject: [PATCH 06/91] Enhancement | Removed back button tooltip Effective in all add forms --- src/app/components/customers/customers_add.html | 1 - src/app/components/inventory/inventory_add.html | 1 - src/app/components/services/services_add.html | 1 - src/app/components/treatments/memberships/memberships_add.html | 1 - src/app/components/treatments/packages/packages_add.html | 1 - src/app/components/treatments/regular/treatments_add.html | 1 - 6 files changed, 6 deletions(-) diff --git a/src/app/components/customers/customers_add.html b/src/app/components/customers/customers_add.html index 849226b8..c1b777d8 100644 --- a/src/app/components/customers/customers_add.html +++ b/src/app/components/customers/customers_add.html @@ -13,7 +13,6 @@ arrow_back - Back diff --git a/src/app/components/inventory/inventory_add.html b/src/app/components/inventory/inventory_add.html index 332e9ae7..80baa17f 100644 --- a/src/app/components/inventory/inventory_add.html +++ b/src/app/components/inventory/inventory_add.html @@ -1,6 +1,5 @@ arrow_back - Back
diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 767667b1..992e2e24 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -130,7 +130,6 @@ arrow_back - Back diff --git a/src/app/components/treatments/memberships/memberships_add.html b/src/app/components/treatments/memberships/memberships_add.html index 9a2a6097..f241adfc 100644 --- a/src/app/components/treatments/memberships/memberships_add.html +++ b/src/app/components/treatments/memberships/memberships_add.html @@ -1,6 +1,5 @@ arrow_back - Back
diff --git a/src/app/components/treatments/packages/packages_add.html b/src/app/components/treatments/packages/packages_add.html index daec1b9e..6a249173 100644 --- a/src/app/components/treatments/packages/packages_add.html +++ b/src/app/components/treatments/packages/packages_add.html @@ -1,6 +1,5 @@ arrow_back - Back
diff --git a/src/app/components/treatments/regular/treatments_add.html b/src/app/components/treatments/regular/treatments_add.html index f9f5dbc1..4c64ca47 100644 --- a/src/app/components/treatments/regular/treatments_add.html +++ b/src/app/components/treatments/regular/treatments_add.html @@ -1,6 +1,5 @@ arrow_back - Back
From 011e39cc9f9c3614aa5763ba01dde63f8414dc1a Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 19 Jul 2016 15:56:36 +0530 Subject: [PATCH 07/91] Enhancement #143 | Bug Fix for First Time Launch --- src/main.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 6144d809..671d55c9 100644 --- a/src/main.js +++ b/src/main.js @@ -27,7 +27,7 @@ const ammPreferences = require('./automint_modules/am-preferences.js'); const fs = require('fs'); // Keep track of path whether it exists - var isUserDataPathExists = false; + var isUserDataPathExists = true; autoUpdater.addListener("error", function(error) {}); @@ -130,6 +130,8 @@ mainWindow.loadURL('file://' + __dirname + '/index.html'); + mainWindow.webContents.openDevTools(); + // Emitted when the window is closed. mainWindow.on('closed', function() { // Dereference the window object, usually you would store windows From 64e3f30965789ef9666e1c077fb57bad67fcfe88 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 21 Jul 2016 17:50:10 +0530 Subject: [PATCH 08/91] Enhancement #146 | Customer Profile --- src/app/app.states.js | 8 +- .../customers/customers-add.controller.js | 9 +- .../customers/customers-edit.controller.js | 432 +++++++----------- .../components/customers/customers_add.html | 55 +-- .../components/customers/customers_edit.html | 248 ++++++++++ .../customers/tmpl/vehicle-crud.controller.js | 135 ++++++ .../customers/tmpl/vehicle-crud.tmpl.html | 66 +++ .../dashboard/dashboard.controller-deps.js | 3 +- .../invoices/invoices-view.controller.js | 5 +- .../services/services-edit.controller.js | 5 +- src/app/components/services/services_add.html | 48 +- src/assets/css/automint.css | 11 +- src/main.js | 2 +- 13 files changed, 659 insertions(+), 368 deletions(-) create mode 100644 src/app/components/customers/customers_edit.html create mode 100644 src/app/components/customers/tmpl/vehicle-crud.controller.js create mode 100644 src/app/components/customers/tmpl/vehicle-crud.tmpl.html diff --git a/src/app/app.states.js b/src/app/app.states.js index 564b1074..1db61b56 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -2,7 +2,7 @@ * Closure for state definitions and mappings to template files * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -100,12 +100,11 @@ }) .state('restricted.customers.edit', { url: '/edit', - templateUrl:'app/components/customers/customers_add.html', + templateUrl:'app/components/customers/customers_edit.html', controller: 'amCtrlCuUI', controllerAs: 'vm', params: { id: undefined, - openTab: undefined, fromState: undefined }, resolve: { @@ -439,7 +438,8 @@ function loadCuUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', - 'app/components/customers/customers-edit.controller.js' + 'app/components/customers/customers-edit.controller.js', + 'app/components/customers/tmpl/vehicle-crud.controller.js' ]) } function loadTreatmentDeps($ocLazyLoad) { diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index d471c36d..1bcfd9f7 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -2,7 +2,7 @@ * Controller for Add Customer component * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -61,7 +61,6 @@ vm.changeVehicleRegLabel = changeVehicleRegLabel; vm.changeVehicleTab = changeVehicleTab; vm.changeUserTab = changeUserTab; - vm.isAddOperation = isAddOperation; vm.save = save; vm.queryMembershipChip = queryMembershipChip; vm.OnClickMembershipChip = OnClickMembershipChip; @@ -385,12 +384,6 @@ vm.vehicle.model = ''; } - // return boolean response to different configurations [BEGIN] - function isAddOperation() { - return true; - } - // return boolean response to different configurations [END] - // change user tab selector variable function changeUserTab(bool) { vm.userTab = bool; diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 348766af..004dbb22 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -2,7 +2,7 @@ * Controller for Edit Customer component * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -12,25 +12,20 @@ .controller('amCtrlCuUI', CustomerEditController) .controller('amCtrlMeD', MembershipEditDialogController); - CustomerEditController.$inject = ['$state', '$q', '$log', '$filter', '$mdDialog', 'utils', 'amCustomers']; + CustomerEditController.$inject = ['$scope', '$state', '$q', '$log', '$filter', '$mdDialog', 'utils', 'amCustomers']; MembershipEditDialogController.$inject = ['$mdDialog', '$filter', 'membership', 'treatments']; - function CustomerEditController($state, $q, $log, $filter, $mdDialog, utils, amCustomers) { + function CustomerEditController($scope, $state, $q, $log, $filter, $mdDialog, utils, amCustomers) { // initialize view model var vm = this; // temporary named assignments var autofillVehicle = false; var userDbInstance; + var nextDueDate = new Date(); + nextDueDate.setMonth(nextDueDate.getMonth() + 3); // vm assignments to keep track of UI related elements - vm.label_userName = 'Enter Full Name'; - vm.label_userMobile = 'Enter Mobile Number'; - vm.label_userEmail = 'Enter Email:'; - vm.label_userAddress = 'Enter Address:'; - vm.label_vehicleReg = 'Enter Vehicle Registration Number'; - vm.label_vehicleManuf = 'Manufacturer:'; - vm.label_vehicleModel = 'Model:'; vm.user = { mobile: '', name: '', @@ -41,7 +36,8 @@ id: '', reg: '', manuf: '', - model: '' + model: '', + }; vm.manufacturers = []; vm.models = []; @@ -54,59 +50,38 @@ total: 0 }; vm.serviceStateList = ['Job Card', 'Estimate', 'Bill']; - vm.isNextDueService = false; - vm.nextDueDate = new Date(); - vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); + vm.vNextDue = {}; + vm.customerTypeList = ['Customer', 'Agency']; + vm.paymentDone = 0; + vm.paymentDue = 0; // function maps - vm.convertNameToTitleCase = convertNameToTitleCase; - vm.convertRegToCaps = convertRegToCaps; - vm.searchVehicleChange = searchVehicleChange; - vm.manufacturersQuerySearch = manufacturersQuerySearch; - vm.modelQuerySearch = modelQuerySearch; - vm.changeUserNameLabel = changeUserNameLabel; - vm.changeUserMobileLabel = changeUserMobileLabel; - vm.changeUserEmailLabel = changeUserEmailLabel; - vm.changeUserAddressLabel = changeUserAddressLabel; - vm.changeVehicleRegLabel = changeVehicleRegLabel; - vm.changeVehicleTab = changeVehicleTab; vm.changeVehicle = changeVehicle; vm.isAddOperation = isAddOperation; - vm.chooseVehicle = chooseVehicle; vm.save = save; vm.queryMembershipChip = queryMembershipChip; vm.OnClickMembershipChip = OnClickMembershipChip; vm.OnAddMembershipChip = OnAddMembershipChip; vm.changeMembershipTab = changeMembershipTab; vm.goBack = goBack; - vm.changeServicesTab = changeServicesTab; vm.getServiceDate = getServiceDate; vm.editService = editService; - vm.deleteService = deleteService; vm.goToInvoice = goToInvoice; vm.autoCapitalizeCustomerAddress = autoCapitalizeCustomerAddress; - vm.autoCapitalizeVehicleModel = autoCapitalizeVehicleModel; vm.unsubscribeMembership = unsubscribeMembership; vm.IsServiceDue = IsServiceDue; vm.IsServiceStateIv = IsServiceStateIv; vm.IsServiceStateEs = IsServiceStateEs; vm.IsServiceStateJc = IsServiceStateJc; vm.getDate = getDate; + vm.IsVehicleSelected = IsVehicleSelected; + vm.editVehicle = editVehicle; // default execution steps + // $state.params.id = ($state.params.id == undefined) ? "usr-anand-kacha-772d071e-852c-4a45-aaaf-089d80f73449" : $state.params.id; if ($state.params.id != undefined) { getMemberships(getRegularTreatments, getVehicleTypes, getCustomer); - switch ($state.params.openTab) { - case 'services': - changeServicesTab(true); - break; - case 'vehicle': - changeVehicleTab(true); - break; - default: - setTimeout(focusCustomerMobile, 300); - break; - } + setTimeout(focusCustomerMobile, 300); } else { utils.showSimpleToast('Something went wrong!'); $state.go('restricted.customers.all'); @@ -114,6 +89,87 @@ // function definitions + function editVehicle(id) { + changeVehicle(id); + $mdDialog.show({ + controller: 'amCtrlCuVeCRUD', + controllerAs: 'vm', + templateUrl: 'app/components/customers/tmpl/vehicle-crud.tmpl.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + vehicle: vm.vehicle + }, + clickOutsideToClose: true + }).then(closeVehicleDialog).catch(closeVehicleDialog); + + function closeVehicleDialog(res) { + if (!res) + return; + var vId = res.id; + if (!userDbInstance.user.vehicles) + userDbInstance.user.vehicles = {}; + + var prefixVehicle = 'vhcl' + ((vm.vehicle.manuf && vm.vehicle.model) ? '-' + angular.lowercase(vm.vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vm.vehicle.model).replace(' ', '-') : ''); + + if (vId == undefined) + vId = utils.generateUUID(prefixVehicle); + + if (userDbInstance.user.vehicles[vId]) { + var tv = userDbInstance.user.vehicles[vId]; + if ((tv.manuf != res.manuf) || (tv.model != res.model)) { + vId = utils.generateUUID(prefixVehicle); + userDbInstance.user.vehicles[vId] = tv; + vm.vNextDue[vId] = vm.vNextDue[res.id]; + delete userDbInstance.user.vehicles[res.id]; + delete vm.vNextDue[res.id]; + } + } else + userDbInstance.user.vehicles[vId] = {}; + var intermvehicle = userDbInstance.user.vehicles[vId]; + intermvehicle.reg = res.reg; + intermvehicle.manuf = res.manuf; + intermvehicle.model = res.model; + + var longname = (intermvehicle.manuf ? intermvehicle.manuf + ' ' : '') + (intermvehicle.model ? intermvehicle.model + ' ' : '') + (intermvehicle.reg ? ((intermvehicle.manuf || intermvehicle.model) ? ' - ' : '') + intermvehicle.reg : ''); + var shortname = (longname.length <= 45) ? longname : longname.substr(0, 45) + '...'; + var isLongName = (longname.length > 45); + + var vfound = $filter('filter')(vm.possibleVehicleList, { + id: res.id + }, true); + + if (vfound.length == 1) { + vfound[0].id = vId + vfound[0].reg = res.reg; + vfound[0].manuf = res.manuf; + vfound[0].model = res.model; + vfound[0].name = longname; + vfound[0].shortname = shortname; + vfound[0].isLongName = isLongName; + } else { + vm.possibleVehicleList.push({ + id: vId, + reg: res.reg, + manuf: res.manuf, + model: res.model, + name: longname, + shortname: shortname, + isLongName: isLongName + }); + vm.vNextDue[vId] = { + isNextDue: false, + nextdue: nextDueDate + } + } + changeVehicle(vId); + } + } + + function IsVehicleSelected(id) { + return (vm.currentVehicleId == id); + } + function getDate(date) { return moment(date).format('DD MMM YYYY'); } @@ -157,14 +213,6 @@ $('#ami-customer-mobile').focus(); } - function autoCapitalizeVehicleModel() { - vm.vehicle.model = utils.autoCapitalizeWord(vm.vehicle.model); - } - - function autoCapitalizeVehicleManuf() { - vm.vehicle.manuf = utils.autoCapitalizeWord(vm.vehicle.manuf); - } - function autoCapitalizeCustomerAddress() { vm.user.address = utils.autoCapitalizeWord(vm.user.address); } @@ -178,40 +226,6 @@ fromState: 'customers.edit.services' }); } - - // delete service from UI - function deleteService(service, ev) { - var confirm = $mdDialog.confirm() - .textContent('Are you sure you want to delete the service ?') - .ariaLabel('Delete Customer') - .targetEvent(ev) - .ok('Yes') - .cancel('No'); - - $mdDialog.show(confirm).then(performDelete, ignoreDelete); - - function performDelete() { - amServices.deleteService(service.cstmr_id, service.vhcl_id, service.srvc_id).then(success).catch(failure); - } - - function ignoreDelete() { - console.info('nope'); - } - - - function success(res) { - if (res.ok) { - utils.showSimpleToast('Service has been deleted.'); - setTimeout(getServices, 200); - } else - failure(); - } - - function failure(err) { - console.warn(err); - utils.showSimpleToast('Service can not be deleted at moment. Please Try Again!'); - } - } // transit to state to view selected invoice function goToInvoice(service) { @@ -226,10 +240,6 @@ function getServiceDate(date) { return moment(date).format('DD MMM YYYY'); } - - function changeServicesTab(bool) { - vm.servicesTab = bool; - } function goBack() { var transitState = 'restricted.customers.all'; @@ -487,6 +497,8 @@ vm.user.email = res.user.email; vm.user.name = res.user.name; vm.user.address = res.user.address; + if (res.user.type) + vm.user.type = res.user.type; changeUserMobileLabel(); changeUserEmailLabel(); changeUserNameLabel(); @@ -497,41 +509,28 @@ Object.keys(res.user.vehicles).forEach(iterateVehicle, this); vm.serviceQuery.total = vm.services.length; vm.possibleVehicleList = pvl; - changeVehicle(pvl.length > 0 ? pvl[0].id : undefined); - vm.services.sort(dateSort); - - function dateSort(lhs, rhs) { - return rhs.srvc_date.localeCompare(lhs.srvc_date); - } + changeVehicle(undefined); + $scope.$apply(); function iterateVehicle(vId) { var vehicle = res.user.vehicles[vId]; + var longname = (vehicle.manuf ? vehicle.manuf + ' ' : '') + (vehicle.model ? vehicle.model + ' ' : '') + (vehicle.reg ? ((vehicle.manuf || vehicle.model) ? ' - ' : '') + vehicle.reg : ''); + var shortname = (longname.length <= 45) ? longname : longname.substr(0, 45) + '...'; + var isLongName = (longname.length > 45); + vm.vNextDue[vId] = { + isNextDue: (vehicle.nextdue != undefined), + nextdue: (vehicle.nextdue ? new Date(vehicle.nextdue) : nextDueDate) + } pvl.push({ id: vId, reg: vehicle.reg, manuf: vehicle.manuf, model: vehicle.model, - name: vehicle.manuf + ' - ' + vehicle.model + (vehicle.reg == '' ? '' : ', ' + vehicle.reg), + name: longname, + shortname: shortname, + isLongName: isLongName, nextdue: vehicle.nextdue }); - if (vehicle.services) - Object.keys(vehicle.services).forEach(iterateServices); - - function iterateServices(sId) { - var service = vehicle.services[sId]; - vm.services.push({ - cstmr_id: res._id, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_reg: vehicle.reg, - vhcl_id: vId, - srvc_id: sId, - srvc_date: service.date, - srvc_cost: service.cost, - srvc_status: utils.convertToTitleCase(service.status), - srvc_state: service.state - }); - } } function iterateMemberships(membership) { res.user.memberships[membership].name = membership; @@ -546,32 +545,74 @@ } } + function loadServices() { + vm.services = []; + vm.paymentDone = 0; + vm.paymentDue = 0; + if (vm.vehicle.id) { + if (userDbInstance.user && userDbInstance.user.vehicles && userDbInstance.user.vehicles[vm.vehicle.id]) + iterateVehicle(vm.vehicle.id); + } else { + if (userDbInstance.user && userDbInstance.user.vehicles) + Object.keys(userDbInstance.user.vehicles).forEach(iterateVehicle); + } + vm.services.sort(dateSort); + + function dateSort(lhs, rhs) { + return rhs.srvc_date.localeCompare(lhs.srvc_date); + } + + function iterateVehicle(vId) { + var vehicle = userDbInstance.user.vehicles[vId]; + if (vehicle.services) + Object.keys(vehicle.services).forEach(iterateServices); + + function iterateServices(sId) { + var service = vehicle.services[sId]; + if (service.status == 'paid') + vm.paymentDone += parseFloat(service.cost); + else + vm.paymentDue += parseFloat(service.cost); + vm.services.push({ + cstmr_id: userDbInstance._id, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_reg: vehicle.reg, + vhcl_id: vId, + srvc_id: sId, + srvc_date: service.date, + srvc_cost: service.cost, + srvc_status: utils.convertToTitleCase(service.status), + srvc_state: service.state + }); + } + } + } + function changeVehicle(id) { if (!id) { setDefaultVehicle(); + loadServices(); return; } var found = $filter('filter')(vm.possibleVehicleList, { id: id }, true); if (found.length > 0) { - vm.currentVehicle = found[0].name; + vm.currentVehicleId = found[0].id; vm.vehicle.id = found[0].id; vm.vehicle.reg = found[0].reg; vm.vehicle.manuf = found[0].manuf; vm.vehicle.model = found[0].model; - if (found[0].nextdue && (found[0].nextdue.localeCompare(moment().format()) > 0)) { - vm.isNextDueService = true; - vm.nextDueDate = new Date(found[0].nextdue); - } changeVehicleRegLabel(); autofillVehicle = true; } else setDefaultVehicle(); + loadServices(); } function setDefaultVehicle() { - vm.currentVehicle = 'New Vehicle'; + vm.currentVehicleId = undefined; vm.vehicle.id = undefined; vm.vehicle.reg = ''; vm.vehicle.manuf = ''; @@ -579,128 +620,6 @@ autofillVehicle = false; } - // choose different existing vehicle - function chooseVehicle($mdOpenMenu, ev) { - $mdOpenMenu(ev); - } - - function convertNameToTitleCase() { - vm.user.name = utils.convertToTitleCase(vm.user.name); - } - - function convertRegToCaps() { - vm.vehicle.reg = vm.vehicle.reg.toUpperCase(); - } - - // query search for manufacturers [autocomplete] - function manufacturersQuerySearch() { - var tracker = $q.defer(); - var results = (vm.vehicle.manuf ? vm.manufacturers.filter(createFilterForManufacturers(vm.vehicle.manuf)) : vm.manufacturers); - - if (results.length > 0) { - return results; - } - - amCustomers.getManufacturers().then(allotManufacturers).catch(noManufacturers); - return tracker.promise; - - function allotManufacturers(res) { - vm.manufacturers = res; - results = (vm.vehicle.manuf ? vm.manufacturers.filter(createFilterForManufacturers(vm.vehicle.manuf)) : vm.manufacturers); - tracker.resolve(results); - } - - function noManufacturers(error) { - results = []; - tracker.resolve(results); - } - } - - // create filter for manufacturers' query list - function createFilterForManufacturers(query) { - var lcQuery = angular.lowercase(query); - return function filterFn(item) { - item = angular.lowercase(item); - return (item.indexOf(lcQuery) === 0); - } - } - - // query search for model [autocomplete] - function modelQuerySearch() { - var tracker = $q.defer(); - var results = (vm.vehicle.model ? vm.models.filter(createFilterForModel(vm.vehicle.model)) : vm.models); - - if (results.length > 0) - return results; - - amCustomers.getModels(vm.vehicle.manuf).then(allotModels).catch(noModels); - return tracker.promise; - - function allotModels(res) { - vm.models = res; - results = (vm.vehicle.model ? vm.models.filter(createFilterForModel(vm.vehicle.model)) : vm.models); - tracker.resolve(results); - } - - function noModels(err) { - results = []; - tracker.resolve(results); - } - } - - // create filter for models' query list - function createFilterForModel(query) { - var lcQuery = angular.lowercase(query); - return function filterFn(item) { - item = angular.lowercase(item); - return (item.indexOf(lcQuery) === 0); - } - } - - function searchVehicleChange(e) { - autoCapitalizeVehicleManuf(); - if (!autofillVehicle) { - vm.models = []; - vm.vehicle.model = ''; - autofillVehicle = false; - } else - autofillVehicle = false; - } - - // return boolean response to different configurations [BEGIN] - function isAddOperation() { - return false; - } - // return boolean response to different configurations [END] - - // change vehicle table selector variable - function changeVehicleTab(bool) { - vm.vehicleTab = bool; - } - - // listen to changes in input fields [BEGIN] - function changeUserNameLabel(force) { - vm.isUserName = (force != undefined || vm.user.name != ''); - vm.label_userName = vm.isUserName ? 'Name:' : 'Enter Full Name:'; - } - function changeUserMobileLabel(force) { - vm.isUserMobile = (force != undefined || vm.user.mobile != ''); - vm.label_userMobile = vm.isUserMobile ? 'Mobile:' : 'Enter Mobile Number:'; - } - function changeUserEmailLabel(force) { - vm.isUserEmail = (force != undefined || vm.user.email != ''); - vm.label_userEmail = vm.isUserEmail ? 'Email:' : 'Enter Email:'; - } - function changeUserAddressLabel(force) { - vm.isUserAddress = (force != undefined || vm.user.address != ''); - vm.label_userAddress = vm.isUserAddress ? 'Address:' : 'Enter Address:'; - } - function changeVehicleRegLabel(force) { - vm.isVehicleReg = (force != undefined || vm.vehicle.reg != ''); - vm.label_vehicleReg = vm.isVehicleReg ? 'Vehcile Registration Number:' : 'Enter Vehicle Registration Number:'; - } - // listen to changes in input fields [END] - function isSame() { var checkuser = userDbInstance.user.mobile == vm.user.mobile && userDbInstance.user.name == vm.user.name && userDbInstance.user.email == vm.user.email && userDbInstance.user.address == vm.user.address; var checkvehicle = (userDbInstance.user.vehicles && userDbInstance.user.vehicles[vm.vehicle.id] && userDbInstance.user.vehicles[vm.vehicle.id].reg == vm.vehicle.reg && userDbInstance.user.vehicles[vm.vehicle.id].manuf == vm.vehicle.manuf && userDbInstance.user.vehicles[vm.vehicle.id].model == vm.vehicle.model) || (vm.vehicle.reg == '' && vm.vehicle.manuf == '' && vm.vehicle.model == ''); @@ -713,6 +632,7 @@ userDbInstance.user.name = vm.user.name; userDbInstance.user.email = vm.user.email; userDbInstance.user.address = vm.user.address; + userDbInstance.user.type = vm.user.type; if (vm.membershipChips != undefined) { var smArray = $.extend([], vm.membershipChips); @@ -720,35 +640,21 @@ smArray.forEach(addMembershipsToUser); } - if (!((vm.vehicle.reg == '' || vm.vehicle.reg == undefined) && (vm.vehicle.manuf == '' || vm.vehicle.manuf == undefined) && (vm.vehicle.model == '' || vm.vehicle.model == undefined))) { - if (!userDbInstance.user.vehicles) - userDbInstance.user.vehicles = {} - var vehicleDbInstance = userDbInstance.user.vehicles[vm.vehicle.id]; - if (vehicleDbInstance) { - vehicleDbInstance.reg = vm.vehicle.reg; - vehicleDbInstance.manuf = vm.vehicle.manuf; - vehicleDbInstance.model = vm.vehicle.model; - if (vm.isNextDueService) - vehicleDbInstance.nextdue = vm.nextDueDate; - else if (vehicleDbInstance.nextdue) - delete vehicleDbInstance['nextdue']; - } else { - var prefixVehicle = 'vhcl' + ((vm.vehicle.manuf && vm.vehicle.model) ? '-' + angular.lowercase(vm.vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vm.vehicle.model).replace(' ', '-') : ''); - var vo = { - reg: (vm.vehicle.reg == undefined ? '' : vm.vehicle.reg), - manuf: (vm.vehicle.manuf == undefined ? '' : vm.vehicle.manuf), - model: (vm.vehicle.model == undefined ? '' : vm.vehicle.model) - } - if (vm.isNextDueService) - vo.nextdue = vm.nextDueDate; - userDbInstance.user.vehicles[utils.generateUUID(prefixVehicle)] = vo; - } - } + if (Object.keys(vm.vNextDue)) + Object.keys(vm.vNextDue).forEach(iterateDueDates); if (vm.user.id) amCustomers.saveCustomer(userDbInstance).then(successfullSave).catch(failedSave); else failedSave(); + + function iterateDueDates(vId) { + var t = vm.vNextDue[vId]; + if (t.isNextDue) + userDbInstance.user.vehicles[vId].nextdue = moment(t.nextdue).format(); + else if (!t.isNextDue) + delete userDbInstance.user.vehicles[vId].nextdue; + } function addMembershipsToUser(membership) { var mTreatments = $.extend([], membership.treatments); diff --git a/src/app/components/customers/customers_add.html b/src/app/components/customers/customers_add.html index c1b777d8..719138ad 100644 --- a/src/app/components/customers/customers_add.html +++ b/src/app/components/customers/customers_add.html @@ -23,7 +23,7 @@ person_pin - +
@@ -65,30 +65,6 @@
-
-
- Choose Vehicle: - - - {{vm.currentVehicle}} - - - - - {{vehicle.name}} - - - - - - New Vehicle - - - - - (To add new vehicle, click on the button above and select new vehicle) -
-
Choose Manufacturer: @@ -134,35 +110,6 @@
- - - - - - - - - - - - - - - - - - - - - -
VehicleDateAmount
(Rs.)
Payment
{{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}){{vm.getServiceDate(service.srvc_date)}}{{service.srvc_cost}}{{service.srvc_status}}{{service.srvc_state}} - - delete - -
-
- -
diff --git a/src/app/components/customers/customers_edit.html b/src/app/components/customers/customers_edit.html new file mode 100644 index 00000000..0e3b49c2 --- /dev/null +++ b/src/app/components/customers/customers_edit.html @@ -0,0 +1,248 @@ + + + arrow_back + + + + + done + Save + + + + {{vm.user.name}} + + + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + + {{type}} + +
+
+ + {{vm.services.length}} +
+
+
+ + + + {{membership.name}} + + + + {{$chip.name}} + + + +
* Click on Membership for details
+
+
+
+ + Rs.{{vm.paymentDone}} +
+
+ + Rs.{{vm.paymentDue}} +
+
+
+ +
+
+ Vehicles: +
+ All Vehicles +
+
+
+ {{vehicle.shortname}}{{vehicle.name}} +
+ edit + Edit +
+
+
+
+
+ Next Service Reminder: +
+ + {{vm.getDate(vm.vNextDue[vm.vehicle.id].nextdue)}} +
+
+
+ + add + Add Vehicle + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
VehicleDateAmount (Rs.)Payment
{{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}){{vm.getServiceDate(service.srvc_date)}}{{service.srvc_cost}}{{service.srvc_status}}{{service.srvc_state}}
+
+
+
+
\ No newline at end of file diff --git a/src/app/components/customers/tmpl/vehicle-crud.controller.js b/src/app/components/customers/tmpl/vehicle-crud.controller.js new file mode 100644 index 00000000..39e6d82d --- /dev/null +++ b/src/app/components/customers/tmpl/vehicle-crud.controller.js @@ -0,0 +1,135 @@ +/** + * Controller for Dialog box for CRUD Operations on Particular Vehicle + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlCuVeCRUD', VehicleCRUDController); + + VehicleCRUDController.$inject = ['$mdDialog', '$q', 'utils', 'amCustomers', 'vehicle']; + + function VehicleCRUDController($mdDialog, $q, utils, amCustomers, vehicle) { + // initialize view model + var vm = this; + + // named assignments to keep track of temporary variables + var autofillVehicle = false; + + // named assignments to view model + vm.vehicle = vehicle; + vm.manufacturers = []; + vm.model = []; + + // function maps to view model + vm.manufacturersQuerySearch = manufacturersQuerySearch; + vm.modelQuerySearch = modelQuerySearch; + vm.searchVehicleChange = searchVehicleChange; + vm.autoCapitalizeVehicleModel = autoCapitalizeVehicleModel; + vm.convertRegToCaps = convertRegToCaps; + vm.save = save; + vm.cancel = cancel; + + // default execution steps + + // function definitions + + // query search for manufacturers [autocomplete] + function manufacturersQuerySearch() { + var tracker = $q.defer(); + var results = (vm.vehicle.manuf ? vm.manufacturers.filter(createFilterForManufacturers(vm.vehicle.manuf)) : vm.manufacturers); + + if (results.length > 0) { + return results; + } + + amCustomers.getManufacturers().then(allotManufacturers).catch(noManufacturers); + return tracker.promise; + + function allotManufacturers(res) { + vm.manufacturers = res; + results = (vm.vehicle.manuf ? vm.manufacturers.filter(createFilterForManufacturers(vm.vehicle.manuf)) : vm.manufacturers); + tracker.resolve(results); + } + + function noManufacturers(error) { + results = []; + tracker.resolve(results); + } + } + + // create filter for manufacturers' query list + function createFilterForManufacturers(query) { + var lcQuery = angular.lowercase(query); + return function filterFn(item) { + item = angular.lowercase(item); + return (item.indexOf(lcQuery) === 0); + } + } + + // query search for model [autocomplete] + function modelQuerySearch() { + var tracker = $q.defer(); + var results = (vm.vehicle.model ? vm.models.filter(createFilterForModel(vm.vehicle.model)) : vm.models); + + if (results.length > 0) + return results; + + amCustomers.getModels(vm.vehicle.manuf).then(allotModels).catch(noModels); + return tracker.promise; + + function allotModels(res) { + vm.models = res; + results = (vm.vehicle.model ? vm.models.filter(createFilterForModel(vm.vehicle.model)) : vm.models); + tracker.resolve(results); + } + + function noModels(err) { + results = []; + tracker.resolve(results); + } + } + + // create filter for models' query list + function createFilterForModel(query) { + var lcQuery = angular.lowercase(query); + return function filterFn(item) { + item = angular.lowercase(item); + return (item.indexOf(lcQuery) === 0); + } + } + + function searchVehicleChange(e) { + autoCapitalizeVehicleManuf(); + if (!autofillVehicle) { + vm.models = []; + vm.vehicle.model = ''; + autofillVehicle = false; + } else + autofillVehicle = false; + } + + function autoCapitalizeVehicleModel() { + vm.vehicle.model = utils.autoCapitalizeWord(vm.vehicle.model); + } + + function autoCapitalizeVehicleManuf() { + vm.vehicle.manuf = utils.autoCapitalizeWord(vm.vehicle.manuf); + } + + function convertRegToCaps() { + vm.vehicle.reg = vm.vehicle.reg.toUpperCase(); + } + + function save() { + $mdDialog.hide(vm.vehicle); + } + + function cancel() { + $mdDialog.cancel(); + } + } +})(); \ No newline at end of file diff --git a/src/app/components/customers/tmpl/vehicle-crud.tmpl.html b/src/app/components/customers/tmpl/vehicle-crud.tmpl.html new file mode 100644 index 00000000..30555d9a --- /dev/null +++ b/src/app/components/customers/tmpl/vehicle-crud.tmpl.html @@ -0,0 +1,66 @@ + + + + Edit Vehicle Info +
+
+ + + + {{manuf}} + + +
+
+ + + + {{model}} + + +
+
+ + +
+
+
+ + + done + Save + + + close + Cancel + + +
\ No newline at end of file diff --git a/src/app/components/dashboard/dashboard.controller-deps.js b/src/app/components/dashboard/dashboard.controller-deps.js index 01e0559d..4469f649 100644 --- a/src/app/components/dashboard/dashboard.controller-deps.js +++ b/src/app/components/dashboard/dashboard.controller-deps.js @@ -2,7 +2,7 @@ * Controller for dashboard sub-views * @author ndkcha * @since 0.6.4 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -246,7 +246,6 @@ function editCustomer(cId) { $state.go('restricted.customers.edit', { id: cId, - openTab: 'vehicle', fromState: 'dashboard.nextdueservices' }); $mdDialog.hide(); diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 7002b8c9..3288faf1 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -2,7 +2,7 @@ * Controller for View Invoice component * @author ndkcha * @since 0.5.0 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -431,8 +431,7 @@ case 'customers.edit.services': transitState = 'restricted.customers.edit'; transitParams = { - id: $state.params.userId, - openTab: 'services' + id: $state.params.userId } break; } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 0cc1f326..de9501eb 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -2,7 +2,7 @@ * Controller for Edit Service component * @author ndkcha * @since 0.4.1 - * @version 0.6.5 + * @version 0.7.0 */ /// @@ -887,8 +887,7 @@ case 'customers.edit.services': transitState = 'restricted.customers.edit'; transitParams = { - id: $state.params.userId, - openTab: 'services' + id: $state.params.userId } break; } diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 992e2e24..63b3f959 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -10,7 +10,7 @@ transition: all 600ms ease; background: rgba(180, 180, 180, 0.1) !important; } - + .am-box-input { border: 0; padding: 0 15px; @@ -22,15 +22,6 @@ background: rgba(180, 180, 180, 0.1); } - .am-service-input-box { - margin: 0.6rem; - } - - .am-service-input-box label { - margin-bottom: 0; - margin-right: 0.4rem; - } - .am-select { margin: 0; } @@ -136,16 +127,15 @@ {{vm.label_titleCustomerMoreInfo()}} Information -
+
-
+
- +
-
+
@@ -172,37 +162,37 @@
-
+
-
+
-
+
-
+
-
+
{{type}}
-
+
-
+
{{type}}
-
+
Next Service Reminder:
@@ -222,7 +212,7 @@
-
+
@@ -230,11 +220,11 @@
-
+
-
+
More Details more @@ -258,7 +248,7 @@
-
+
@@ -266,7 +256,7 @@
-
+
@@ -275,7 +265,7 @@
-
+
diff --git a/src/assets/css/automint.css b/src/assets/css/automint.css index 742f9b42..99740701 100644 --- a/src/assets/css/automint.css +++ b/src/assets/css/automint.css @@ -2,7 +2,7 @@ * Customised style sheet for Automint App * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ input[type=number]::-webkit-inner-spin-button, @@ -11,6 +11,15 @@ input[type=number]::-webkit-outer-spin-button { margin: 0; } +.am-input-box { + margin: 0.6rem; +} + +.am-input-box label { + margin-bottom: 0; + margin-right: 0.4rem; +} + .am-jobcard-text { color: #FB8C00; font-weight: 500; diff --git a/src/main.js b/src/main.js index 671d55c9..04fde7b7 100644 --- a/src/main.js +++ b/src/main.js @@ -130,7 +130,7 @@ mainWindow.loadURL('file://' + __dirname + '/index.html'); - mainWindow.webContents.openDevTools(); + // mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', function() { From f08a04325a15b3b65d79fec976fa7940b5776762 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 23 Jul 2016 16:33:17 +0530 Subject: [PATCH 09/91] Enhancement #143 | Storing App Data > Used copy function instead of move. And deleted previous folder after copying it. --- src/app/components/settings/settings.controller.js | 8 ++++---- src/main.js | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 5c62a6b4..d66e279b 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -8,6 +8,7 @@ /// (function() { + const ipcRenderer = require('electron').ipcRenderer; const electron = require('electron').remote; const fse = require('fs-extra'); const amApp = electron.app; @@ -152,15 +153,14 @@ var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); if (newPath) { ammPreferences.storePreference('automint.userDataPath', newPath[0]); - fse.move(amApp.getPath('userData'), newPath[0], { + fse.copy(amApp.getPath('userData'), newPath[0], { clobber: true }, success); } function success(res) { - var exec = require('child_process').exec; - exec(process.argv.join(' ')); - amApp.quit(); + fse.removeSync(amApp.getPath('userData')); + ipcRenderer.send('am-do-restart', true); } // /Users/ndkcha/Library/Application Support/Automint } diff --git a/src/main.js b/src/main.js index 04fde7b7..afb57bda 100644 --- a/src/main.js +++ b/src/main.js @@ -89,6 +89,13 @@ ipcMain.on('am-quit-update', updateAndRestartApp); + ipcMain.on('am-do-restart', restartApp); + + function restartApp(event, args) { + app.relaunch(); + app.exit(0); + } + function updateAndRestartApp(event, args) { autoUpdater.quitAndInstall(); } From cd37e00b3b04d95ce3f8fe2ba5a0600fd9837a13 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 24 Jul 2016 18:07:03 +0530 Subject: [PATCH 10/91] Feature #161 | Partial payment support --- src/app/app.service.js | 10 +- src/app/app.states.js | 2 + .../dashboard/dashboard.controller-deps.js | 5 + .../dashboard/dashboard.controller.js | 6 +- .../tmpl/dashboard_due-payments.tmpl.html | 4 +- .../services/services-add.controller.js | 47 ++++++ .../services/services-edit.controller.js | 49 ++++++ src/app/components/services/services_add.html | 56 ++++++- .../tmpl/dialog_partialpayment.controller.js | 146 ++++++++++++++++++ .../services/tmpl/dialog_partialpayment.html | 81 ++++++++++ 10 files changed, 391 insertions(+), 15 deletions(-) create mode 100644 src/app/components/services/tmpl/dialog_partialpayment.controller.js create mode 100644 src/app/components/services/tmpl/dialog_partialpayment.html diff --git a/src/app/app.service.js b/src/app/app.service.js index cbe67736..f6448620 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -2,7 +2,7 @@ * Closure for root level service * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -174,6 +174,7 @@ function iterateServices(sId) { var service = vehicle.services[sId]; var cd = moment(service.date).format('MMM YYYY'); + var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.state == "paid") ? service.cost : 0); cd = angular.lowercase(cd).replace(' ', '-'); if (service._deleted == true) { if (cachedoc[cd][sId] != undefined) @@ -194,7 +195,8 @@ srvc_date: service.date, srvc_cost: service.cost, srvc_status: service.status, - srvc_state: service.state + srvc_state: service.state, + srvc_payreceived: payreceived }; } } @@ -254,6 +256,7 @@ function iterateServices(sId) { var service = vehicle.services[sId]; + var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.state == "paid") ? service.cost : 0); var cd = moment(service.date).format('MMM YYYY'); cd = angular.lowercase(cd).replace(' ', '-'); if (service._deleted == true) { @@ -281,7 +284,8 @@ srvc_date: service.date, srvc_cost: service.cost, srvc_status: service.status, - srvc_state: service.state + srvc_state: service.state, + srvc_payreceived: payreceived }; } } diff --git a/src/app/app.states.js b/src/app/app.states.js index 1db61b56..9308a495 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -476,6 +476,7 @@ function loadSeCIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'app/components/services/tmpl/dialog_partialpayment.controller.js', 'app/components/services/services-add.controller.js' ]) } @@ -489,6 +490,7 @@ function loadSeUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'app/components/services/tmpl/dialog_partialpayment.controller.js', 'app/components/services/services-edit.controller.js' ]) } diff --git a/src/app/components/dashboard/dashboard.controller-deps.js b/src/app/components/dashboard/dashboard.controller-deps.js index 4469f649..a3ec6dfc 100644 --- a/src/app/components/dashboard/dashboard.controller-deps.js +++ b/src/app/components/dashboard/dashboard.controller-deps.js @@ -36,11 +36,16 @@ vm.services = unbilledServices; vm.editService = editService; vm.closeDialog = closeDialog; + vm.getCost = getCost; // default execution steps vm.services.sort(sortFunction); // function definitions + + function getCost(service) { + return (service.srvc_payreceived ? (parseFloat(service.srvc_cost) - parseFloat(service.srvc_payreceived)) : parseFloat(service.srvc_cost)); + } function sortFunction(lhs, rhs) { return (rhs.srvc_date.localeCompare(lhs.srvc_date)); diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index 73e00914..cd227f55 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -2,7 +2,7 @@ * Controller for dashboard view * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -423,7 +423,7 @@ openDuePayments(); function iterateUnbilledServices(ubs) { - vm.totalPendingPayments += parseFloat(ubs.srvc_cost); + vm.totalPendingPayments += (ubs.srvc_payreceived) ? (parseFloat(ubs.srvc_cost) - parseFloat(ubs.srvc_payreceived)) : parseFloat(ubs.srvc_cost); } } @@ -465,6 +465,8 @@ ++vm.totalServicesDone; if (service.srvc_status == 'Paid') vm.totalRevenueEarned += parseFloat(service.srvc_cost); + else + vm.totalRevenueEarned += (service.srvc_payreceived ? parseFloat(service.srvc_payreceived) : 0); var d = moment(service.srvc_date).format('DD MMM YYYY'); if (!spd[d]) { spd[d] = 0; diff --git a/src/app/components/dashboard/tmpl/dashboard_due-payments.tmpl.html b/src/app/components/dashboard/tmpl/dashboard_due-payments.tmpl.html index b3c82b88..35b1da33 100644 --- a/src/app/components/dashboard/tmpl/dashboard_due-payments.tmpl.html +++ b/src/app/components/dashboard/tmpl/dashboard_due-payments.tmpl.html @@ -18,7 +18,7 @@ Mobile Vehicle Date - Cost + Payment Due @@ -27,7 +27,7 @@ {{service.cstmr_mobile}} {{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}) {{vm.getServiceDate(service.srvc_date)}} - {{service.srvc_cost}} + {{vm.getCost(service)}} diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index c58f5c38..25f7938c 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -194,6 +194,10 @@ vm.getDate = getDate; vm.IsProblemFocusIndex = IsProblemFocusIndex; vm.IsInventoryFocusIndex = IsInventoryFocusIndex; + vm.openPartialPaymentBox = openPartialPaymentBox; + vm.calculateDue = calculateDue; + vm.IsPartialPayment = IsPartialPayment; + vm.changeServiceStatus = changeServiceStatus; // default execution steps setCoverPic(); @@ -217,6 +221,49 @@ // function definitions + function changeServiceStatus() { + if (vm.servicestatus) { + if (vm.service.partialpayment) { + if ((vm.service.partialpayment.total - vm.service.cost) != 0) + vm.service.partialpayment[moment().format()] = vm.service.cost - vm.service.partialpayment.total; + } + } else + openPartialPaymentBox(); + } + + function IsPartialPayment() { + return (vm.service.partialpayment && !vm.servicestatus); + } + + function calculateDue() { + return (vm.service.cost - vm.service.partialpayment.total); + } + + function openPartialPaymentBox() { + $mdDialog.show({ + controller: 'amCtrlSePp', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl/dialog_partialpayment.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + totalCost: vm.service.cost, + partialPayments: vm.service.partialpayment + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (!res) { + vm.servicestatus = vm.service.partialpayment ? ((vm.service.partialpayment.total - vm.service.cost) == 0) : true; + return; + } + if (res.total) + vm.servicestatus = ((parseFloat(res.total) - parseFloat(vm.service.cost)) == 0); + vm.service.partialpayment = res; + } + } + function IsInventoryFocusIndex(index) { return (vm.inventoryFocusIndex == index); } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index de9501eb..e9f4e4f3 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -190,6 +190,10 @@ vm.getDate = getDate; vm.IsProblemFocusIndex = IsProblemFocusIndex; vm.IsInventoryFocusIndex = IsInventoryFocusIndex; + vm.openPartialPaymentBox = openPartialPaymentBox; + vm.calculateDue = calculateDue; + vm.IsPartialPayment = IsPartialPayment; + vm.changeServiceStatus = changeServiceStatus; // default execution steps setCoverPic(); @@ -204,6 +208,49 @@ // function definitions + function changeServiceStatus() { + if (vm.servicestatus) { + if (vm.service.partialpayment) { + if ((vm.service.partialpayment.total - vm.service.cost) != 0) + vm.service.partialpayment[moment().format()] = vm.service.cost - vm.service.partialpayment.total; + } + } else + openPartialPaymentBox(); + } + + function IsPartialPayment() { + return (vm.service.partialpayment && !vm.servicestatus); + } + + function calculateDue() { + return (vm.service.cost - vm.service.partialpayment.total); + } + + function openPartialPaymentBox() { + $mdDialog.show({ + controller: 'amCtrlSePp', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl/dialog_partialpayment.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + totalCost: vm.service.cost, + partialPayments: vm.service.partialpayment + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (!res) { + vm.servicestatus = vm.service.partialpayment ? ((vm.service.partialpayment.total - vm.service.cost) == 0) : true; + return; + } + if (res.total) + vm.servicestatus = ((parseFloat(res.total) - parseFloat(vm.service.cost)) == 0); + vm.service.partialpayment = res; + } + } + function IsInventoryFocusIndex(index) { return (vm.inventoryFocusIndex == index); } @@ -1240,6 +1287,8 @@ vm.service.cost = res.vehicle.service.cost; vm.service.odo = res.vehicle.service.odo; vm.service.status = res.vehicle.service.status; + if (res.vehicle.service.partialpayment) + vm.service.partialpayment = res.vehicle.service.partialpayment; vm.service.state = (res.vehicle.service.state == undefined) ? vm.serviceStateList[2] : res.vehicle.service.state; vm.label_invoice = (vm.service.state == vm.serviceStateList[2]) ? 'Invoice' : 'Send'; if (res.vehicle.service.discount) { diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 63b3f959..2d2125ad 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -1,4 +1,18 @@ arrow_back @@ -193,7 +221,7 @@
- Next Service Reminder: + Next Service Reminder:
{{vm.getDate(vm.nextDueDate)}} @@ -702,7 +730,13 @@ {{vm.getDate(vm.service.date)}}
- Payment + + Payment + + + history + Manage Payments + Service Tax  % @@ -719,19 +753,25 @@
- Subtotal: Rs. {{vm.calculateSubtotal()}} + Subtotal: Rs. {{vm.calculateSubtotal()}}   - Service Tax: Rs. {{vm.calculateTax()}} + Service Tax: Rs. {{vm.calculateTax()}}   - VAT: Rs. {{vm.calculateVat()}} + VAT: Rs. {{vm.calculateVat()}}   - Discount: Rs. + Discount: Rs.   - Round Off: Rs. + Round Off: Rs.   - Total Cost: Rs. {{vm.service.cost}} + Total Cost: Rs. {{vm.service.cost}}
+
+   + Received: Rs. {{vm.service.partialpayment.total}} +   + Due: Rs. {{vm.calculateDue()}} +
done diff --git a/src/app/components/services/tmpl/dialog_partialpayment.controller.js b/src/app/components/services/tmpl/dialog_partialpayment.controller.js new file mode 100644 index 00000000..589d8c26 --- /dev/null +++ b/src/app/components/services/tmpl/dialog_partialpayment.controller.js @@ -0,0 +1,146 @@ +/** + * Controller for Partial Payment Dialogbox + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlSePp', PartialPaymentController); + + PartialPaymentController.$inject = ['$mdDialog', '$filter', 'totalCost', 'partialPayments']; + + function PartialPaymentController($mdDialog, $filter, totalCost, partialPayments) { + // initialize view model + var vm = this; + + // named assignments for view model + vm.partialPayments = []; + vm.tc = totalCost; + vm.paymentFocusIndex = -1; + + // function mappings to view model + vm.getDate = getDate; + vm.addPayment = addPayment; + vm.save = save; + vm.cancel = cancel; + vm.calculateRemaining = calculateRemaining; + vm.calculateReceived = calculateReceived; + vm.IsPaymentFocusIndex = IsPaymentFocusIndex; + + // default execution steps + setTimeout(setViewportHeight, 400); + feedExistingPayments(); + + $(window).on('resize', OnWindowResize); + + // function definitions + + function feedExistingPayments() { + var keys = (partialPayments) ? Object.keys(partialPayments) : undefined; + if (partialPayments && keys && (keys.length > 1)) { + keys.forEach(iteratePp); + calculateRemaining(); + } else + addPayment(); + + function iteratePp(pp) { + if (pp == "total") + return; + vm.partialPayments.push({ + amount: partialPayments[pp], + date: new Date(pp), + adjust: false + }); + } + } + + function IsPaymentFocusIndex(index) { + return (vm.paymentFocusIndex == index); + } + + function calculateTotalReceived() { + var temptr = 0; + vm.partialPayments.forEach(iteratePp); + return temptr; + + function iteratePp(pp) { + if (!pp.amount || pp.amount == '' || (pp.adjust == true)) + return; + temptr += parseFloat(pp.amount); + } + } + + function calculateReceived() { + var x = totalCost - (vm.tc + calculateTotalReceived()); + var found = $filter('filter')(vm.partialPayments, { + adjust: true + }, true); + + if (found.length > 0) + found[0].amount = x; + else { + vm.partialPayments.push({ + amount: x, + date: new Date(), + adjust: true + }); + } + } + + function adjustPaymentCalc() { + vm.partialPayments.forEach(iteratePp); + + function iteratePp(pp) { + pp.adjust = (!pp.amount || pp.amount == ''); + } + } + + function calculateRemaining(payment) { + adjustPaymentCalc(); + vm.tc = totalCost - calculateTotalReceived(); + } + + function OnWindowResize() { + setViewportHeight(); + } + + function setViewportHeight() { + $('#am-dpp').css('max-height', $(window).height() - ($('#am-dpp').offset().top + 15)); + } + + function addPayment() { + vm.paymentFocusIndex = -1; + vm.partialPayments.push({ + amount: '', + date: new Date(), + adjust: true + }); + vm.paymentFocusIndex = vm.partialPayments.length - 1; + } + + function getDate(date) { + return moment(date).format('DD MMM YYYY'); + } + + function save() { + var temp = {}, total = 0; + vm.partialPayments.forEach(iteratePp); + temp.total = parseFloat(total); + $mdDialog.hide(temp); + + function iteratePp(pp) { + if (pp.amount == '') + return; + temp[moment(pp.date).format()] = pp.amount; + total += pp.amount; + } + } + + function cancel() { + $mdDialog.cancel(); + } + } +})(); \ No newline at end of file diff --git a/src/app/components/services/tmpl/dialog_partialpayment.html b/src/app/components/services/tmpl/dialog_partialpayment.html new file mode 100644 index 00000000..e400d8c8 --- /dev/null +++ b/src/app/components/services/tmpl/dialog_partialpayment.html @@ -0,0 +1,81 @@ + + + +
+
+ {{$index+1}}. Received:   + +  Rs. +
+
+ + {{vm.getDate(payment.date)}} +
+
+
+ + add + Add More + +
+
+ Remaining:   + +  Rs. +
+
+ + done + Done + + + close + Cancel + +
+
+
\ No newline at end of file From 2045250d80919b84c56ca42009aba0977dd459e4 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 25 Jul 2016 11:08:39 +0530 Subject: [PATCH 11/91] Enhancement #146 | Initialization Bug Fix > It's not proper [image] --- src/app/components/customers/customers-edit.controller.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 004dbb22..24441ca8 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -57,7 +57,6 @@ // function maps vm.changeVehicle = changeVehicle; - vm.isAddOperation = isAddOperation; vm.save = save; vm.queryMembershipChip = queryMembershipChip; vm.OnClickMembershipChip = OnClickMembershipChip; @@ -499,10 +498,6 @@ vm.user.address = res.user.address; if (res.user.type) vm.user.type = res.user.type; - changeUserMobileLabel(); - changeUserEmailLabel(); - changeUserNameLabel(); - changeUserAddressLabel(); if (res.user.memberships) Object.keys(res.user.memberships).forEach(iterateMemberships); if (res.user.vehicles) @@ -540,6 +535,7 @@ } function failure(err) { + console.log(err); utils.showSimpleToast('Something went wrong!'); $state.go('restricted.customers.all'); } @@ -604,7 +600,6 @@ vm.vehicle.reg = found[0].reg; vm.vehicle.manuf = found[0].manuf; vm.vehicle.model = found[0].model; - changeVehicleRegLabel(); autofillVehicle = true; } else setDefaultVehicle(); From 158ba63514f4672defa7f47c98285f3828b44890 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 25 Jul 2016 11:52:55 +0530 Subject: [PATCH 12/91] Enhancement #143 | Custom AppData Location --- src/app/components/settings/settings.controller.js | 9 ++++++++- src/main.js | 11 ++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index d66e279b..b3e0309e 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -159,7 +159,14 @@ } function success(res) { - fse.removeSync(amApp.getPath('userData')); + if (res) + console.error(res); + fse.remove(amApp.getPath('userData'), removeSuccess); + } + + function removeSuccess(err) { + if (err) + console.error(err); ipcRenderer.send('am-do-restart', true); } // /Users/ndkcha/Library/Application Support/Automint diff --git a/src/main.js b/src/main.js index afb57bda..ebde7581 100644 --- a/src/main.js +++ b/src/main.js @@ -47,6 +47,7 @@ let mainWindow; var shouldQuit = app.makeSingleInstance(msiCallback); + console.log('shouldQuit: ' + shouldQuit); if (shouldQuit) { app.quit(); @@ -92,8 +93,10 @@ ipcMain.on('am-do-restart', restartApp); function restartApp(event, args) { - app.relaunch(); - app.exit(0); + app.relaunch({ + args: process.argv.slice(1) + ['--relaunch'] + }); + app.quit(); } function updateAndRestartApp(event, args) { @@ -120,9 +123,7 @@ function openCorrectFileUrl() { var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); ammPreferences.storePreference('automint.userDataPath', newPath[0]); - var exec = require('child_process').exec; - exec(process.argv.join(' ')); - app.quit(); + restartApp(); } } From 22241e28d980523936260ed60dd8aaa4b4f7c3f3 Mon Sep 17 00:00:00 2001 From: Viral Kacha Date: Mon, 25 Jul 2016 12:38:18 +0530 Subject: [PATCH 13/91] Enhancement #143 | Fix: Restart App --- src/main.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main.js b/src/main.js index ebde7581..d39aa00b 100644 --- a/src/main.js +++ b/src/main.js @@ -47,7 +47,6 @@ let mainWindow; var shouldQuit = app.makeSingleInstance(msiCallback); - console.log('shouldQuit: ' + shouldQuit); if (shouldQuit) { app.quit(); @@ -93,9 +92,7 @@ ipcMain.on('am-do-restart', restartApp); function restartApp(event, args) { - app.relaunch({ - args: process.argv.slice(1) + ['--relaunch'] - }); + app.relaunch({args: process.argv.slice(1).concat('--relaunch')}); app.quit(); } From d68715b36fea86fe4c74a05b8bfb69dfa2218344 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 25 Jul 2016 18:18:16 +0530 Subject: [PATCH 14/91] Enhancement #146 | Add Customer > Also remove wizard while adding customer from here. --- src/app/app.states.js | 4 +- .../customers/customers-add.controller.js | 292 ++++++++---------- .../components/customers/customers.factory.js | 15 +- .../components/customers/customers_add.html | 287 ++++++++++------- src/assets/js/angular-elastic-input.min.js | 11 + 5 files changed, 326 insertions(+), 283 deletions(-) create mode 100755 src/assets/js/angular-elastic-input.min.js diff --git a/src/app/app.states.js b/src/app/app.states.js index 9308a495..53b1fe21 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -432,7 +432,9 @@ function loadCuCIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', - 'app/components/customers/customers-add.controller.js' + 'assets/js/angular-elastic-input.min.js', + 'app/components/customers/customers-add.controller.js', + 'app/components/customers/tmpl/vehicle-crud.controller.js' ]) } function loadCuUIDeps($ocLazyLoad) { diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index 1bcfd9f7..4cb8b137 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -18,15 +18,10 @@ function CustomerAddController($state, $filter, $q, $log, $mdDialog, utils, amCustomers) { // initialize view model var vm = this; + var nextDueDate = new Date(); + nextDueDate.setMonth(nextDueDate.getMonth() + 3); // vm assignments to keep track of UI related elements - vm.label_userName = 'Enter Full Name:'; - vm.label_userMobile = 'Enter Mobile Number:'; - vm.label_userEmail = 'Enter Email:'; - vm.label_userAddress = 'Enter Address:'; - vm.label_vehicleReg = 'Enter Vehicle Registration Number:'; - vm.label_vehicleManuf = 'Manufacturer:'; - vm.label_vehicleModel = 'Model:'; vm.user = { mobile: '', name: '', @@ -44,34 +39,23 @@ vm.membershipChips = []; vm.vehicleTypeList = []; vm.loadingBasedOnMobile = false; - vm.isNextDueService = false; - vm.nextDueDate = new Date(); - vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); + vm.customerTypeList = ['Customer', 'Agency']; + vm.possibleVehicleList = []; + vm.vNextDue = {}; // function maps vm.convertNameToTitleCase = convertNameToTitleCase; - vm.convertRegToCaps = convertRegToCaps; - vm.searchVehicleChange = searchVehicleChange; - vm.manufacturersQuerySearch = manufacturersQuerySearch; - vm.modelQuerySearch = modelQuerySearch; - vm.changeUserNameLabel = changeUserNameLabel; - vm.changeUserMobileLabel = changeUserMobileLabel; - vm.changeUserEmailLabel = changeUserEmailLabel; - vm.changeUserAddressLabel = changeUserAddressLabel; - vm.changeVehicleRegLabel = changeVehicleRegLabel; - vm.changeVehicleTab = changeVehicleTab; - vm.changeUserTab = changeUserTab; vm.save = save; vm.queryMembershipChip = queryMembershipChip; vm.OnClickMembershipChip = OnClickMembershipChip; vm.OnAddMembershipChip = OnAddMembershipChip; - vm.changeMembershipTab = changeMembershipTab; vm.goBack = goBack; vm.autoCapitalizeCustomerAddress = autoCapitalizeCustomerAddress; - vm.autoCapitalizeVehicleModel = autoCapitalizeVehicleModel; vm.checkExistingCustomerMobile = checkExistingCustomerMobile; vm.unsubscribeMembership = unsubscribeMembership; vm.getDate = getDate; + vm.editVehicle = editVehicle; + vm.blurAddVehicle = blurAddVehicle; // default execution steps setTimeout(focusCustomerName, 300); @@ -81,6 +65,120 @@ // function definitions + function blurAddVehicle(event) { + if (event.keyCode == 9) + setTimeout(focusToSave, 100); + } + + function focusToSave() { + $('#amb-save').focus(); + } + + function changeVehicle(id) { + if (!id) { + setDefaultVehicle(); + return; + } + var found = $filter('filter')(vm.possibleVehicleList, { + id: id + }, true); + if (found.length > 0) { + vm.currentVehicleId = found[0].id; + vm.vehicle.id = found[0].id; + vm.vehicle.reg = found[0].reg; + vm.vehicle.manuf = found[0].manuf; + vm.vehicle.model = found[0].model; + autofillVehicle = true; + } else + setDefaultVehicle(); + } + + function setDefaultVehicle() { + vm.currentVehicleId = undefined; + vm.vehicle.id = undefined; + vm.vehicle.reg = ''; + vm.vehicle.manuf = ''; + vm.vehicle.model = ''; + autofillVehicle = false; + } + + function editVehicle(id) { + changeVehicle(id); + $mdDialog.show({ + controller: 'amCtrlCuVeCRUD', + controllerAs: 'vm', + templateUrl: 'app/components/customers/tmpl/vehicle-crud.tmpl.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + vehicle: vm.vehicle + }, + clickOutsideToClose: true + }).then(closeVehicleDialog).catch(closeVehicleDialog); + + function closeVehicleDialog(res) { + if (!res) + return; + var vId = res.id; + if (!vm.vehicles) + vm.vehicles = {}; + + var prefixVehicle = 'vhcl' + ((vm.vehicle.manuf && vm.vehicle.model) ? '-' + angular.lowercase(vm.vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vm.vehicle.model).replace(' ', '-') : ''); + + if (vId == undefined) + vId = utils.generateUUID(prefixVehicle); + + if (vm.vehicles[vId]) { + var tv = vm.vehicles[vId]; + if ((tv.manuf != res.manuf) || (tv.model != res.model)) { + vId = utils.generateUUID(prefixVehicle); + vm.vehicles[vId] = tv; + vm.vNextDue[vId] = vm.vNextDue[res.id]; + delete vm.vehicles[res.id]; + delete vm.vNextDue[res.id]; + } + } else + vm.vehicles[vId] = {}; + var intermvehicle = vm.vehicles[vId]; + intermvehicle.reg = res.reg; + intermvehicle.manuf = res.manuf; + intermvehicle.model = res.model; + + var longname = (intermvehicle.manuf ? intermvehicle.manuf + ' ' : '') + (intermvehicle.model ? intermvehicle.model + ' ' : '') + (intermvehicle.reg ? ((intermvehicle.manuf || intermvehicle.model) ? ' - ' : '') + intermvehicle.reg : ''); + var shortname = (longname.length <= 45) ? longname : longname.substr(0, 45) + '...'; + var isLongName = (longname.length > 45); + + var vfound = $filter('filter')(vm.possibleVehicleList, { + id: res.id + }, true); + + if (vfound.length == 1) { + vfound[0].id = vId + vfound[0].reg = res.reg; + vfound[0].manuf = res.manuf; + vfound[0].model = res.model; + vfound[0].name = longname; + vfound[0].shortname = shortname; + vfound[0].isLongName = isLongName; + } else { + vm.possibleVehicleList.push({ + id: vId, + reg: res.reg, + manuf: res.manuf, + model: res.model, + name: longname, + shortname: shortname, + isLongName: isLongName + }); + vm.vNextDue[vId] = { + isNextDue: false, + nextdue: nextDueDate + } + } + changeVehicle(vId); + } + } + function getDate(date) { return moment(date).format('DD MMM YYYY'); } @@ -137,18 +235,9 @@ function failure(err) { vm.loadingBasedOnMobile = false; - console.info('New Customer'); } } - function autoCapitalizeVehicleModel() { - vm.vehicle.model = utils.autoCapitalizeWord(vm.vehicle.model); - } - - function autoCapitalizeVehicleManuf() { - vm.vehicle.manuf = utils.autoCapitalizeWord(vm.vehicle.manuf); - } - function autoCapitalizeCustomerAddress() { vm.user.address = utils.autoCapitalizeWord(vm.user.address); } @@ -157,10 +246,6 @@ $state.go('restricted.customers.all'); } - function changeMembershipTab(bool) { - vm.membershipTab = bool; - } - function OnClickMembershipChip(event) { var chipIndex = angular.element(event.currentTarget).controller('mdChips').selectedChip; if (chipIndex < 0) @@ -308,124 +393,11 @@ function convertNameToTitleCase() { vm.user.name = utils.convertToTitleCase(vm.user.name); } - - function convertRegToCaps() { - vm.vehicle.reg = vm.vehicle.reg.toUpperCase(); - } - - // query search for manufacturers [autocomplete] - function manufacturersQuerySearch() { - var tracker = $q.defer(); - var results = (vm.vehicle.manuf ? vm.manufacturers.filter(createFilterForManufacturers(vm.vehicle.manuf)) : vm.manufacturers); - - if (results.length > 0) { - return results; - } - - amCustomers.getManufacturers().then(allotManufacturers).catch(noManufacturers); - return tracker.promise; - - function allotManufacturers(res) { - vm.manufacturers = res; - results = (vm.vehicle.manuf ? vm.manufacturers.filter(createFilterForManufacturers(vm.vehicle.manuf)) : vm.manufacturers); - tracker.resolve(results); - } - - function noManufacturers(error) { - results = []; - tracker.resolve(results); - } - } - - // create filter for manufacturers' query list - function createFilterForManufacturers(query) { - var lcQuery = angular.lowercase(query); - return function filterFn(item) { - item = angular.lowercase(item); - return (item.indexOf(lcQuery) === 0); - } - } - - // query search for model [autocomplete] - function modelQuerySearch() { - var tracker = $q.defer(); - var results = (vm.vehicle.model ? vm.models.filter(createFilterForModel(vm.vehicle.model)) : vm.models); - - if (results.length > 0) - return results; - - amCustomers.getModels(vm.vehicle.manuf).then(allotModels).catch(noModels); - return tracker.promise; - - function allotModels(res) { - vm.models = res; - results = (vm.vehicle.model ? vm.models.filter(createFilterForModel(vm.vehicle.model)) : vm.models); - tracker.resolve(results); - } - - function noModels(err) { - results = []; - tracker.resolve(results); - } - } - - // create filter for models' query list - function createFilterForModel(query) { - var lcQuery = angular.lowercase(query); - return function filterFn(item) { - item = angular.lowercase(item); - return (item.indexOf(lcQuery) === 0); - } - } - - function searchVehicleChange() { - autoCapitalizeVehicleManuf(); - vm.models = []; - vm.vehicle.model = ''; - } - - // change user tab selector variable - function changeUserTab(bool) { - vm.userTab = bool; - } - - // change vehicle tab selector variable - function changeVehicleTab(bool) { - vm.vehicleTab = bool; - } - - // listen to changes in input fields [BEGIN] - function changeUserNameLabel(force) { - vm.isUserName = (force != undefined || vm.user.name != ''); - vm.label_userName = vm.isUserName ? 'Name:' : 'Enter Full Name:'; - } - function changeUserMobileLabel(force) { - vm.isUserMobile = (force != undefined || vm.user.mobile != ''); - vm.label_userMobile = vm.isUserMobile ? 'Mobile:' : 'Enter Mobile Number:'; - } - function changeUserEmailLabel(force) { - vm.isUserEmail = (force != undefined || vm.user.email != ''); - vm.label_userEmail = vm.isUserEmail ? 'Email:' : 'Enter Email:'; - } - function changeUserAddressLabel(force) { - vm.isUserAddress = (force != undefined || vm.user.address != ''); - vm.label_userAddress = vm.isUserAddress ? 'Address:' : 'Enter Address:'; - } - function changeVehicleRegLabel(force) { - vm.isVehicleReg = (force != undefined || vm.vehicle.reg != ''); - vm.label_vehicleReg = vm.isVehicleReg ? 'Vehcile Registration Number:' : 'Enter Vehicle Registration Number:'; - } - // listen to changes in input fields [END] function validate() { if (vm.user.name == '') { - changeUserTab(true); - setTimeout(doFocus, 300); + setTimeout(focusCustomerName, 300); utils.showSimpleToast('Please Enter Name'); - - function doFocus() { - $('#ami-user-name').focus(); - } return false; } return true; @@ -434,14 +406,20 @@ // save to database function save() { if (!validate()) return; - vm.user.memberships = vm.membershipChips; - if (!(vm.vehicle.reg == '' && vm.vehicle.manuf == '' && vm.vehicle.model == '')) { - vm.vehicle.reg = vm.vehicle.reg.replace(/\s/g, ''); - if (vm.isNextDueService) - vm.vehicle.nextdue = vm.nextDueDate; - amCustomers.addNewCustomer(vm.user, vm.vehicle).then(successfullSave).catch(failedSave); - } else + var ndKeys = Object.keys(vm.vNextDue); + if (ndKeys.length > 0) + ndKeys.forEach(iterateNextDue); + if (vm.membershipChips.length > 0) + vm.user.memberships = vm.membershipChips; + if (vm.vehicles && Object.keys(vm.vehicles.length > 0)) + amCustomers.addNewCustomer(vm.user, vm.vehicles).then(successfullSave).catch(failedSave); + else amCustomers.addNewCustomer(vm.user).then(successfullSave).catch(failedSave); + + function iterateNextDue(nd) { + if (vm.vNextDue[nd].isNextDue) + vm.vehicles[nd].nextdue = moment(vm.vNextDue[nd].nextdue).format(); + } } function successfullSave(res) { diff --git a/src/app/components/customers/customers.factory.js b/src/app/components/customers/customers.factory.js index 01873495..b1dd03c8 100644 --- a/src/app/components/customers/customers.factory.js +++ b/src/app/components/customers/customers.factory.js @@ -2,7 +2,7 @@ * Factory that handles database interactions between customer database and controller * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -414,16 +414,11 @@ } // add new customer - function addNewCustomer(customer, vehicle) { - var prefixUser = 'usr-' + angular.lowercase(customer.name).replace(' ', '-'), - prefixVehicle; + function addNewCustomer(customer, vehicles) { + var prefixUser = 'usr-' + angular.lowercase(customer.name).replace(' ', '-'); - if (vehicle) { - prefixVehicle = 'vhcl' + ((vehicle.manuf && vehicle.model) ? '-' + angular.lowercase(vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vehicle.model).replace(' ', '-') : ''); - customer.vehicles = {}; - delete vehicle.id; - customer.vehicles[utils.generateUUID(prefixVehicle)] = vehicle; - } + if (vehicles) + customer.vehicles = vehicles; if (customer.memberships != undefined) { var smArray = $.extend([], customer.memberships); diff --git a/src/app/components/customers/customers_add.html b/src/app/components/customers/customers_add.html index 719138ad..fc52a711 100644 --- a/src/app/components/customers/customers_add.html +++ b/src/app/components/customers/customers_add.html @@ -1,4 +1,48 @@ arrow_back - - - - -
-
- - person_pin - - - + + + + done + Save + Save Customer + + + + + + +
+
+
+ +
-
- - phone - - - - - email - - - -
-
- - home - - - +
+ +
-
-
- - Please wait -
- - done - Save - - - Add Vehicle - navigate_next - +
+ +
- - - - -
-
-
- Choose Manufacturer: - - - {{manuf}} - - -
-
- Choose Model: - - - {{model}} - - -
-
-
- - - -
e.g. GJ01AB9999
-
-
- Next Service Reminder: -
- - {{vm.getDate(vm.nextDueDate)}} -
-
-
+
+ + + {{type}} +
-
- - done - Save - - - Add Memberships - navigate_next - +
+ + + + {{membership.name}} + + + + {{$chip.name}} + + + +
* Click on Membership for details
- - - - -
-
- card_membership -
- - - {{membership.name}} - - - - {{$chip.name}} - - - -
+
+ +
+
+ Vehicles: +
+
+ {{vehicle.shortname}}{{vehicle.name}} +
+ edit + Edit
-
* Click on Membership for details
-
- - done - Save +
+
+
+ Next Service Reminder: +
+ + {{vm.getDate(vm.vNextDue[vm.vehicle.id].nextdue)}} +
+
+
+ + add + Add Vehicle
- - - +
+ + \ No newline at end of file diff --git a/src/assets/js/angular-elastic-input.min.js b/src/assets/js/angular-elastic-input.min.js new file mode 100755 index 00000000..31dba480 --- /dev/null +++ b/src/assets/js/angular-elastic-input.min.js @@ -0,0 +1,11 @@ +/** + * angular-elastic-input + * A directive for AngularJS which automatically resizes the width of input field according to the content, while typing. + * @version: 2.4.0 + * @author: Jacek Pulit + * @license: MIT + * @build: Thursday, July 7th, 2016, 11:27:27 PM GMT+0200 + */ +(function(){ +"use strict";angular.module("puElasticInput",[]).directive("puElasticInput",["$document","$window",function(a,b){function c(a,b){var c="";if(window.getComputedStyle)c=getComputedStyle(a).getPropertyValue(b);else if(a.currentStyle)try{c=a.currentStyle[b]}catch(d){}return c}function d(a){var b,d=a[0];do d=d.parentNode,b=parseInt(c(d,"width"),10)-parseInt(c(d,"padding-left"),10)-parseInt(c(d,"padding-right"),10);while("block"!=c(d,"display")&&"body"!=d.nodeName.toLowerCase());return b+"px"}function e(a,c,e){var f=b.getComputedStyle(c[0]),g="none"===f.maxWidth?d(c):f.maxWidth;c.css("minWidth",e.puElasticInputMinwidth||f.minWidth),c.css("maxWidth",e.puElasticInputMaxwidth||g),angular.forEach(["fontFamily","fontSize","fontWeight","fontStyle","letterSpacing","textTransform","wordSpacing"],function(b){a.css(b,f[b])}),a.css("paddingLeft",f.textIndent),"border-box"===f.boxSizing?angular.forEach(["paddingLeft","paddingRight","borderLeftStyle","borderLeftWidth","borderRightStyle","borderRightWidth"],function(b){a.css(b,f[b])}):"padding-box"===f.boxSizing&&angular.forEach(["paddingLeft","paddingRight"],function(b){a.css(b,f[b])})}var f=angular.element('
');return angular.element(a[0].body).append(f),{restrict:"A",link:function(a,b,c){function d(){var a=b.val()||c.placeholder||"";if(g.text()!=a){g.text(a);var d=parseInt(c.puElasticInputWidthDelta)||1;b.css("width",g.prop("offsetWidth")+d+"px")}}c.$set("ngTrim","true"===c.ngTrim?"true":"false");var g=angular.element('');e(g,b,c),f.append(g),d(),a.$watch(c.ngModel,d),b.on("keydown keyup focus input propertychange change",d),a.$on("$destroy",function(){g.remove()})}}}]); +})(); \ No newline at end of file From 5e2eafd155620fec10cb9487f953f9c2f0c14d28 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 26 Jul 2016 11:32:56 +0530 Subject: [PATCH 15/91] Enhancement #146 & Bug Fix > 146. Fix: Add multiple vehicles > Add Service, populate user details when app has no default entries. --- .../customers/customers-add.controller.js | 33 ++++++++++++------- .../customers/customers-edit.controller.js | 2 ++ .../components/customers/customers_add.html | 10 ++++-- .../services/services-add.controller.js | 2 ++ 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index 4cb8b137..9c607f1a 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -18,8 +18,11 @@ function CustomerAddController($state, $filter, $q, $log, $mdDialog, utils, amCustomers) { // initialize view model var vm = this; + + // temporary named assignments var nextDueDate = new Date(); nextDueDate.setMonth(nextDueDate.getMonth() + 3); + var vehicles; // vm assignments to keep track of UI related elements vm.user = { @@ -56,6 +59,8 @@ vm.getDate = getDate; vm.editVehicle = editVehicle; vm.blurAddVehicle = blurAddVehicle; + vm.IsVehicleSelected = IsVehicleSelected; + vm.changeVehicle = changeVehicle; // default execution steps setTimeout(focusCustomerName, 300); @@ -65,6 +70,10 @@ // function definitions + function IsVehicleSelected(id) { + return (vm.currentVehicleId == id); + } + function blurAddVehicle(event) { if (event.keyCode == 9) setTimeout(focusToSave, 100); @@ -120,26 +129,28 @@ if (!res) return; var vId = res.id; - if (!vm.vehicles) - vm.vehicles = {}; + if (res.id == undefined) + res.id = ''; + if (!vehicles) + vehicles = {}; var prefixVehicle = 'vhcl' + ((vm.vehicle.manuf && vm.vehicle.model) ? '-' + angular.lowercase(vm.vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vm.vehicle.model).replace(' ', '-') : ''); if (vId == undefined) vId = utils.generateUUID(prefixVehicle); - if (vm.vehicles[vId]) { - var tv = vm.vehicles[vId]; + if (vehicles[vId]) { + var tv = vehicles[vId]; if ((tv.manuf != res.manuf) || (tv.model != res.model)) { vId = utils.generateUUID(prefixVehicle); - vm.vehicles[vId] = tv; + vehicles[vId] = tv; vm.vNextDue[vId] = vm.vNextDue[res.id]; - delete vm.vehicles[res.id]; + delete vehicles[res.id]; delete vm.vNextDue[res.id]; } } else - vm.vehicles[vId] = {}; - var intermvehicle = vm.vehicles[vId]; + vehicles[vId] = {}; + var intermvehicle = vehicles[vId]; intermvehicle.reg = res.reg; intermvehicle.manuf = res.manuf; intermvehicle.model = res.model; @@ -411,14 +422,14 @@ ndKeys.forEach(iterateNextDue); if (vm.membershipChips.length > 0) vm.user.memberships = vm.membershipChips; - if (vm.vehicles && Object.keys(vm.vehicles.length > 0)) - amCustomers.addNewCustomer(vm.user, vm.vehicles).then(successfullSave).catch(failedSave); + if (vehicles && Object.keys(vehicles.length > 0)) + amCustomers.addNewCustomer(vm.user, vehicles).then(successfullSave).catch(failedSave); else amCustomers.addNewCustomer(vm.user).then(successfullSave).catch(failedSave); function iterateNextDue(nd) { if (vm.vNextDue[nd].isNextDue) - vm.vehicles[nd].nextdue = moment(vm.vNextDue[nd].nextdue).format(); + vehicles[nd].nextdue = moment(vm.vNextDue[nd].nextdue).format(); } } diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 24441ca8..50a2693f 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -106,6 +106,8 @@ if (!res) return; var vId = res.id; + if (res.id == undefined) + res.id = ''; if (!userDbInstance.user.vehicles) userDbInstance.user.vehicles = {}; diff --git a/src/app/components/customers/customers_add.html b/src/app/components/customers/customers_add.html index fc52a711..29f1d57b 100644 --- a/src/app/components/customers/customers_add.html +++ b/src/app/components/customers/customers_add.html @@ -68,6 +68,7 @@ .am-vehicle-selector .am-button { padding: 8px 14px; + outline: none; } .am-vehicle-selector .am-button span { @@ -81,6 +82,11 @@ margin-right: 6px; } + .am-vehicle-selector.selected { + color: #fff; + background: #1565C0; + } + .am-vehicle-selector:hover { color: #fff; background: #1565C0; @@ -171,9 +177,9 @@
Vehicles:
-
+
{{vehicle.shortname}}{{vehicle.name}} -
+
edit Edit
diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 25f7938c..b8a318c6 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -1495,6 +1495,8 @@ // replace all the treatment values with updated vehicle type function changeVehicleType() { + if (vm.vehicleTypeList.length < 1) + return; vm.service.problems.forEach(iterateProblem); iterateProblem(vm.problem); calculatePackageTax(); From 271c2af36497de07f5e85f4ce336b22d67837c23 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 26 Jul 2016 11:37:45 +0530 Subject: [PATCH 16/91] Enhancement #143 | Fix: Handled undefined vars --- src/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index d39aa00b..beb78fd1 100644 --- a/src/main.js +++ b/src/main.js @@ -101,7 +101,8 @@ } function OnAutomintUpdated(event, releaseNotes, releaseName, releaseDate, updateURL) { - mainWindow.webContents.send('automint-updated', true); + if (mainWindow && mainWindow.webContents) + mainWindow.webContents.send('automint-updated', true); } function OnAppReady() { @@ -119,7 +120,8 @@ function openCorrectFileUrl() { var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); - ammPreferences.storePreference('automint.userDataPath', newPath[0]); + if (newPath) + ammPreferences.storePreference('automint.userDataPath', newPath[0]); restartApp(); } } From 8f1dcf55c077929a51d9bb20848813738ae48edc Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 26 Jul 2016 15:42:55 +0530 Subject: [PATCH 17/91] Enhancement #143 | Bug Fix > need to manage the case of removing the pendrive, where the path is store. --- src/main.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main.js b/src/main.js index beb78fd1..fdafb962 100644 --- a/src/main.js +++ b/src/main.js @@ -139,6 +139,13 @@ // mainWindow.webContents.openDevTools(); + fs.watchFile(app.getPath('userData'), (curr, prev) => { + if (curr.ino == 0) { + fs.unwatchFile(app.getPath('userData')); + restartApp(); + } + }); + // Emitted when the window is closed. mainWindow.on('closed', function() { // Dereference the window object, usually you would store windows From 5a121bec4c97caf685ee42c11f3cc73fcb4dec6b Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 2 Aug 2016 20:07:06 +0530 Subject: [PATCH 18/91] Enhancement #181 | Initial Design > Accessible from Add Service > Does not affect values in Add Service yet --- src/app/app.states.js | 2 + .../services/services-add.controller.js | 28 +++++++- src/app/components/services/services_add.html | 11 ++- .../tmpl/dialog_discount.controller.js | 72 +++++++++++++++++++ .../services/tmpl/dialog_discount.html | 45 ++++++++++++ 5 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 src/app/components/services/tmpl/dialog_discount.controller.js create mode 100644 src/app/components/services/tmpl/dialog_discount.html diff --git a/src/app/app.states.js b/src/app/app.states.js index 53b1fe21..2eca813b 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -478,6 +478,7 @@ function loadSeCIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'app/components/services/tmpl/dialog_discount.controller.js', 'app/components/services/tmpl/dialog_partialpayment.controller.js', 'app/components/services/services-add.controller.js' ]) @@ -492,6 +493,7 @@ function loadSeUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'app/components/services/tmpl/dialog_discount.controller.js', 'app/components/services/tmpl/dialog_partialpayment.controller.js', 'app/components/services/services-edit.controller.js' ]) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index b8a318c6..23619ac2 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -198,12 +198,13 @@ vm.calculateDue = calculateDue; vm.IsPartialPayment = IsPartialPayment; vm.changeServiceStatus = changeServiceStatus; + vm.openDiscountBox = openDiscountBox; // default execution steps setCoverPic(); - changeUserInfoState(true); // ammToDo: Enable this while commiting + // changeUserInfoState(true); // ammToDo: Enable this while commiting setTimeout(focusUserName, 700); - // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting + changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting buildDelayedToggler('service-details-left'); getDefaultServiceType(); getTreatmentDisplayFormat(); @@ -221,6 +222,29 @@ // function definitions + function openDiscountBox() { + $mdDialog.show({ + controller: 'amCtrlSeDc', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl/dialog_discount.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + discountValue: vm.discountValue, + treatmentLength: (vm.selectedProblems.length ? vm.selectedProblems.length : 0), + partLength: (vm.selectedInventories.length ? vm.selectedInventories.length : 0), + discountObj: undefined + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (!res) + return; + console.log(res); + } + } + function changeServiceStatus() { if (vm.servicestatus) { if (vm.service.partialpayment) { diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 2d2125ad..d0ab02cf 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -746,9 +746,14 @@ Round Off - - Discount -  % + + Discount +  % + + + more + Manage Discounts +
diff --git a/src/app/components/services/tmpl/dialog_discount.controller.js b/src/app/components/services/tmpl/dialog_discount.controller.js new file mode 100644 index 00000000..8d230e17 --- /dev/null +++ b/src/app/components/services/tmpl/dialog_discount.controller.js @@ -0,0 +1,72 @@ +/** + * Controller for discount control dialogbox + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlSeDc', DiscountController); + + DiscountController.$inject = ['$mdDialog', 'treatmentLength', 'partLength', 'discountObj', 'discountValue']; + + function DiscountController($mdDialog, treatmentLength, partLength, discountObj, discountValue) { + // initiailize view model + var vm = this; + + // adjustments to input assignments + discountValue = (discountValue ? parseFloat(discountValue) : 0); + + // named assignments for view model + vm.treatmentDiscount = 0; + vm.partDiscount = 0; + vm.totalDiscount = 0; + vm.isTreatment = (treatmentLength > 0); + vm.isPart = (partLength > 0); + vm.isTreatmentSelected = false; + vm.isPartSelected = false; + + // function maps to view model + vm.cancel = cancel; + vm.calculateTotal = calculateTotal; + vm.checkTreatmentDiscount = checkTreatmentDiscount; + vm.checkPartDiscount = checkPartDiscount; + + // default execution steps + initialize(); + + // function definitions + + function checkTreatmentDiscount() { + vm.isTreatmentSelected = (vm.treatmentDiscount > 0); + calculateTotal(); + } + + function checkPartDiscount() { + vm.isPartSelected = (vm.partDiscount > 0); + calculateTotal(); + } + + function initialize() { + if (discountObj == undefined) { + if (partLength > 0) + vm.partDiscount = (treatmentLength > 0) ? (discountValue / 2) : discountValue; + if (treatmentLength > 0) + vm.treatmentDiscount = (partLength > 0) ? (discountValue / 2) : discountValue; + vm.isTreatmentSelected = (vm.treatmentDiscount > 0); + vm.isPartSelected = (vm.partDiscount > 0); + calculateTotal(); + } + } + + function calculateTotal() { + vm.totalDiscount = (vm.isTreatmentSelected ? parseFloat(vm.treatmentDiscount) : 0) + (vm.isPartSelected ? parseFloat(vm.partDiscount) : 0); + } + + function cancel() { + $mdDialog.cancel(); + } + } +})(); \ No newline at end of file diff --git a/src/app/components/services/tmpl/dialog_discount.html b/src/app/components/services/tmpl/dialog_discount.html new file mode 100644 index 00000000..b21dc4e6 --- /dev/null +++ b/src/app/components/services/tmpl/dialog_discount.html @@ -0,0 +1,45 @@ + + + + Discount For: +
+
+ Treatments: + + Rs. +
+
+ Parts: + + Rs. +
+
+ Total: + {{vm.totalDiscount}} + Rs. +
+
+ + done + Done + + + close + Cancel + +
+
+
+
\ No newline at end of file From c704094b60ced65e8cf307036e8887f3c172f0cc Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 4 Aug 2016 12:03:48 +0530 Subject: [PATCH 19/91] Enhancement #181 | Tax Calculation > Tax should not be calculated on discounted amount --- .../services/services-add.controller.js | 241 ++++++++++------ .../services/services-edit.controller.js | 268 ++++++++++++------ src/app/components/services/services_add.html | 17 +- .../tmpl/dialog_discount.controller.js | 28 +- .../services/tmpl/dialog_discount.html | 2 +- 5 files changed, 353 insertions(+), 203 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 23619ac2..b9671d56 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -25,16 +25,14 @@ var vm = this; // temporary named assignments - var autofillVehicle = false, - flagGetUserBasedOnMobile = true, - olInvoiceNo = 1, - olJobCardNo = 1, - olEstimateNo = 1, - transitIds = undefined, - transitInterState = undefined, - forceStopCalCost = false, - serviceTcRo = 0, - serviceTcDc = 0; + var autofillVehicle = false; + var flagGetUserBasedOnMobile = true; + var olInvoiceNo = 1, olJobCardNo = 1, olEstimateNo = 1; + var transitIds = undefined, transitInterState = undefined; + var forceStopCalCost = false; + var serviceTcRo = 0, serviceTcDc = 0; + var treatmentTotal = 0, inventoryTotal = 0; + var dTreatmentTax, dInventoryTax, dTreatment, dInventory; // vm assignments to keep track of UI related elements vm.vehicleTypeList = []; @@ -61,7 +59,12 @@ invoiceno: 1, jobcardno: 1, estimateno: 1, - problems: [] + problems: [], + taxes: { + serviceTax: 0, + vat: 0 + }, + subtotal: 0 }; vm.problem = { details: '', @@ -80,8 +83,6 @@ vm.membershipChips = []; vm.serviceTypeList = ['Treatments', 'Package', 'Membership']; vm.roundedOffVal = 0; - vm.discountPercentage = 0; - vm.discountValue = 0; vm.inventories = []; vm.selectedInventories = []; vm.inventory = { @@ -104,6 +105,11 @@ vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); vm.problemFocusIndex = -1; vm.inventoryFocusIndex = -1; + vm.discount = { + treatment: 0, + part: 0, + total: 0 + } // named assignments to handle behaviour of UI elements vm.redirect = { @@ -132,7 +138,6 @@ vm.save = save; vm.queryMembershipChip = queryMembershipChip; vm.OnClickMembershipChip = OnClickMembershipChip; - vm.calculateCost = calculateCost; vm.OnAddMembershipChip = OnAddMembershipChip; vm.navigateToSubscribeMembership = navigateToSubscribeMembership; vm.goBack = goBack; @@ -140,7 +145,6 @@ vm.changeServiceTax = changeServiceTax; vm.OnServiceTaxEnabledChange = OnServiceTaxEnabledChange; vm.convertPbToTitleCase = convertPbToTitleCase; - vm.calculateTax = calculateTax; vm.convertInToTitleCase = convertInToTitleCase; vm.changeVat = changeVat; vm.onInventorySelected = onInventorySelected; @@ -149,7 +153,6 @@ vm.inventoryQuerySearch = inventoryQuerySearch; vm.updateInventoryDetails = updateInventoryDetails; vm.finalizeNewInventory = finalizeNewInventory; - vm.calculateVat = calculateVat; vm.changeQty = changeQty; vm.changeInventoryTotal = changeInventoryTotal; vm.populateRoD = populateRoD; @@ -199,12 +202,13 @@ vm.IsPartialPayment = IsPartialPayment; vm.changeServiceStatus = changeServiceStatus; vm.openDiscountBox = openDiscountBox; + vm.OnDiscountStateChange = OnDiscountStateChange; // default execution steps setCoverPic(); - // changeUserInfoState(true); // ammToDo: Enable this while commiting + changeUserInfoState(true); // ammToDo: Enable this while commiting setTimeout(focusUserName, 700); - changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting + // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting buildDelayedToggler('service-details-left'); getDefaultServiceType(); getTreatmentDisplayFormat(); @@ -222,6 +226,19 @@ // function definitions + function calculate() { + calculateSubtotal(); + calculateTotalDiscount(); + calculateServiceTax(); + calculateVat(); + calculateCost(); + } + + function OnDiscountStateChange() { + calculateDiscount(); + calculate(); + } + function openDiscountBox() { $mdDialog.show({ controller: 'amCtrlSeDc', @@ -230,10 +247,11 @@ parent: angular.element(document.body), targetEvent: event, locals: { - discountValue: vm.discountValue, - treatmentLength: (vm.selectedProblems.length ? vm.selectedProblems.length : 0), - partLength: (vm.selectedInventories.length ? vm.selectedInventories.length : 0), - discountObj: undefined + treatmentLength: (vm.selectedProblems.length ? vm.selectedProblems.length : 0) + ((parseFloat(vm.problem.amount) > 0) ? 1 : 0), + partLength: (vm.selectedInventories.length ? vm.selectedInventories.length : 0) + ((parseFloat(vm.inventory.amount) > 0) ? 1 : 0), + discountObj: vm.discount, + partTotal: inventoryTotal, + treatmentTotal: treatmentTotal }, clickOutsideToClose: true }).then(success).catch(success); @@ -241,7 +259,10 @@ function success(res) { if (!res) return; - console.log(res); + vm.discount.treatment = res.treatment; + vm.discount.part = res.part; + vm.discount.total = res.total; + calculate(); } } @@ -343,22 +364,27 @@ function calculateSubtotal() { var totalCost = 0; + treatmentTotal = 0, inventoryTotal = 0; vm.service.problems.forEach(iterateProblem); - iterateProblem(vm.problem); + totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; vm.selectedInventories.forEach(iterateInventories); - iterateInventories(vm.inventory); + totalCost += (vm.inventory.rate) ? (parseFloat(vm.inventory.rate) * parseFloat(vm.inventory.qty)) : 0; if (vm.serviceType == vm.serviceTypeList[1]) { vm.packages.forEach(iteratePackages); } totalCost = (totalCost % 1 != 0) ? totalCost.toFixed(2) : parseInt(totalCost); - return totalCost; + vm.service.subtotal = parseFloat(totalCost); function iterateProblem(element) { - totalCost += parseFloat(element.rate ? (element.rate * (element.checked ? 1 : 0)) : 0); + var temp = parseFloat(element.rate ? (element.rate * (element.checked ? 1 : 0)) : 0); + totalCost += temp; + treatmentTotal += temp; } function iterateInventories(element) { - totalCost += parseFloat(element.rate ? ((element.rate * element.qty) * (element.checked ? 1 : 0)) : 0); + var temp = parseFloat(element.rate ? ((element.rate * element.qty) * (element.checked ? 1 : 0)) : 0); + totalCost += temp; + inventoryTotal += temp; } function iteratePackages(package) { @@ -368,7 +394,9 @@ } function ipt(treatment) { - totalCost += treatment.rate[vm.vehicle.type.toLowerCase().replace(' ', '-')]; + var temp = treatment.rate[vm.vehicle.type.toLowerCase().replace(' ', '-')]; + totalCost += temp; + treatmentTotal += temp; } } @@ -599,11 +627,16 @@ } function calculateVat() { + if (vm.isDiscountApplied && (vm.discount.total > 0)) { + vm.service.taxes.vat = dInventoryTax; + return; + } var totalTax = 0.0; + if ((vm.vatSettings && vm.vatSettings.applyTax) && vm.inventory.tax && (vm.inventory.amount > 0)) + totalTax += parseFloat(vm.inventory.tax * vm.inventory.qty); vm.selectedInventories.forEach(iterateInventories); - iterateInventories(vm.inventory); totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : totalTax; - return totalTax; + vm.service.taxes.vat = totalTax; function iterateInventories(element) { if ((vm.vatSettings && !vm.vatSettings.applyTax) || !element.tax || !element.checked) @@ -665,7 +698,7 @@ vm.inventory.tax = ''; vm.inventory.qty = 1; vm.inventory.total = ''; - calculateCost(); + calculate(); if (isFromAutocomplete || foundExisting.length != 0) vm.inventoryFocusIndex = (foundExisting.length == 0) ? vm.selectedInventories.length - 1 : vm.selectedInventories.indexOf(foundExisting[0]); else @@ -708,7 +741,7 @@ } vm.inventory.total = vm.inventory.amount * vm.inventory.qty; vm.inventory.checked = true; - calculateCost(); + calculate(); } } else { vm.inventory.checked = false; @@ -786,7 +819,7 @@ function changeQty(inventory) { inventory.total = ((vm.vatSettings.applyTax ? inventory.amount : inventory.rate) * inventory.qty); - calculateCost(); + calculate(); if (!vm.vatSettings.applyTax) inventory.amount = inventory.rate; @@ -811,7 +844,7 @@ } else inventory.rate = inventory.amount; changeQty(inventory); - calculateCost(); + calculate(); } function convertPbToTitleCase() { @@ -855,7 +888,7 @@ problem.amount = problem.rate; } else problem.rate = problem.amount; - calculateCost(); + calculate(); } function getServiceTaxSettings() { @@ -1139,7 +1172,7 @@ var total = 0; package.selectedTreatments.forEach(it); package.total = total; - calculateCost(); + calculate(); return total; function it(t) { @@ -1524,7 +1557,7 @@ vm.service.problems.forEach(iterateProblem); iterateProblem(vm.problem); calculatePackageTax(); - calculateCost(); + calculate(); function iterateProblem(problem) { var found = $filter('filter')(vm.treatments, { @@ -1616,8 +1649,39 @@ function populateRoD() { if (vm.isDiscountApplied) { - vm.discountValue = serviceTcDc - vm.service.cost; - calculateDiscount(false); + var noTerminate = true, tp = 0.5, ip = 1 - tp; + if (treatmentTotal == 0) { + tp = 0; + ip = 1; + } else if (inventoryTotal == 0) { + ip = 0; + tp = 1; + } + while (noTerminate) { + var xt = (tp * vm.service.cost * 100) / (vm.sTaxSettings.tax + 100); + if (treatmentTotal < xt) { + tp -= 0.1; + ip = 1 - tp; + if (tp < 0) + break; + continue; + } + vm.discount.treatment = treatmentTotal - xt; + var xi = (ip * vm.service.cost * 100) / (vm.vatSettings.tax + 100); + if (inventoryTotal < xi) { + ip -= 0.1; + tp = 1 - ip; + if (ip < 0) + break; + continue; + } + vm.discount.part = inventoryTotal - xi; + noTerminate = false; + } + + vm.discount.total = parseFloat(vm.discount.treatment) + parseFloat(vm.discount.part); + vm.discount.total = (vm.discount.total % 1 != 0) ? parseFloat(vm.discount.total.toFixed(2)) : parseInt(vm.discount.total); + calculate(); } else if (vm.isRoundOffVal) vm.roundedOffVal = vm.service.cost - serviceTcRo; } @@ -1629,20 +1693,40 @@ vm.roundedOffVal = (totalCost - ot); vm.roundedOffVal = (vm.roundedOffVal % 1 != 0) ? parseFloat(vm.roundedOffVal.toFixed(2)) : parseInt(vm.roundedOffVal); } - calculateCost(); + calculate(); } - function calculateDiscount(isDiscountByPercent) { - var totalCost = vm.service.cost; - if (isDiscountByPercent) { - vm.discountValue = totalCost * parseFloat(vm.discountPercentage) / 100; - vm.discountValue = (isNaN(vm.discountValue) || vm.discountValue == null) ? '' : vm.discountValue; - vm.discountValue = (vm.discountValue % 1 != 0) ? parseFloat(vm.discountValue.toFixed(2)) : parseInt(vm.discountValue); - } else if (vm.discountValue != '') { - vm.discountPercentage = 100 * parseFloat(vm.discountValue) / totalCost; - vm.discountPercentage = (vm.discountPercentage % 1 != 0) ? parseFloat(vm.discountPercentage.toFixed(1)) : parseInt(vm.discountPercentage); - } - calculateCost(); + function calculateDiscount() { + var treatmentLength = (vm.selectedProblems.length ? vm.selectedProblems.length : 0) + ((parseFloat(vm.problem.amount) > 0) ? 1 : 0); + var partLength = (vm.selectedInventories.length ? vm.selectedInventories.length : 0) + ((parseFloat(vm.inventory.amount) > 0) ? 1 : 0); + if (partLength > 0) { + var dv2 = vm.discount.total * 0.5; + var partValue = (dv2 > inventoryTotal) ? inventoryTotal : dv2; + vm.discount.part = (treatmentLength > 0) ? partValue : vm.discount.total; + } else + vm.discount.part = 0; + if (treatmentLength > 0) { + var treatmentValue = vm.discount.total - vm.discount.part; + if (treatmentValue > treatmentTotal) { + treatmentValue = treatmentTotal; + vm.discount.part = vm.discount.total - treatmentValue; + } + vm.discount.treatment = (partLength > 0) ? treatmentValue : vm.discount.total; + } else + vm.discount.treatment = 0; + calculate(); + } + + function calculateTotalDiscount() { + if (!vm.isDiscountApplied) + return; + dTreatment = treatmentTotal - vm.discount.treatment; + dInventory = inventoryTotal - vm.discount.part; + + dTreatmentTax = (vm.sTaxSettings.applyTax) ? (dTreatment * vm.sTaxSettings.tax / 100) : 0; + dInventoryTax = (vm.vatSettings.applyTax) ? (dInventory * vm.vatSettings.tax / 100) : 0; + dTreatmentTax = (dTreatmentTax % 1 != 0) ? parseFloat(dTreatmentTax.toFixed(2)) : dTreatmentTax; + dInventoryTax = (dInventoryTax % 1 != 0) ? parseFloat(dInventoryTax.toFixed(2)) : dInventoryTax; } function changeForceStopCalCost(bool) { @@ -1653,17 +1737,9 @@ if (forceStopCalCost) return; var totalCost = 0; - vm.service.problems.forEach(iterateProblem); - iterateProblem(vm.problem); - vm.selectedInventories.forEach(iterateInventories); - iterateInventories(vm.inventory); - if (vm.serviceType == vm.serviceTypeList[1]) { - vm.packages.forEach(iteratePackages); - } + var discountedSubtotal = parseFloat(dTreatment + dTreatmentTax) + parseFloat(dInventory + dInventoryTax); + totalCost = (vm.isDiscountApplied && (vm.discount.total > 0)) ? Math.round(discountedSubtotal) : (parseFloat(vm.service.subtotal) + parseFloat(vm.service.taxes.serviceTax) + parseFloat(vm.service.taxes.vat)); serviceTcDc = totalCost; - if (vm.isDiscountApplied) { - totalCost = vm.isDiscountApplied && !isNaN(vm.discountValue) ? totalCost - vm.discountValue : totalCost; - } serviceTcRo = totalCost; if (vm.isRoundOffVal) { totalCost += parseFloat(vm.roundedOffVal); @@ -1672,34 +1748,21 @@ totalCost = (totalCost % 1 != 0) ? parseFloat(totalCost.toFixed(2)) : totalCost; totalCost = (totalCost % 1).toFixed(2) == 0.00 ? Math.round(totalCost) : totalCost; vm.service.cost = parseFloat(totalCost); - - function iterateProblem(element) { - totalCost += parseFloat(element.amount ? (element.amount * (element.checked ? 1 : 0)) : 0); - } - - function iterateInventories(element) { - totalCost += parseFloat(element.total ? (element.total * (element.checked ? 1 : 0)) : 0); - } - - function iteratePackages(package) { - if (!package.checked) - return; - package.selectedTreatments.forEach(ipt); - } - - function ipt(treatment) { - totalCost += treatment.amount[vm.vehicle.type.toLowerCase().replace(' ', '-')]; - } } - function calculateTax() { + function calculateServiceTax() { + if (vm.isDiscountApplied && (vm.discount.total > 0)) { + vm.service.taxes.serviceTax = dTreatmentTax; + return; + } var totalTax = 0.0; - iterateProblems(vm.problem); + if ((vm.problem.amount > 0) && (vm.sTaxSettings && vm.sTaxSettings.applyTax) && vm.problem.tax) + totalTax += parseFloat(vm.problem.tax); vm.service.problems.forEach(iterateProblems); if (vm.serviceType == vm.serviceTypeList[1]) vm.packages.forEach(iteratePackages); totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : parseInt(totalTax); - return totalTax; + vm.service.taxes.serviceTax = parseFloat(totalTax); function iterateProblems(problem) { if ((vm.sTaxSettings && !vm.sTaxSettings.applyTax) || !problem.tax || !problem.checked) @@ -1755,7 +1818,7 @@ vm.problem.amount = ''; vm.problem.rate = ''; vm.problem.tax = ''; - calculateCost(); + calculate(); if (isFromAutocomplete || foundExisting.length != 0) vm.problemFocusIndex = (foundExisting.length == 0) ? vm.selectedProblems.length - 1 : vm.selectedProblems.indexOf(foundExisting[0]); else @@ -1791,7 +1854,7 @@ vm.problem.amount = (rate == '' || rate == undefined ? vm.problem.amount : rate); } vm.problem.checked = true; - calculateCost(); + calculate(); } } else { vm.problem.rate = ''; @@ -1866,12 +1929,8 @@ vm.service.date = moment(vm.service.date).format(); if (vm.isNextDueService) vm.vehicle.nextdue = moment(vm.nextDueDate).format(); - if (vm.isDiscountApplied) { - vm.service['discount'] = { - percent: parseFloat(vm.discountPercentage), - amount: parseFloat(vm.discountValue) - } - } + if (vm.isDiscountApplied) + vm.service.discount = vm.discount; if (vm.isRoundOffVal) { vm.service['roundoff'] = vm.roundedOffVal; } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index e9f4e4f3..3b3819b5 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -25,20 +25,16 @@ var vm = this; // temporary named assignments - var autofillVehicle = false, - isDiscountByPercent = true, - isManualRoundOff = false, - isFirstTimeLoad = true, - serviceTcRo = 0, - serviceTcDc = 0, - forceStopCalCost = false, - olInvoiceNo = 0, - olJobCardNo = 0, - olEstimateNo = 0, - isInvoice = false, - isEstimate = false, - isJobCard = false, - wasNextDueService = false; + var autofillVehicle = false; + var isDiscountByPercent = true, isManualRoundOff = false; + var isFirstTimeLoad = true; + var serviceTcRo = 0, serviceTcDc = 0; + var forceStopCalCost = false; + var olInvoiceNo = 0, olJobCardNo = 0, olEstimateNo = 0; + var isInvoice = false, isEstimate = false, isJobCard = false; + var wasNextDueService = false; + var treatmentTotal = 0, inventoryTotal = 0; + var dTreatmentTax, dInventoryTax, dTreatment, dInventory; // vm assignments to keep track of UI related elements vm.vehicleTypeList = []; @@ -65,7 +61,12 @@ invoiceno: undefined, jobcardno: undefined, estimateno: undefined, - problems: [] + problems: [], + taxes: { + serviceTax: 0, + vat: 0 + }, + subtotal: 0 }; vm.problem = { details: '', @@ -82,8 +83,6 @@ vm.membershipChips = []; vm.serviceTypeList = ['Treatments', 'Package', 'Membership']; vm.roundedOffVal = 0; - vm.discountPercentage = 0; - vm.discountValue = 0; vm.inventories = []; vm.selectedInventories = []; vm.inventory = { @@ -106,6 +105,11 @@ vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); vm.problemFocusIndex = -1; vm.inventoryFocusIndex = -1; + vm.discount = { + treatment: 0, + part: 0, + total: 0 + } // named assignments to handle behaviour of UI elements vm.redirect = { @@ -128,7 +132,6 @@ vm.save = save; vm.queryMembershipChip = queryMembershipChip; vm.OnClickMembershipChip = OnClickMembershipChip; - vm.calculateCost = calculateCost; vm.OnAddMembershipChip = OnAddMembershipChip; vm.navigateToSubscribeMembership = navigateToSubscribeMembership; vm.goBack = goBack; @@ -136,7 +139,6 @@ vm.changeServiceTax = changeServiceTax; vm.OnServiceTaxEnabledChange = OnServiceTaxEnabledChange; vm.convertPbToTitleCase = convertPbToTitleCase; - vm.calculateTax = calculateTax; vm.convertInToTitleCase = convertInToTitleCase; vm.changeVat = changeVat; vm.onInventorySelected = onInventorySelected; @@ -194,6 +196,8 @@ vm.calculateDue = calculateDue; vm.IsPartialPayment = IsPartialPayment; vm.changeServiceStatus = changeServiceStatus; + vm.openDiscountBox = openDiscountBox; + vm.OnDiscountStateChange = OnDiscountStateChange; // default execution steps setCoverPic(); @@ -208,6 +212,46 @@ // function definitions + function calculate() { + calculateSubtotal(); + calculateTotalDiscount(); + calculateServiceTax(); + calculateVat(); + calculateCost(); + } + + function OnDiscountStateChange() { + calculateDiscount(); + calculate(); + } + + function openDiscountBox() { + $mdDialog.show({ + controller: 'amCtrlSeDc', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl/dialog_discount.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + treatmentLength: (vm.selectedProblems.length ? vm.selectedProblems.length : 0) + ((parseFloat(vm.problem.amount) > 0) ? 1 : 0), + partLength: (vm.selectedInventories.length ? vm.selectedInventories.length : 0) + ((parseFloat(vm.inventory.amount) > 0) ? 1 : 0), + discountObj: vm.discount, + partTotal: inventoryTotal, + treatmentTotal: treatmentTotal + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (!res) + return; + vm.discount.treatment = res.treatment; + vm.discount.part = res.part; + vm.discount.total = res.total; + calculate(); + } + } + function changeServiceStatus() { if (vm.servicestatus) { if (vm.service.partialpayment) { @@ -306,22 +350,27 @@ function calculateSubtotal() { var totalCost = 0; + treatmentTotal = 0, inventoryTotal = 0; vm.service.problems.forEach(iterateProblem); - iterateProblem(vm.problem); + totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; vm.selectedInventories.forEach(iterateInventories); - iterateInventories(vm.inventory); + totalCost += (vm.inventory.rate) ? (parseFloat(vm.inventory.rate) * parseFloat(vm.inventory.qty)) : 0; if (vm.serviceType == vm.serviceTypeList[1]) { vm.packages.forEach(iteratePackages); } totalCost = (totalCost % 1 != 0) ? totalCost.toFixed(2) : parseInt(totalCost); - return totalCost; + vm.service.subtotal = parseFloat(totalCost); function iterateProblem(element) { - totalCost += parseFloat(element.rate ? (element.rate * (element.checked ? 1 : 0)) : 0); + var temp = parseFloat(element.rate ? (element.rate * (element.checked ? 1 : 0)) : 0); + totalCost += temp; + treatmentTotal += temp; } function iterateInventories(element) { - totalCost += parseFloat(element.rate ? ((element.rate * element.qty) * (element.checked ? 1 : 0)) : 0); + var temp = parseFloat(element.rate ? ((element.rate * element.qty) * (element.checked ? 1 : 0)) : 0); + totalCost += temp; + inventoryTotal += temp; } function iteratePackages(package) { @@ -331,7 +380,9 @@ } function ipt(treatment) { - totalCost += treatment.rate[vm.vehicle.type.toLowerCase().replace(' ', '-')]; + var temp = treatment.rate[vm.vehicle.type.toLowerCase().replace(' ', '-')]; + totalCost += temp; + treatmentTotal += temp; } } @@ -587,11 +638,16 @@ } function calculateVat() { + if (vm.isDiscountApplied && (vm.discount.total > 0)) { + vm.service.taxes.vat = dInventoryTax; + return; + } var totalTax = 0.0; + if ((vm.vatSettings && vm.vatSettings.applyTax) && vm.inventory.tax && (vm.inventory.amount > 0)) + totalTax += parseFloat(vm.inventory.tax * vm.inventory.qty); vm.selectedInventories.forEach(iterateInventories); - iterateInventories(vm.inventory); totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : totalTax; - return totalTax; + vm.service.taxes.vat = totalTax; function iterateInventories(element) { if ((vm.vatSettings && !vm.vatSettings.applyTax) || !element.tax || !element.checked) @@ -635,7 +691,7 @@ }); vm.selectedInventories.push(vm.inventories[vm.inventories.length - 1]); } - calculateCost(); + calculate(); vm.inventory.name = ''; vm.inventory.amount = ''; vm.inventory.rate = ''; @@ -682,7 +738,7 @@ } vm.inventory.total = vm.inventory.amount * vm.inventory.qty; vm.inventory.checked = true; - calculateCost(); + calculate(); } } else { vm.inventory.checked = false; @@ -695,7 +751,7 @@ function changeQty(inventory) { inventory.total = ((vm.vatSettings.applyTax ? inventory.amount : inventory.rate) * inventory.qty); - calculateCost(); + calculate(); } function inventoryQuerySearch() { @@ -831,7 +887,7 @@ } else inventory.rate = inventory.amount; changeQty(inventory); - calculateCost(); + calculate(); } function convertPbToTitleCase() { @@ -881,7 +937,7 @@ problem.amount = problem.rate; } else problem.rate = problem.amount; - calculateCost(); + calculate(); } function getServiceTaxSettings() { @@ -1166,7 +1222,7 @@ var total = 0; package.selectedTreatments.forEach(it); package.total = total; - calculateCost(); + calculate(); return total; function it(t) { @@ -1291,12 +1347,6 @@ vm.service.partialpayment = res.vehicle.service.partialpayment; vm.service.state = (res.vehicle.service.state == undefined) ? vm.serviceStateList[2] : res.vehicle.service.state; vm.label_invoice = (vm.service.state == vm.serviceStateList[2]) ? 'Invoice' : 'Send'; - if (res.vehicle.service.discount) { - vm.isDiscountApplied = true; - isDiscountByPercent = false; - vm.discountPercentage = parseFloat(res.vehicle.service.discount.percent); - vm.discountValue = parseFloat(res.vehicle.service.discount.amount); - } if (res.vehicle.service.roundoff) { vm.isRoundOffVal = true; isManualRoundOff = vm.isRoundOffVal; @@ -1324,6 +1374,15 @@ getLastJobCardNo(); getLastEstimateNo(); getLastInvoiceNo(); + if (res.vehicle.service.discount) { + vm.isDiscountApplied = true; + if (res.vehicle.service.discount.amount) { + vm.discount.total = res.vehicle.service.discount.amount; + calculateDiscount(); + } else { + vm.discount = res.vehicle.service.discount; + } + } function iterateMemberships(membership) { res.memberships[membership].name = membership; @@ -1486,7 +1545,7 @@ vm.service.problems.forEach(iterateProblem); iterateProblem(vm.problem); calculatePackageTax(); - calculateCost(); + calculate(); function iterateProblem(problem) { var found = $filter('filter')(vm.treatments, { @@ -1601,8 +1660,38 @@ function populateRoD() { if (vm.isDiscountApplied) { - vm.discountValue = serviceTcDc - vm.service.cost; - calculateDiscount(false); + var noTerminate = true, tp = 0.5, ip = 1 - tp; + if (treatmentTotal == 0) { + tp = 0; + ip = 1; + } else if (inventoryTotal == 0) { + ip = 0; + tp = 1; + } + while (noTerminate) { + var xt = (tp * vm.service.cost * 100) / (vm.sTaxSettings.tax + 100); + if (treatmentTotal < xt) { + tp -= 0.1; + ip = 1 - tp; + if (tp < 0) + break; + continue; + } + vm.discount.treatment = treatmentTotal - xt; + var xi = (ip * vm.service.cost * 100) / (vm.vatSettings.tax + 100); + if (inventoryTotal < xi) { + ip -= 0.1; + tp = 1 - ip; + if (ip < 0) + break; + continue; + } + vm.discount.part = inventoryTotal - xi; + noTerminate = false; + } + vm.discount.total = parseFloat(vm.discount.treatment) + parseFloat(vm.discount.part); + vm.discount.total = (vm.discount.total % 1 != 0) ? parseFloat(vm.discount.total.toFixed(2)) : parseInt(vm.discount.total); + calculate(); } else if (vm.isRoundOffVal) vm.roundedOffVal = vm.service.cost - serviceTcRo; } @@ -1614,20 +1703,40 @@ vm.roundedOffVal = (totalCost - ot); vm.roundedOffVal = (vm.roundedOffVal % 1 != 0) ? parseFloat(vm.roundedOffVal.toFixed(2)) : parseInt(vm.roundedOffVal); } - calculateCost(); + calculate(); } function calculateDiscount(isDiscountByPercent) { - var totalCost = vm.service.cost; - if (isDiscountByPercent) { - vm.discountValue = totalCost * parseFloat(vm.discountPercentage) / 100; - vm.discountValue = (isNaN(vm.discountValue) || vm.discountValue == null) ? '' : vm.discountValue; - vm.discountValue = (vm.discountValue % 1 != 0) ? parseFloat(vm.discountValue.toFixed(2)) : parseInt(vm.discountValue); - } else if (vm.discountValue != '') { - vm.discountPercentage = 100 * parseFloat(vm.discountValue) / totalCost; - vm.discountPercentage = (vm.discountPercentage % 1 != 0) ? parseFloat(vm.discountPercentage.toFixed(1)) : parseInt(vm.discountPercentage); - } - calculateCost(); + var treatmentLength = (vm.selectedProblems.length ? vm.selectedProblems.length : 0) + ((parseFloat(vm.problem.amount) > 0) ? 1 : 0); + var partLength = (vm.selectedInventories.length ? vm.selectedInventories.length : 0) + ((parseFloat(vm.inventory.amount) > 0) ? 1 : 0); + if (partLength > 0) { + var dv2 = vm.discount.total * 0.5; + var partValue = (dv2 > inventoryTotal) ? inventoryTotal : dv2; + vm.discount.part = (treatmentLength > 0) ? partValue : vm.discount.total; + } else + vm.discount.part = 0; + if (treatmentLength > 0) { + var treatmentValue = vm.discount.total - vm.discount.part; + if (treatmentValue > treatmentTotal) { + treatmentValue = treatmentTotal; + vm.discount.part = vm.discount.total - treatmentValue; + } + vm.discount.treatment = (partLength > 0) ? treatmentValue : vm.discount.total; + } else + vm.discount.treatment = 0; + calculate(); + } + + function calculateTotalDiscount() { + if (!vm.isDiscountApplied) + return; + dTreatment = treatmentTotal - vm.discount.treatment; + dInventory = inventoryTotal - vm.discount.part; + + dTreatmentTax = (vm.sTaxSettings && vm.sTaxSettings.applyTax) ? (dTreatment * vm.sTaxSettings.tax / 100) : 0; + dInventoryTax = (vm.vatSettings && vm.vatSettings.applyTax) ? (dInventory * vm.vatSettings.tax / 100) : 0; + dTreatmentTax = (dTreatmentTax % 1 != 0) ? parseFloat(dTreatmentTax.toFixed(2)) : dTreatmentTax; + dInventoryTax = (dInventoryTax % 1 != 0) ? parseFloat(dInventoryTax.toFixed(2)) : dInventoryTax; } function changeForceStopCalCost(bool) { @@ -1638,17 +1747,9 @@ if (forceStopCalCost) return; var totalCost = 0; - vm.service.problems.forEach(iterateProblem); - iterateProblem(vm.problem); - vm.selectedInventories.forEach(iterateInventories); - iterateInventories(vm.inventory); - if (vm.serviceType == vm.serviceTypeList[1]) { - vm.packages.forEach(iteratePackages); - } + var discountedSubtotal = parseFloat(dTreatment + dTreatmentTax) + parseFloat(dInventory + dInventoryTax); + totalCost = (vm.isDiscountApplied && (vm.discount.total > 0)) ? Math.round(discountedSubtotal) : (parseFloat(vm.service.subtotal) + parseFloat(vm.service.taxes.serviceTax) + parseFloat(vm.service.taxes.vat)); serviceTcDc = totalCost; - if (vm.isDiscountApplied) { - totalCost = vm.isDiscountApplied && !isNaN(vm.discountValue) ? totalCost - vm.discountValue : totalCost; - } serviceTcRo = totalCost; if (vm.isRoundOffVal) { totalCost += parseFloat(vm.roundedOffVal); @@ -1657,34 +1758,21 @@ totalCost = (totalCost % 1 != 0) ? parseFloat(totalCost.toFixed(2)) : totalCost; totalCost = (totalCost % 1).toFixed(2) == 0.00 ? Math.round(totalCost) : totalCost; vm.service.cost = parseFloat(totalCost); - - function iterateProblem(element) { - totalCost += parseFloat(element.amount ? (element.amount * (element.checked ? 1 : 0)) : 0); - } - - function iterateInventories(element) { - totalCost += parseFloat(element.total ? (element.total * (element.checked ? 1 : 0)) : 0); - } - - function iteratePackages(package) { - if (!package.checked) - return; - package.selectedTreatments.forEach(ipt); - } - - function ipt(treatment) { - totalCost += treatment.amount[vm.vehicle.type.toLowerCase().replace(' ', '-')]; - } } - function calculateTax() { + function calculateServiceTax() { + if (vm.isDiscountApplied && (vm.discount.total > 0)) { + vm.service.taxes.serviceTax = dTreatmentTax; + return; + } var totalTax = 0.0; + if ((vm.problem.amount > 0) && (vm.sTaxSettings && vm.sTaxSettings.applyTax) && vm.problem.tax) + totalTax += parseFloat(vm.problem.tax); vm.service.problems.forEach(iterateProblems); - iterateProblems(vm.problem); if (vm.serviceType == vm.serviceTypeList[1]) vm.packages.forEach(iteratePackages); totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : parseInt(totalTax); - return totalTax; + vm.service.taxes.serviceTax = parseFloat(totalTax); function iterateProblems(problem) { if ((vm.sTaxSettings && !vm.sTaxSettings.applyTax) || !problem.tax || !problem.checked) @@ -1736,7 +1824,7 @@ }); vm.selectedProblems.push(vm.service.problems[vm.service.problems.length - 1]); } - calculateCost(); + calculate(); vm.problem.details = ''; vm.problem.amount = ''; vm.problem.rate = ''; @@ -1775,7 +1863,7 @@ vm.problem.amount = (rate == '' || rate == undefined ? vm.problem.amount : rate); } vm.problem.checked = true; - calculateCost(); + calculate(); } } else { vm.problem.rate = ''; @@ -1838,12 +1926,8 @@ if (vm.problem.details) vm.service.problems.push(vm.problem); vm.service.problems.forEach(iterateProblems); - if (vm.isDiscountApplied) { - vm.service['discount'] = { - percent: parseFloat(vm.discountPercentage), - amount: parseFloat(vm.discountValue) - } - } + if (vm.isDiscountApplied) + vm.service.discount = vm.discount; if (vm.isRoundOffVal) { vm.service['roundoff'] = vm.roundedOffVal; } diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index d0ab02cf..771fbcdb 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -747,10 +747,9 @@ Round Off - Discount -  % + Discount - + more Manage Discounts @@ -758,16 +757,16 @@
- Subtotal: Rs. {{vm.calculateSubtotal()}} + Subtotal: Rs. {{vm.service.subtotal}}   - Service Tax: Rs. {{vm.calculateTax()}} -   - VAT: Rs. {{vm.calculateVat()}} -   - Discount: Rs. + Discount: Rs.   Round Off: Rs.   + Service Tax: Rs. {{vm.service.taxes.serviceTax}} +   + VAT: Rs. {{vm.service.taxes.vat}} +   Total Cost: Rs. {{vm.service.cost}}
diff --git a/src/app/components/services/tmpl/dialog_discount.controller.js b/src/app/components/services/tmpl/dialog_discount.controller.js index 8d230e17..f60aa31e 100644 --- a/src/app/components/services/tmpl/dialog_discount.controller.js +++ b/src/app/components/services/tmpl/dialog_discount.controller.js @@ -10,15 +10,12 @@ (function() { angular.module('automintApp').controller('amCtrlSeDc', DiscountController); - DiscountController.$inject = ['$mdDialog', 'treatmentLength', 'partLength', 'discountObj', 'discountValue']; + DiscountController.$inject = ['$mdDialog', 'treatmentLength', 'partLength', 'treatmentTotal', 'partTotal', 'discountObj']; - function DiscountController($mdDialog, treatmentLength, partLength, discountObj, discountValue) { + function DiscountController($mdDialog, treatmentLength, partLength, treatmentTotal, partTotal, discountObj) { // initiailize view model var vm = this; - // adjustments to input assignments - discountValue = (discountValue ? parseFloat(discountValue) : 0); - // named assignments for view model vm.treatmentDiscount = 0; vm.partDiscount = 0; @@ -33,6 +30,7 @@ vm.calculateTotal = calculateTotal; vm.checkTreatmentDiscount = checkTreatmentDiscount; vm.checkPartDiscount = checkPartDiscount; + vm.save = save; // default execution steps initialize(); @@ -50,11 +48,11 @@ } function initialize() { - if (discountObj == undefined) { - if (partLength > 0) - vm.partDiscount = (treatmentLength > 0) ? (discountValue / 2) : discountValue; - if (treatmentLength > 0) - vm.treatmentDiscount = (partLength > 0) ? (discountValue / 2) : discountValue; + if (discountObj != undefined) { + vm.treatmentDiscount = parseFloat(discountObj.treatment); + vm.partDiscount = parseFloat(discountObj.part); + vm.treatmentDiscount = (vm.treatmentDiscount % 1 != 0) ? parseFloat(vm.treatmentDiscount.toFixed(2)) : parseInt(vm.treatmentDiscount); + vm.partDiscount = (vm.partDiscount % 1 != 0) ? parseFloat(vm.partDiscount.toFixed(2)) : parseInt(vm.partDiscount); vm.isTreatmentSelected = (vm.treatmentDiscount > 0); vm.isPartSelected = (vm.partDiscount > 0); calculateTotal(); @@ -63,6 +61,16 @@ function calculateTotal() { vm.totalDiscount = (vm.isTreatmentSelected ? parseFloat(vm.treatmentDiscount) : 0) + (vm.isPartSelected ? parseFloat(vm.partDiscount) : 0); + vm.totalDiscount = (vm.totalDiscount % 1 != 0) ? parseFloat(vm.totalDiscount.toFixed(2)) : parseInt(vm.totalDiscount); + } + + function save() { + var discount = { + treatment: (vm.isTreatmentSelected ? vm.treatmentDiscount : 0), + part: (vm.isPartSelected ? vm.partDiscount : 0), + total: vm.totalDiscount + } + $mdDialog.hide(discount); } function cancel() { diff --git a/src/app/components/services/tmpl/dialog_discount.html b/src/app/components/services/tmpl/dialog_discount.html index b21dc4e6..24db08b9 100644 --- a/src/app/components/services/tmpl/dialog_discount.html +++ b/src/app/components/services/tmpl/dialog_discount.html @@ -31,7 +31,7 @@ Rs.
- + done Done From 30e4f6f84223ba7dd8c3ec224dc332e7fad8bf96 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 4 Aug 2016 13:12:05 +0530 Subject: [PATCH 20/91] Bug Fix | Changes in Time Filter > Deleted those where selected but not available --- .../dashboard/dashboard.controller-deps.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/app/components/dashboard/dashboard.controller-deps.js b/src/app/components/dashboard/dashboard.controller-deps.js index a3ec6dfc..eff103b7 100644 --- a/src/app/components/dashboard/dashboard.controller-deps.js +++ b/src/app/components/dashboard/dashboard.controller-deps.js @@ -103,6 +103,7 @@ // default execution steps populateYearRange(); + detectUnavailable(); // function mappings vm.IsNextYear = IsNextYear; @@ -116,6 +117,27 @@ // function definitions + function detectUnavailable() { + var newlist = []; + vm.selectedDateSet.forEach(iterateDateSet); + vm.selectedDateSet = newlist; + + function iterateDateSet(ds) { + var found = $filter('filter')(filterRange, { + year: ds.year, + month: ds.month + }, true); + + if (found.length == 1) { + newlist.push(found[0]); + } + } + + function deleteSelected(ds) { + vm.selectedDateSet.splice(ds, 1); + } + } + function confirm() { if (vm.selectedDateSet.length < 1) vm.selectedDateSet = currentTimeSet; From 4f58be0507de84da1560df428ea38e6860b6d3ff Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 4 Aug 2016 13:26:54 +0530 Subject: [PATCH 21/91] Enhancement # 143 | Put two options in setting 1. Change and Move - Change the location of App data as well move current data to the new location 2. Select Existing - Do not move current data to this selected new location. Instead load the data from the selected location and set that location. --- .../components/settings/settings.controller.js | 11 +++++++---- src/app/components/settings/settings.html | 15 +++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index b3e0309e..8f8af84b 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -149,13 +149,16 @@ // function definitions - function setAmAppDataPath() { + function setAmAppDataPath(move) { var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); if (newPath) { ammPreferences.storePreference('automint.userDataPath', newPath[0]); - fse.copy(amApp.getPath('userData'), newPath[0], { - clobber: true - }, success); + if (move) { + fse.copy(amApp.getPath('userData'), newPath[0], { + clobber: true + }, success); + } else + removeSuccess(); } function success(res) { diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 6ab4f705..afda2b77 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -250,14 +250,17 @@ -
+
App Data + : {{vm.amAppPath}}
-
- {{vm.amAppPath}} - - - Change +
+ + create_new_folder + Change and Move + + + Select Existing folder_open
From f9ddd800228f127413815434864413be1395b870 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 5 Aug 2016 10:56:39 +0530 Subject: [PATCH 22/91] Feature #192 | Initialized Editable Taxes > Implementation done at Settings Page --- src/app/app.db.js | 6 +- src/app/app.states.js | 3 +- .../settings/settings-servicetax.factory.js | 188 ------------------ .../settings/settings-tax.factory.js | 137 +++++++++++++ .../settings/settings.controller.js | 165 ++++++++------- src/app/components/settings/settings.html | 143 ++++++------- 6 files changed, 306 insertions(+), 336 deletions(-) delete mode 100644 src/app/components/settings/settings-servicetax.factory.js create mode 100644 src/app/components/settings/settings-tax.factory.js diff --git a/src/app/app.db.js b/src/app/app.db.js index 8bd03e50..d0ce95c3 100644 --- a/src/app/app.db.js +++ b/src/app/app.db.js @@ -2,7 +2,7 @@ * Closure to create factories for handling databases * @author ndkcha * @since 0.4.1 - * @version 0.6.0 + * @version 0.7.0 */ /// @@ -238,7 +238,9 @@ // setup database function setDatabase(name) { - database = new PouchDB(name); + database = new PouchDB(name, { + auto_compaction: true + }); } // return change listeners of database diff --git a/src/app/app.states.js b/src/app/app.states.js index 2eca813b..a27a2554 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -505,8 +505,9 @@ 'app/components/settings/settings-login.factory.js', 'app/components/settings/settings-importdata.service.js', 'app/components/settings/settings-invoices.factory.js', - 'app/components/settings/settings-servicetax.factory.js', + 'app/components/settings/settings-tax.factory.js', 'app/components/settings/settings.factory.js', + 'assets/js/angular-elastic-input.min.js', 'assets/js/jquery.csv.min.js' ]) } diff --git a/src/app/components/settings/settings-servicetax.factory.js b/src/app/components/settings/settings-servicetax.factory.js deleted file mode 100644 index 9f4a016d..00000000 --- a/src/app/components/settings/settings-servicetax.factory.js +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Factory to fetch and retrieve service tax settings from database - * @author ndkcha - * @since 0.5.0 - * @version 0.6.1 - */ - -/// - -(function() { - angular.module('automintApp').factory('amSeTaxSettings', ServiceTaxSettings); - - ServiceTaxSettings.$inject = ['$q', '$amRoot', 'utils', 'pdbConfig']; - - function ServiceTaxSettings($q, $amRoot, utils, pdbConfig) { - // intialize factory variable and function maps - var factory = { - getServiceTaxSettings: getServiceTaxSettings, - saveServiceTaxSettings: saveServiceTaxSettings, - getVatSettings: getVatSettings, - saveVatSettings: saveVatSettings - } - - return factory; - - // function definitions - - function getServiceTaxSettings() { - var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - - function getSettingsObj(res) { - if (res.settings && res.settings.servicetax) { - tracker.resolve({ - applyTax: res.settings.servicetax.applyTax, - inclusive: (res.settings.servicetax.taxIncType == 'inclusive') ? true : false, - tax: res.settings.servicetax.tax - }); - } else - failure(); - } - - function failure(err) { - if (!err) { - err = { - success: false, - message: 'Service Tax Settings Not Found!' - } - } - tracker.reject(err); - } - } - - function saveServiceTaxSettings(taxObject) { - var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(writeSettingsDoc); - } - - function getSettingsObj(res) { - if (!res.settings) - res.settings = {}; - if (!res.settings.servicetax) - res.settings.servicetax = {}; - if (taxObject.applyTax != undefined) - res.settings.servicetax.applyTax = taxObject.applyTax; - if (taxObject.inclusive != undefined) - res.settings.servicetax.taxIncType = (taxObject.inclusive) ? 'inclusive' : 'exclusive'; - if (taxObject.tax != undefined) - res.settings.servicetax.tax = taxObject.tax; - pdbConfig.save(res).then(success).catch(failure); - } - - function writeSettingsDoc(err) { - var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, - settings: { - servicetax: {} - } - } - if (taxObject.applyTax) - doc.settings.servicetax.applyTax = taxObject.applyTax; - if (taxObject.inclusive) - doc.settings.servicetax.taxIncType = (taxObject.inclusive) ? 'inclusive' : 'exclusive'; - if (taxObject.tax) - doc.settings.servicetax.tax = taxObject.tax; - pdbConfig.save(doc).then(success).catch(failure); - } - - function success(res) { - tracker.resolve(res); - } - - function failure(err) { - tracker.reject(err); - } - } - - function getVatSettings() { - var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - - function getSettingsObj(res) { - if (res.settings && res.settings.vat) { - tracker.resolve({ - applyTax: res.settings.vat.applyTax, - inclusive: (res.settings.vat.taxIncType == 'inclusive') ? true : false, - tax: res.settings.vat.tax - }); - } else - failure(); - } - - function failure(err) { - if (!err) { - err = { - success: false, - message: 'VAT Settings Not Found!' - } - } - tracker.reject(err); - } - } - - function saveVatSettings(taxObject) { - var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(writeSettingsDoc); - } - - function getSettingsObj(res) { - if (!res.settings) - res.settings = {}; - if (!res.settings.vat) - res.settings.vat = {}; - if (taxObject.applyTax != undefined) - res.settings.vat.applyTax = taxObject.applyTax; - if (taxObject.inclusive != undefined) - res.settings.vat.taxIncType = (taxObject.inclusive) ? 'inclusive' : 'exclusive'; - if (taxObject.tax != undefined) - res.settings.vat.tax = taxObject.tax; - pdbConfig.save(res).then(success).catch(failure); - } - - function writeSettingsDoc(err) { - var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, - settings: { - vat: {} - } - } - if (taxObject.applyTax) - doc.settings.vat.applyTax = taxObject.applyTax; - if (taxObject.inclusive) - doc.settings.vat.taxIncType = (taxObject.inclusive) ? 'inclusive' : 'exclusive'; - if (taxObject.tax) - doc.settings.vat.tax = taxObject.tax; - pdbConfig.save(doc).then(success).catch(failure); - } - - function success(res) { - tracker.resolve(res); - } - - function failure(err) { - tracker.reject(err); - } - } - } -})(); \ No newline at end of file diff --git a/src/app/components/settings/settings-tax.factory.js b/src/app/components/settings/settings-tax.factory.js new file mode 100644 index 00000000..1ecd5e02 --- /dev/null +++ b/src/app/components/settings/settings-tax.factory.js @@ -0,0 +1,137 @@ +/** + * Factory to fetch and retrieve service tax settings from database + * @author ndkcha + * @since 0.5.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').factory('amTaxSettings', TaxSettings); + + TaxSettings.$inject = ['$q', '$amRoot', 'utils', 'pdbConfig']; + + function TaxSettings($q, $amRoot, utils, pdbConfig) { + // intialize factory variable and function maps + var factory = { + getAllTaxSettings: getAllTaxSettings, + saveTaxSettings: saveTaxSettings, + deleteTaxSettings: deleteTaxSettings + } + + return factory; + + // function definitions + + function deleteTaxSettings(tax) { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); + } + + function getSettingsObj(res) { + if (res.settings && res.settings.tax && res.settings.tax[tax.name]) { + delete res.settings.tax[tax.name]; + pdbConfig.save(res).then(success).catch(failure); + } + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function saveTaxSettings(tax) { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(writeSettingsDoc); + } + + function getSettingsObj(res) { + if (!res.settings) + res.settings = {}; + if (res.settings && !res.settings.tax) + res.settings.tax = {}; + res.settings.tax[tax.name] = { + type: (tax.inclusive) ? "inclusive" : "exclusive", + isTaxApplied: tax.isTaxApplied, + isForTreatments: tax.isForTreatments, + isForInventory: tax.isForInventory, + percent: tax.percent + }; + pdbConfig.save(res).then(success).catch(failure); + } + + function writeSettingsDoc(err) { + var doc = { + _id: utils.generateUUID('sttngs'), + creator: $amRoot.username, + settings: { + tax: {} + } + } + doc.settings.tax[tax.name] = { + type: (tax.inclusive) ? "inclusive" : "exclusive", + isTaxApplied: tax.isTaxApplied, + isForTreatments: tax.isForTreatments, + isForInventory: tax.isForInventory, + percent: tax.percent + } + pdbConfig.save(doc).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function getAllTaxSettings() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); + } + + function getSettingsObj(res) { + var result = []; + if (res.settings && res.settings.tax) { + Object.keys(res.settings.tax).forEach(iterateTax); + + function iterateTax(tax) { + var t = res.settings.tax[tax]; + result.push({ + inclusive: (t.type == "inclusive"), + isTaxApplied: t.isTaxApplied, + isForTreatments: t.isForTreatments, + isForInventory: t.isForInventory, + percent: t.percent, + name: tax + }); + } + } + tracker.resolve(result); + } + + function failure(err) { + tracker.reject(err); + } + } + } +})(); \ No newline at end of file diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 8f8af84b..a625d3c8 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -17,24 +17,20 @@ angular.module('automintApp').controller('amCtrlSettings', SettingsController); - SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', 'utils', 'amBackup', 'amLogin', 'amImportdata', 'amIvSettings', 'amSeTaxSettings', 'amSettings']; + SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', 'utils', 'amBackup', 'amLogin', 'amImportdata', 'amIvSettings', 'amTaxSettings', 'amSettings']; - function SettingsController($rootScope, $scope, $state, $log, utils, amBackup, amLogin, amImportdata, amIvSettings, amSeTaxSettings, amSettings) { + function SettingsController($rootScope, $scope, $state, $log, utils, amBackup, amLogin, amImportdata, amIvSettings, amTaxSettings, amSettings) { // initialize view model var vm = this; // temporary named assignments - var olino = 0, - oljbno = 0, - oleno = 0, - oPasscode = '1234', - oPasscodeEnabled, - oIvAlignTop = '', - oIvAlignBottom = '', - ivEmailSubject, oIvFacebookLink, oIvInstagramLink, oIvTwitterLink, oIvWorkshopName, oIvWorkshopPhone, oIvWorkshopAddress1, oIvWorkshopAddress2, oIvWorkshopCity, oDefaultServiceType; + var olino = 0, oljbno = 0, oleno = 0; + var oPasscode = '1234', oPasscodeEnabled; + var oIvAlignTop = '', oIvAlignBottom = ''; + var ivEmailSubject, oIvFacebookLink, oIvInstagramLink, oIvTwitterLink, oIvWorkshopName, oIvWorkshopPhone, oIvWorkshopAddress1, oIvWorkshopAddress2, oIvWorkshopCity, oDefaultServiceType; + var currentTaxFocusIndex = -1; // named assignments to keep track of UI [BEGIN] - // general settings vm.user = { username: '', password: '' @@ -44,7 +40,6 @@ vm.serviceStateList = ['Job Card', 'Estimate', 'Bill']; vm.serviceState = vm.serviceStateList[2]; vm.amAppPath = 'Default'; - // invoice settings vm.workshop = { name: '', phone: '', @@ -69,11 +64,11 @@ }; vm.label_ivAlignTopAlignment = 'Enter Top Margin:'; vm.label_ivAlignBottomAlignment = 'Enter Bottom Margin:'; + vm.taxSettings = []; // named assignments to keep track of UI [END] // function maps [BEGIN] vm.changeInvoiceTab = changeInvoiceTab; - // general settings vm.changeUsernameLabel = changeUsernameLabel; vm.changePasswordLabel = changePasswordLabel; vm.doBackup = doBackup; @@ -85,7 +80,6 @@ vm.handlePasscodeVisibility = handlePasscodeVisibility; vm.saveDefaultServiceType = saveDefaultServiceType; vm.setAmAppDataPath = setAmAppDataPath; - // invoice settings vm.changeWorkshopNameLabel = changeWorkshopNameLabel; vm.changeWorkshopPhoneLabel = changeWorkshopPhoneLabel; vm.changeWorkshopAddress1Label = changeWorkshopAddress1Label; @@ -101,8 +95,6 @@ vm.saveFacebookLink = saveFacebookLink; vm.saveInstagramLink = saveInstagramLink; vm.saveTwitterLink = saveTwitterLink; - vm.saveServiceTaxSettings = saveServiceTaxSettings; - vm.saveVatSettings = saveVatSettings; vm.savePasscode = savePasscode; vm.changeTopAlignLabel = changeTopAlignLabel; vm.changeBottomAlignLabel = changeBottomAlignLabel; @@ -121,6 +113,11 @@ vm.autoCapitalizeAddressLine2 = autoCapitalizeAddressLine2; vm.autoCapitalizeWorkshopName = autoCapitalizeWorkshopName; vm.autoCapitalizeEmailSubject = autoCapitalizeEmailSubject; + vm.addNewTax = addNewTax; + vm.deleteTax = deleteTax; + vm.saveTax = saveTax; + vm.autoCapitalizeName = autoCapitalizeName; + vm.IsTaxFocused = IsTaxFocused; // function maps [END] // default execution steps [BEGIN] @@ -131,24 +128,96 @@ default: break; } - // general settings checkLogin(); getPasscode(); getDefaultServiceType(); getAmAppDataPath(); - // invoice settings getWorkshopDetails(); getInvoiceSettings(); loadInvoiceWLogo(); getIvAlignMargins(); - // service tax settings - getServiceTaxSettings(); - getVatSettings(); + getAllTaxSettings(); // changeInvoiceTab(true) // testing purposes amTODO: remove it + changeTaxTab(true); // testing purposes amTODO: remove it + // default execution steps [END] // function definitions + function IsTaxFocused(index) { + return (currentTaxFocusIndex == index); + } + + function autoCapitalizeName(tax) { + tax.name = utils.autoCapitalizeWord(tax.name); + } + + function getAllTaxSettings() { + amTaxSettings.getAllTaxSettings().then(success).catch(failure); + + function success(res) { + if (angular.isArray(res)) + vm.taxSettings = res; + else + failure('No Taxes Found!'); + } + + function failure(err) { + console.warn(err); + } + } + + function saveTax(tax) { + if ((tax.name == '') || (tax.name == undefined)) { + utils.showSimpleToast('Please enter name of tax in order to save it!'); + return; + } + amTaxSettings.saveTaxSettings(tax).then(success).catch(failure); + + function success(res) { + if (res.ok) + utils.showSimpleToast(tax.name + ' settings saved successfully!'); + else + failure(); + } + + function failure(err) { + utils.showSimpleToast('Could not save ' + tax.name + ' settings. Try Again Later!'); + } + } + + function deleteTax(tax) { + amTaxSettings.deleteTaxSettings(tax).then(success).catch(failure); + + function success(res) { + if (res.ok) { + utils.showSimpleToast(tax.name + ' deleted successfully!'); + getAllTaxSettings(); + } else + failure(); + } + + function failure(err) { + utils.showSimpleToast('Could not delete ' + tax.name + ' settings. Try Again Later!'); + } + } + + function addNewTax() { + vm.taxSettings.push({ + name: '', + isTaxApplied: false, + inclusive: false, + percent: 0, + isForTreatments: true, + isForInventory: true + }); + currentTaxFocusIndex = vm.taxSettings.length - 1; + } + + function changeTaxTab(bool) { + vm.taxTab = bool; + } + function setAmAppDataPath(move) { var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); if (newPath) { @@ -700,60 +769,6 @@ } } - function getServiceTaxSettings() { - amSeTaxSettings.getServiceTaxSettings().then(success).catch(failure); - - function success(res) { - vm.sTaxSettings = res; - } - - function failure(err) { - vm.sTaxSettings = {}; - } - } - - function saveServiceTaxSettings() { - amSeTaxSettings.saveServiceTaxSettings(vm.sTaxSettings).then(success).catch(failure); - - function success(res) { - if (res.ok) - utils.showSimpleToast('Service Tax Settings Saved Successfully!'); - else - failure(); - } - - function failure(err) { - utils.showSimpleToast('Failed to save Service Tax Settings. Please Try Again!'); - } - } - - function getVatSettings() { - amSeTaxSettings.getVatSettings().then(success).catch(failure); - - function success(res) { - vm.vatSettings = res; - } - - function failure(err) { - vm.vatSettings = {}; - } - } - - function saveVatSettings() { - amSeTaxSettings.saveVatSettings(vm.vatSettings).then(success).catch(failure); - - function success(res) { - if (res.ok) - utils.showSimpleToast('VAT Settings Saved Successfully!'); - else - failure(); - } - - function failure(err) { - utils.showSimpleToast('Failed to save VAT Settings. Please Try Again!'); - } - } - function saveIvEmailSubject(es, reset) { if (vm.ivSettings.emailsubject == undefined || ((ivEmailSubject == vm.ivSettings.emailsubject) && !es)) return; diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index afda2b77..85f859d4 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -11,29 +11,22 @@ .am-tax-inline-input { border: 0; - border-bottom: 1px solid black; - width: 97%; + border-bottom: 1px solid #BDBDBD; + min-width: 40px; font-weight: 400; padding: 6px; outline: none; background: transparent; - color: rgba(30, 30, 30, 0.7); - -webkit-transition: background 1s ease 0s, box-shadow 1s ease 0s, border-bottom 1s ease 0s; - -moz-transition: background 1s ease 0s, box-shadow 1s ease 0s, border-bottom 1s ease 0s; - transition: background 1s ease 0s, box-shadow 1s ease 0s, border-bottom 1s ease 0s; - box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0); + color: #000; + transition: border-bottom 1s ease 0s; } .am-tax-inline-input:focus { - border-bottom: 0; - background: white; - box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0.3); + border-bottom: 1px solid #424242; } .am-tax-inline-input:hover { - border-bottom: 0; - background: white; - box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0.2); + border-bottom: 1px solid #9E9E9E; } .am-tax-adjust md-switch .md-bar { @@ -138,16 +131,6 @@ - - -
- Default Service Type -
- - {{state}} - -
-
@@ -202,52 +185,6 @@
- - -
- Service Tax -
- - Apply Service Tax - -
-
-
- Exclusive - - Inclusive -
-
-
- Service Tax (%): - -
-
-
-
- - -
- VAT -
- - Apply VAT - -
-
-
- Exclusive - - Inclusive -
-
-
- VAT (%): - -
-
-
-
@@ -266,10 +203,19 @@
+ + +
+ Default Service Type +
+ + {{state}} + +
+
-
@@ -425,5 +371,62 @@
+ + + + + +
+ add + Add New +
+ +
+ + delete + Delete Tax + +
+ + Apply Tax + +
+
+ +
+ Exclusive + + Inclusive +
+
+ Tax: + + % +
+
+
+

Applicable To:

+ Treatments + Parts +
+
+ +
+
+
+
\ No newline at end of file From cf3c3b864f6fdaa495116714cf73e8ef6cbe2a17 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 5 Aug 2016 11:33:00 +0530 Subject: [PATCH 23/91] Enhancement #143 | Fix: Cancel on Open Dialog > Cancel on Open dialog from Starting Screen should exit the app --- src/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index fdafb962..6612c895 100644 --- a/src/main.js +++ b/src/main.js @@ -120,9 +120,11 @@ function openCorrectFileUrl() { var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); - if (newPath) + if (newPath) { ammPreferences.storePreference('automint.userDataPath', newPath[0]); - restartApp(); + restartApp(); + } else + app.exit(0); } } From 1f34b2083083ce163dfd8e7707bde32e5a9a0ce8 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 5 Aug 2016 13:39:28 +0530 Subject: [PATCH 24/91] Separated membership edit module from Service CU --- src/app/app.states.js | 9 +- .../services/services-add.controller.js | 152 +---------------- .../services/services-edit.controller.js | 143 +--------------- .../dialog_membership.edit-template.html} | 0 .../tmpl/dialog_membership.edit.controller.js | 159 ++++++++++++++++++ 5 files changed, 165 insertions(+), 298 deletions(-) rename src/app/components/services/{service_membership.edit-template.html => tmpl/dialog_membership.edit-template.html} (100%) create mode 100644 src/app/components/services/tmpl/dialog_membership.edit.controller.js diff --git a/src/app/app.states.js b/src/app/app.states.js index a27a2554..3470d07a 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -381,13 +381,6 @@ } }); - function loadAddServiceDeps($ocLazyLoad) { - return $ocLazyLoad.load([ - 'material-datatable', - 'app/components/services/services.factory.js', - 'app/components/services/services-add.controller.js' - ]) - } function loadInUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'app/components/inventory/inventory-edit.controller.js' @@ -478,6 +471,7 @@ function loadSeCIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'app/components/services/tmpl/dialog_membership.edit.controller.js', 'app/components/services/tmpl/dialog_discount.controller.js', 'app/components/services/tmpl/dialog_partialpayment.controller.js', 'app/components/services/services-add.controller.js' @@ -493,6 +487,7 @@ function loadSeUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'app/components/services/tmpl/dialog_membership.edit.controller.js', 'app/components/services/tmpl/dialog_discount.controller.js', 'app/components/services/tmpl/dialog_partialpayment.controller.js', 'app/components/services/services-edit.controller.js' diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index b9671d56..ed110184 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -8,12 +8,9 @@ /// (function() { - angular.module('automintApp') - .controller('amCtrlSeCI', ServiceAddController) - .controller('amCtrlMeD', MembershipEditDialogController); + angular.module('automintApp').controller('amCtrlSeCI', ServiceAddController); ServiceAddController.$inject = ['$scope', '$state', '$q', '$log', '$filter', '$timeout', '$mdEditDialog', '$mdDialog', '$mdSidenav', 'utils', 'amServices']; - MembershipEditDialogController.$inject = ['$mdDialog', '$filter', 'membership', 'treatments']; /* ====== NOTE ======= @@ -950,7 +947,7 @@ $mdDialog.show({ controller: 'amCtrlMeD', controllerAs: 'vm', - templateUrl: 'app/components/services/service_membership.edit-template.html', + templateUrl: 'app/components/services/tmpl/dialog_membership.edit-template.html', parent: angular.element(document.body), targetEvent: event, locals: { @@ -2072,149 +2069,4 @@ } } } - - function MembershipEditDialogController($mdDialog, $filter, membership, treatments) { - var editMsVm = this; - editMsVm.treatment = { - details: '' - }; - editMsVm.membership = { - name: membership.name, - occurences: membership.occurences, - duration: membership.duration - }; - editMsVm.selectedTreatments = []; - editMsVm.treatments = treatments; - editMsVm.confirmDialog = confirmDialog; - editMsVm.treatmentQuerySearch = treatmentQuerySearch; - editMsVm.finalizeNewTreatment = finalizeNewTreatment; - editMsVm.updateTreatmentDetails = updateTreatmentDetails; - - loadDefaultOccDur(); - loadMemberships(); - - function loadMemberships() { - membership.treatments.forEach(iterateTreatments); - - function iterateTreatments(treatment) { - var found = $filter('filter')(editMsVm.treatments, { - name: treatment.name - }, true); - - if (found.length == 1) { - found[0].given.occurences = treatment.given.occurences; - found[0].given.duration = treatment.given.duration; - found[0].used.occurences = treatment.used.occurences; - found[0].used.duration = treatment.used.duration; - editMsVm.selectedTreatments.push(found[0]); - } else { - editMsVm.treatments.push(treatment); - editMsVm.selectedTreatments.push(editMsVm.treatments[editMsVm.treatments.length - 1]); - } - } - } - - function updateTreatmentDetails() { - var found = $filter('filter')(editMsVm.treatments, { - name: editMsVm.treatment.details - }); - if (found.length == 1 && found[0].name == editMsVm.treatment.details) { - editMsVm.treatment.occurences = found[0].given.occurences; - editMsVm.treatment.duration = found[0].given.duration; - } else { - editMsVm.treatment.occurences = editMsVm.membership.occurences - editMsVm.treatment.duration = editMsVm.membership.duration; - } - } - - // query search for treatments [autocomplete] - function treatmentQuerySearch() { - var tracker = $q.defer(); - var results = (editMsVm.treatment.details ? editMsVm.treatments.filter(createFilterForTreatments(editMsVm.treatment.details)) : editMsVm.treatments); - - return results; - } - - // create filter for users' query list - function createFilterForTreatments(query) { - var lcQuery = angular.lowercase(query); - return function filterFn(item) { - return (angular.lowercase(item.name).indexOf(lcQuery) === 0); - } - } - - function finalizeNewTreatment(btnClicked) { - editMsVm.treatment.details = editMsVm.treatment.details.trim(); - if (editMsVm.treatment.details != '') { - var found = $filter('filter')(editMsVm.treatments, { - name: editMsVm.treatment.details - }, true); - if (found.length == 1 && found[0].name == editMsVm.treatment.details) { - found[0].checked = true; - found[0].rate = editMsVm.treatment.rate; - found[0].given.duration = editMsVm.treatment.duration; - found[0].given.occurences = editMsVm.treatment.occurences; - found[0].used.duration = 0; - found[0].used.occurences = 0; - editMsVm.selectedTreatments.push(found[0]); - } else { - editMsVm.treatments.push({ - name: editMsVm.treatment.details, - rate: editMsVm.treatment.rate, - given: { - duration: editMsVm.treatment.duration, - occurences: editMsVm.treatment.occurences, - }, - used: { - duration: 0, - occurences: 0 - }, - checked: true - }); - editMsVm.selectedTreatments.push(editMsVm.treatments[editMsVm.treatments.length - 1]); - } - editMsVm.treatment.details = ''; - editMsVm.treatment.rate = {}; - editMsVm.treatment.occurences = editMsVm.membership.occurences; - editMsVm.treatment.duration = editMsVm.membership.duration; - angular.element('#new-treatment-details').find('input')[0].focus(); - } - if (btnClicked) - angular.element('#new-treatment-details').find('input')[0].focus(); - } - - function loadDefaultOccDur() { - editMsVm.treatments.forEach(iterateTreatments); - - function iterateTreatments(treatment) { - if (!treatment.given) { - treatment.given = { - occurences: membership.occurences, - duration: membership.duration - } - } - if (!treatment.used) { - treatment.used = { - occurences: 0, - duration: 0 - } - } - } - } - - function confirmDialog() { - membership.treatments = editMsVm.selectedTreatments; - membership.selectedTreatments = []; - membership.treatments.forEach(makeSelectedTreatments); - $mdDialog.hide(); - - function makeSelectedTreatments(treatment) { - if (membership.calculateTOccurenceLeft(treatment) != 0 && membership.calculateTDurationLeft(treatment) != 0) { - treatment.checked = true; - membership.selectedTreatments.push(treatment); - } else - treatment.checked = false; - } - } - } })(); \ No newline at end of file diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 3b3819b5..ab617e02 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -8,12 +8,9 @@ /// (function() { - angular.module('automintApp') - .controller('amCtrlSeUI', ServiceEditController) - .controller('amCtrlMeD', MembershipEditDialogController); + angular.module('automintApp').controller('amCtrlSeUI', ServiceEditController); ServiceEditController.$inject = ['$scope', '$state', '$q', '$log', '$filter', '$timeout', '$mdEditDialog', '$mdDialog', '$mdSidenav', 'utils', 'amServices']; - MembershipEditDialogController.$inject = ['$mdDialog', '$filter', 'membership', 'treatments']; /* ====== NOTE ======= @@ -1015,7 +1012,7 @@ $mdDialog.show({ controller: 'amCtrlMeD', controllerAs: 'vm', - templateUrl: 'app/components/services/service_membership.edit-template.html', + templateUrl: 'app/components/services/tmpl/dialog_membership.edit-template.html', parent: angular.element(document.body), targetEvent: event, locals: { @@ -2079,140 +2076,4 @@ } } } - - function MembershipEditDialogController($mdDialog, $filter, membership, treatments) { - var editMsVm = this; - - editMsVm.treatment = { - details: '', - rate: '' - }; - editMsVm.membership = { - name: membership.name, - occurences: membership.occurences, - duration: membership.duration - }; - editMsVm.selectedTreatments = []; - editMsVm.treatments = treatments; - editMsVm.confirmDialog = confirmDialog; - editMsVm.treatmentQuerySearch = treatmentQuerySearch; - editMsVm.finalizeNewTreatment = finalizeNewTreatment; - editMsVm.updateTreatmentDetails = updateTreatmentDetails; - - loadDefaultOccDur(); - loadMemberships(); - - function loadMemberships() { - membership.treatments.forEach(iterateTreatments); - - function iterateTreatments(treatment) { - var found = $filter('filter')(editMsVm.treatments, { - name: treatment.name - }, true); - - if (found.length == 1) { - found[0].given.occurences = treatment.given.occurences; - found[0].given.duration = treatment.given.duration; - found[0].used.occurences = treatment.used.occurences; - found[0].used.duration = treatment.used.duration; - editMsVm.selectedTreatments.push(found[0]); - } else { - editMsVm.treatments.push(treatment); - editMsVm.selectedTreatments.push(editMsVm.treatments[editMsVm.treatments.length - 1]); - } - } - } - - function updateTreatmentDetails() { - var found = $filter('filter')(editMsVm.treatments, { - name: editMsVm.treatment.details - }); - if (found.length == 1 && found[0].name == editMsVm.treatment.details) { - editMsVm.treatment.occurences = found[0].given.occurences; - editMsVm.treatment.duration = found[0].given.duration; - } else { - editMsVm.treatment.occurences = editMsVm.membership.occurences - editMsVm.treatment.duration = editMsVm.membership.duration; - } - } - - // query search for treatments [autocomplete] - function treatmentQuerySearch() { - var tracker = $q.defer(); - var results = (editMsVm.treatment.details ? editMsVm.treatments.filter(createFilterForTreatments(editMsVm.treatment.details)) : editMsVm.treatments); - - return results; - } - - // create filter for users' query list - function createFilterForTreatments(query) { - var lcQuery = angular.lowercase(query); - return function filterFn(item) { - return (angular.lowercase(item.name).indexOf(lcQuery) === 0); - } - } - - function finalizeNewTreatment(btnClicked) { - editMsVm.treatment.details = editMsVm.treatment.details.trim(); - if (editMsVm.treatment.details != '') { - var found = $filter('filter')(editMsVm.treatments, { - name: editMsVm.treatment.details - }, true); - if (found.length == 1 && found[0].name == editMsVm.treatment.details) { - found[0].checked = true; - found[0].rate = editMsVm.treatment.rate; - found[0].duration = editMsVm.treatment.duration; - found[0].occurences = editMsVm.treatment.occurences; - editMsVm.selectedTreatments.push(found[0]); - } else { - editMsVm.treatments.push({ - name: editMsVm.treatment.details, - duration: editMsVm.treatment.duration, - occurences: editMsVm.treatment.occurences, - checked: true - }); - editMsVm.selectedTreatments.push(editMsVm.treatments[editMsVm.treatments.length - 1]); - } - editMsVm.treatment.details = ''; - editMsVm.treatment.occurences = editMsVm.membership.occurences; - editMsVm.treatment.duration = editMsVm.membership.duration; - angular.element('#new-treatment-details').find('input')[0].focus(); - } - if (btnClicked) - angular.element('#new-treatment-details').find('input')[0].focus(); - } - - function loadDefaultOccDur() { - editMsVm.treatments.forEach(iterateTreatments); - - function iterateTreatments(treatment) { - if (!treatment.given) { - treatment.given = { - occurences: membership.occurences, - duration: membership.duration - } - } - if (!treatment.used) { - treatment.used = { - occurences: 0, - duration: 0 - } - } - } - } - - function confirmDialog() { - membership.treatments = editMsVm.selectedTreatments; - membership.selectedTreatments = []; - membership.treatments.forEach(makeSelectedTreatments); - $mdDialog.hide(); - - function makeSelectedTreatments(treatment) { - if (membership.calculateTOccurenceLeft(treatment) != 0 && membership.calculateTDurationLeft(treatment) != 0) { - treatment.checked = true; - membership.selectedTreatments.push(treatment); - } - } - } - } })(); \ No newline at end of file diff --git a/src/app/components/services/service_membership.edit-template.html b/src/app/components/services/tmpl/dialog_membership.edit-template.html similarity index 100% rename from src/app/components/services/service_membership.edit-template.html rename to src/app/components/services/tmpl/dialog_membership.edit-template.html diff --git a/src/app/components/services/tmpl/dialog_membership.edit.controller.js b/src/app/components/services/tmpl/dialog_membership.edit.controller.js new file mode 100644 index 00000000..43436628 --- /dev/null +++ b/src/app/components/services/tmpl/dialog_membership.edit.controller.js @@ -0,0 +1,159 @@ +/** + * Controller for Edit Membership Dialogbox + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlMeD', MembershipEditDialogController); + + MembershipEditDialogController.$inject = ['$mdDialog', '$filter', 'membership', 'treatments']; + + function MembershipEditDialogController($mdDialog, $filter, membership, treatments) { + var editMsVm = this; + editMsVm.treatment = { + details: '' + }; + editMsVm.membership = { + name: membership.name, + occurences: membership.occurences, + duration: membership.duration + }; + editMsVm.selectedTreatments = []; + editMsVm.treatments = treatments; + editMsVm.confirmDialog = confirmDialog; + editMsVm.treatmentQuerySearch = treatmentQuerySearch; + editMsVm.finalizeNewTreatment = finalizeNewTreatment; + editMsVm.updateTreatmentDetails = updateTreatmentDetails; + + loadDefaultOccDur(); + loadMemberships(); + + function loadMemberships() { + membership.treatments.forEach(iterateTreatments); + + function iterateTreatments(treatment) { + var found = $filter('filter')(editMsVm.treatments, { + name: treatment.name + }, true); + + if (found.length == 1) { + found[0].given.occurences = treatment.given.occurences; + found[0].given.duration = treatment.given.duration; + found[0].used.occurences = treatment.used.occurences; + found[0].used.duration = treatment.used.duration; + editMsVm.selectedTreatments.push(found[0]); + } else { + editMsVm.treatments.push(treatment); + editMsVm.selectedTreatments.push(editMsVm.treatments[editMsVm.treatments.length - 1]); + } + } + } + + function updateTreatmentDetails() { + var found = $filter('filter')(editMsVm.treatments, { + name: editMsVm.treatment.details + }); + if (found.length == 1 && found[0].name == editMsVm.treatment.details) { + editMsVm.treatment.occurences = found[0].given.occurences; + editMsVm.treatment.duration = found[0].given.duration; + } else { + editMsVm.treatment.occurences = editMsVm.membership.occurences + editMsVm.treatment.duration = editMsVm.membership.duration; + } + } + + // query search for treatments [autocomplete] + function treatmentQuerySearch() { + var tracker = $q.defer(); + var results = (editMsVm.treatment.details ? editMsVm.treatments.filter(createFilterForTreatments(editMsVm.treatment.details)) : editMsVm.treatments); + + return results; + } + + // create filter for users' query list + function createFilterForTreatments(query) { + var lcQuery = angular.lowercase(query); + return function filterFn(item) { + return (angular.lowercase(item.name).indexOf(lcQuery) === 0); + } + } + + function finalizeNewTreatment(btnClicked) { + editMsVm.treatment.details = editMsVm.treatment.details.trim(); + if (editMsVm.treatment.details != '') { + var found = $filter('filter')(editMsVm.treatments, { + name: editMsVm.treatment.details + }, true); + if (found.length == 1 && found[0].name == editMsVm.treatment.details) { + found[0].checked = true; + found[0].rate = editMsVm.treatment.rate; + found[0].given.duration = editMsVm.treatment.duration; + found[0].given.occurences = editMsVm.treatment.occurences; + found[0].used.duration = 0; + found[0].used.occurences = 0; + editMsVm.selectedTreatments.push(found[0]); + } else { + editMsVm.treatments.push({ + name: editMsVm.treatment.details, + rate: editMsVm.treatment.rate, + given: { + duration: editMsVm.treatment.duration, + occurences: editMsVm.treatment.occurences, + }, + used: { + duration: 0, + occurences: 0 + }, + checked: true + }); + editMsVm.selectedTreatments.push(editMsVm.treatments[editMsVm.treatments.length - 1]); + } + editMsVm.treatment.details = ''; + editMsVm.treatment.rate = {}; + editMsVm.treatment.occurences = editMsVm.membership.occurences; + editMsVm.treatment.duration = editMsVm.membership.duration; + angular.element('#new-treatment-details').find('input')[0].focus(); + } + if (btnClicked) + angular.element('#new-treatment-details').find('input')[0].focus(); + } + + function loadDefaultOccDur() { + editMsVm.treatments.forEach(iterateTreatments); + + function iterateTreatments(treatment) { + if (!treatment.given) { + treatment.given = { + occurences: membership.occurences, + duration: membership.duration + } + } + if (!treatment.used) { + treatment.used = { + occurences: 0, + duration: 0 + } + } + } + } + + function confirmDialog() { + membership.treatments = editMsVm.selectedTreatments; + membership.selectedTreatments = []; + membership.treatments.forEach(makeSelectedTreatments); + $mdDialog.hide(); + + function makeSelectedTreatments(treatment) { + if (membership.calculateTOccurenceLeft(treatment) != 0 && membership.calculateTDurationLeft(treatment) != 0) { + treatment.checked = true; + membership.selectedTreatments.push(treatment); + } else + treatment.checked = false; + } + } + } +})(); \ No newline at end of file From 09aa23122e9f9f48ecbad03a2bb6688aabf8f12a Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 10:44:33 +0530 Subject: [PATCH 25/91] Feature #192 | Editable Taxes [complete] --- .../services/services-add.controller.js | 560 +++++++++-------- .../services/services-edit.controller.js | 583 ++++++++++-------- .../components/services/services.factory.js | 140 +++-- src/app/components/services/services_add.html | 120 +--- .../settings/settings.controller.js | 2 +- 5 files changed, 738 insertions(+), 667 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index ed110184..651cc724 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -58,15 +58,15 @@ estimateno: 1, problems: [], taxes: { - serviceTax: 0, - vat: 0 + inventory: 0, + treatments: 0 }, subtotal: 0 }; vm.problem = { details: '', rate: '', - tax: '', + tax: {}, amount: '' }; vm.servicestatus = true; @@ -85,7 +85,7 @@ vm.inventory = { name: '', rate: '', - tax: '', + tax: {}, qty: 1, amount: '', total: '' @@ -106,7 +106,8 @@ treatment: 0, part: 0, total: 0 - } + }; + vm.taxSettings = []; // named assignments to handle behaviour of UI elements vm.redirect = { @@ -139,11 +140,9 @@ vm.navigateToSubscribeMembership = navigateToSubscribeMembership; vm.goBack = goBack; vm.changeProblemRate = changeProblemRate; - vm.changeServiceTax = changeServiceTax; - vm.OnServiceTaxEnabledChange = OnServiceTaxEnabledChange; + vm.OnTaxEnabledChange = OnTaxEnabledChange; vm.convertPbToTitleCase = convertPbToTitleCase; vm.convertInToTitleCase = convertInToTitleCase; - vm.changeVat = changeVat; vm.onInventorySelected = onInventorySelected; vm.onInventoryDeselected = onInventoryDeselected; vm.changeInventoryRate = changeInventoryRate; @@ -169,14 +168,9 @@ vm.IsMembershipEnabled = IsMembershipEnabled; vm.IsPackageEnabled = IsPackageEnabled; vm.convertVehicleTypeToAF = convertVehicleTypeToAF; - vm.IsTreatmentAmountEditable = IsTreatmentAmountEditable; - vm.IsTreatmentAmountText = IsTreatmentAmountText; vm.IsPackageEnabled = IsPackageEnabled; vm.changeDisplayAsList = changeDisplayAsList; - vm.IsTreatmentRateDisplayed = IsTreatmentRateDisplayed; vm.changeInventoryAsList = changeInventoryAsList; - vm.IsInventoryTotalEditable = IsInventoryTotalEditable; - vm.IsInventoryTotalText = IsInventoryTotalText; vm.IsServiceStateSelected = IsServiceStateSelected; vm.selectServiceState = selectServiceState; vm.WhichServiceStateEnabled = WhichServiceStateEnabled; @@ -226,11 +220,84 @@ function calculate() { calculateSubtotal(); calculateTotalDiscount(); - calculateServiceTax(); - calculateVat(); + calculateTaxes(); calculateCost(); } + function calculateTaxes() { + vm.taxSettings.forEach(iterateTaxes); + vm.service.problems.forEach(iterateProblems); + iterateProblem(vm.problem); + if (vm.packages) + vm.packages.forEach(iteratePackages); + vm.selectedInventories.forEach(iterateProblems); + iterateProblem(vm.inventory); + if (vm.isDiscountApplied) + vm.taxSettings.forEach(iterateTaxesAfter); + + function iterateTaxesAfter(tax) { + if (tax.isTaxApplied && tax.isForTreatments) + tax.tax = (dTreatment * tax.percent / 100); + if (tax.isTaxApplied && tax.isForInventory) + tax.tax = (dInventory * tax.percent / 100); + tax.tax = (tax.tax % 1 != 0) ? parseFloat(tax.tax.toFixed(2)) : parseInt(tax.tax); + } + + function iterateTaxes(tax) { + tax.tax = 0; + } + + function iteratePackages(package) { + if (!package.checked) + return; + package.selectedTreatments.forEach(iterateProblems); + } + + function iterateProblems(problem) { + if (!problem.checked) + return; + if (problem.tax) { + Object.keys(problem.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += problem.tax[tax]; + } + } + } + } + + function iterateProblem(problem) { + if (problem.tax) { + Object.keys(problem.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + if (problem.tax[tax] > 0) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += problem.tax[tax]; + } + } + } + } + } + } + function OnDiscountStateChange() { calculateDiscount(); calculate(); @@ -346,7 +413,14 @@ } function IsSubtotalEnabled() { - return (vm.isDiscountApplied || vm.isRoundOffVal || (vm.sTaxSettings && vm.sTaxSettings.applyTax) || (vm.vatSettings && vm.vatSettings.applyTax)); + var isTaxEnabled = false; + vm.taxSettings.forEach(iterateTaxes); + return (vm.isDiscountApplied || vm.isRoundOffVal || isTaxEnabled); + + function iterateTaxes(tax) { + if (tax.isTaxApplied) + isTaxEnabled = true; + } } function isRoD() { @@ -444,23 +518,11 @@ $('#ami-display-as').focus(); } - function IsInventoryTotalText() { - return (vm.vatSettings && !vm.vatSettings.inclusive && vm.vatSettings.applyTax); - } - - function IsInventoryTotalEditable() { - return (vm.vatSettings && (vm.vatSettings.inclusive || !vm.vatSettings.applyTax)); - } - function changeInventoryAsList(bool) { vm.displayInventoriesAsList = bool; setTimeout(focusShowAllInventoriesButton, 300); } - function IsTreatmentRateDisplayed() { - return (vm.sTaxSettings && vm.sTaxSettings.applyTax && !vm.sTaxSettings.inclusive); - } - function changeDisplayAsList(bool) { vm.displayTreatmentAsList = bool; setTimeout(focusShowAllTreatmentsButton, 300); @@ -470,14 +532,6 @@ return (vm.packages.length < 1); } - function IsTreatmentAmountText() { - return (vm.sTaxSettings && !vm.sTaxSettings.inclusive && vm.sTaxSettings.applyTax); - } - - function IsTreatmentAmountEditable() { - return (vm.sTaxSettings && (vm.sTaxSettings.inclusive || !vm.sTaxSettings.applyTax)); - } - function convertVehicleTypeToAF() { return (vm.vehicle.type.toLowerCase().replace(' ', '-')); } @@ -623,25 +677,6 @@ changeInventoryRate(inventory); } - function calculateVat() { - if (vm.isDiscountApplied && (vm.discount.total > 0)) { - vm.service.taxes.vat = dInventoryTax; - return; - } - var totalTax = 0.0; - if ((vm.vatSettings && vm.vatSettings.applyTax) && vm.inventory.tax && (vm.inventory.amount > 0)) - totalTax += parseFloat(vm.inventory.tax * vm.inventory.qty); - vm.selectedInventories.forEach(iterateInventories); - totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : totalTax; - vm.service.taxes.vat = totalTax; - - function iterateInventories(element) { - if ((vm.vatSettings && !vm.vatSettings.applyTax) || !element.tax || !element.checked) - return; - totalTax += parseFloat(element.tax * element.qty); - } - } - function getInventoriesSettings() { amServices.getInventoriesSettings().then(success).catch(failure); @@ -713,37 +748,36 @@ name: vm.inventory.name }, true); if (found.length == 1) { - var rate; - if (vm.vatSettings.applyTax) - rate = (vm.vatSettings.inclusive) ? found[0].amount : found[0].rate; - else - rate = found[0].rate; - vm.inventory.amount = (rate == '' || rate == undefined ? vm.inventory.amount : rate); - if (vm.vatSettings.applyTax) { - if (vm.vatSettings.inclusive) { - vm.inventory.rate = (vm.inventory.amount * 100) / (vm.vatSettings.tax + 100); - vm.inventory.tax = (vm.inventory.rate * vm.vatSettings.tax / 100); - } else { - vm.inventory.rate = (rate == '' || rate == undefined ? vm.inventory.rate : rate); - vm.inventory.tax = (vm.inventory.rate * vm.vatSettings.tax / 100); - vm.inventory.amount = Math.round(vm.inventory.rate + vm.inventory.tax); - } - } else { - if (vm.vatSettings.inclusive) - vm.inventory.rate = (rate == '' || rate == undefined ? vm.inventory.rate : rate); - else { - vm.inventory.amount = (rate == '' || rate == undefined ? vm.inventory.amount : rate); - vm.inventory.rate = (rate == '' || rate == undefined ? vm.inventory.rate : rate); - } - } + var taxable = vm.inventory.amount; + vm.inventory.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + vm.inventory.rate = taxable; vm.inventory.total = vm.inventory.amount * vm.inventory.qty; vm.inventory.checked = true; calculate(); + + function iterateTaxes(tax) { + if (!tax.isForInventory) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + vm.inventory.rate = (taxable * 100) / (tax.percent + 100); + temptax = (vm.inventory.rate * tax.percent / 100); + vm.inventory.tax[tax.name] = temptax; + taxable = vm.inventory.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + vm.inventory.tax[tax.name] = temptax; + } + } + } } } else { vm.inventory.checked = false; vm.inventory.rate = ''; - vm.inventory.tax = 0; + vm.inventory.tax = {}; vm.inventory.amount = ''; vm.inventory.total = ''; } @@ -778,26 +812,35 @@ function success(res) { vm.inventories = res; - getVatSettings(); + getInventoryTax(); } function failure(err) { vm.inventories = []; - getVatSettings(); + getInventoryTax(); } } - function getVatSettings() { - amServices.getVatSettings().then(success).catch(failure); + function getInventoryTax() { + amServices.getInventoryTax().then(success).catch(failure); function success(res) { - vm.vatSettings = res; - vm.orgApplyVat = res.applyTax; - changeVat(); + res.forEach(iterateTaxes); + OnTaxEnabledChange(); + + function iterateTaxes(tax) { + tax.tax = 0; + var found = $filter('filter')(vm.taxSettings, { + name: tax.name + }, true); + + if (found.length == 0) + vm.taxSettings.push(tax); + } } function failure(err) { - vm.vatSettings = {}; + console.warn(err); } } @@ -805,62 +848,52 @@ vm.inventory.name = utils.autoCapitalizeWord(vm.inventory.name); } - function changeVat() { - vm.inventories.forEach(iterateInventories); - changeInventoryRate(vm.inventory, true); - - function iterateInventories(inventory) { - changeInventoryRate(inventory, true); - } - } - function changeQty(inventory) { - inventory.total = ((vm.vatSettings.applyTax ? inventory.amount : inventory.rate) * inventory.qty); + inventory.total = (inventory.amount * inventory.qty); calculate(); - - if (!vm.vatSettings.applyTax) - inventory.amount = inventory.rate; } function changeInventoryRate(inventory, force) { - if (vm.vatSettings.applyTax) { - if (vm.vatSettings.inclusive) { - inventory.rate = (inventory.amount * 100) / (vm.vatSettings.tax + 100); - inventory.tax = (inventory.rate * vm.vatSettings.tax / 100); - } else { - if (!inventory.rate) - inventory.rate = inventory.amount; - inventory.tax = (inventory.rate * vm.vatSettings.tax / 100); - inventory.amount = inventory.rate + inventory.tax; - } - } else if (force) { - if (vm.vatSettings.inclusive) - inventory.rate = inventory.amount; - else - inventory.amount = inventory.rate; - } else - inventory.rate = inventory.amount; + var taxable = inventory.amount; + inventory.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + inventory.rate = taxable; changeQty(inventory); calculate(); + + function iterateTaxes(tax) { + if (!tax.isForInventory) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + inventory.rate = (taxable * 100) / (tax.percent + 100); + temptax = (inventory.rate * tax.percent / 100); + inventory.tax[tax.name] = temptax; + taxable = inventory.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + inventory.tax[tax.name] = temptax; + } + } + } } function convertPbToTitleCase() { vm.problem.details = utils.autoCapitalizeWord(vm.problem.details); } - function OnServiceTaxEnabledChange() { + function OnTaxEnabledChange() { vm.service.problems.forEach(iterateProblems); + changeProblemRate(vm.problem, true); calculatePackageTax(); + vm.inventories.forEach(iterateInventories); + changeInventoryRate(vm.inventory, true); - function iterateProblems(problem) { - changeProblemRate(problem, true); + function iterateInventories(inventory) { + changeInventoryRate(inventory, true); } - } - - function changeServiceTax() { - vm.service.problems.forEach(iterateProblems); - changeProblemRate(vm.problem, true); - calculatePackageTax(); function iterateProblems(problem) { changeProblemRate(problem, true); @@ -868,39 +901,51 @@ } function changeProblemRate(problem, force) { - if (vm.sTaxSettings.applyTax) { - if (vm.sTaxSettings.inclusive) { - problem.rate = (problem.amount * 100) / (vm.sTaxSettings.tax + 100); - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - } else { - if (!problem.rate) - problem.rate = problem.amount; - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - problem.amount = problem.rate + problem.tax; - } - } else if (force) { - if (vm.sTaxSettings.inclusive) - problem.rate = problem.amount; - else - problem.amount = problem.rate; - } else - problem.rate = problem.amount; + var taxable = problem.amount; + problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + problem.rate = parseFloat(taxable); calculate(); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (problem.rate * tax.percent / 100); + problem.tax[tax.name] = temptax; + taxable = problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + problem.tax[tax.name] = temptax; + } + } + } } - function getServiceTaxSettings() { - amServices.getServiceTaxSettings().then(success).catch(failure); + function getTreatmentsTax() { + amServices.getTreatmentsTax().then(success).catch(failure); function success(res) { - vm.sTaxSettings = res; - vm.orgApplyTax = res.applyTax; - changeServiceTax(); + res.forEach(iterateTaxes); getPackages(); - OnServiceTaxEnabledChange(); + OnTaxEnabledChange(); + + function iterateTaxes(tax) { + tax.tax = 0; + var found = $filter('filter')(vm.taxSettings, { + name: tax.name + }, true); + + if (found.length == 0) + vm.taxSettings.push(tax); + } } function failure(err) { - vm.sTaxSettings = {}; getPackages(); } } @@ -1177,23 +1222,29 @@ } } - function changeTreatmentTax(treatment, force) { + function changeTreatmentTax(problem, force) { var cvt = vm.vehicle.type.toLowerCase().replace(' ', '-'); - if (vm.sTaxSettings.applyTax) { - if (treatment.tax == undefined) - treatment.tax = {}; - if (vm.sTaxSettings.inclusive) { - treatment.rate[cvt] = (treatment.amount[cvt] * 100) / (vm.sTaxSettings.tax + 100); - treatment.tax[cvt] = (treatment.rate[cvt] * vm.sTaxSettings.tax / 100); - } else { - treatment.tax[cvt] = (treatment.rate[cvt] * vm.sTaxSettings.tax / 100); - treatment.amount[cvt] = treatment.rate[cvt] + treatment.tax[cvt]; + var taxable = problem.amount; + problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + problem.rate = parseFloat(taxable); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (problem.rate * tax.percent / 100); + problem.tax[tax.name] = temptax; + taxable = problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + problem.tax[tax.name] = temptax; + } } - } else if (force) { - if (vm.sTaxSettings.inclusive) - treatment.rate[cvt] = treatment.amount[cvt]; - else - treatment.amount[cvt] = treatment.rate[cvt]; } } @@ -1562,19 +1613,27 @@ }); if (found.length == 1) { var rate = found[0].rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; - if (vm.sTaxSettings.applyTax) { - if (vm.sTaxSettings.inclusive) { - problem.amount = (rate == '' || rate == undefined ? problem.amount : rate); - problem.rate = (problem.amount * 100) / (vm.sTaxSettings.tax + 100); - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - } else { - problem.rate = (rate == '' || rate == undefined ? problem.rate : rate); - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - problem.amount = problem.rate + problem.tax; + var taxable = problem.amount; + problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + problem.rate = parseFloat(taxable); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (problem.rate * tax.percent / 100); + problem.tax[tax.name] = temptax; + taxable = problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + problem.tax[tax.name] = temptax; + } } - } else { - problem.rate = (rate == '' || rate == undefined ? problem.rate : rate); - problem.amount = (rate == '' || rate == undefined ? problem.rate : rate); } } } @@ -1604,7 +1663,7 @@ // load treatment list into problem list function loadTreatmentIntoProblems() { vm.treatments.forEach(iterateTreatment); - getServiceTaxSettings(); + getTreatmentsTax(); function iterateTreatment(treatment) { vm.service.problems.push({ @@ -1645,6 +1704,8 @@ } function populateRoD() { + var isTreatmentTaxed = false, isInventoryTaxed = false, treatmentTaxPercent = 0, inventoryTaxPercent = 0; + vm.taxSettings.forEach(iterateTaxes); if (vm.isDiscountApplied) { var noTerminate = true, tp = 0.5, ip = 1 - tp; if (treatmentTotal == 0) { @@ -1655,7 +1716,7 @@ tp = 1; } while (noTerminate) { - var xt = (tp * vm.service.cost * 100) / (vm.sTaxSettings.tax + 100); + var xt = (tp * vm.service.cost * 100) / (treatmentTaxPercent + 100); if (treatmentTotal < xt) { tp -= 0.1; ip = 1 - tp; @@ -1664,7 +1725,7 @@ continue; } vm.discount.treatment = treatmentTotal - xt; - var xi = (ip * vm.service.cost * 100) / (vm.vatSettings.tax + 100); + var xi = (ip * vm.service.cost * 100) / (inventoryTaxPercent + 100); if (inventoryTotal < xi) { ip -= 0.1; tp = 1 - ip; @@ -1681,6 +1742,17 @@ calculate(); } else if (vm.isRoundOffVal) vm.roundedOffVal = vm.service.cost - serviceTcRo; + + function iterateTaxes(tax) { + if (tax.isTaxApplied && tax.isForTreatments) { + isTreatmentTaxed = true; + treatmentTaxPercent += tax.percent; + } + if (tax.isTaxApplied && tax.isForInventory) { + isInventoryTaxed = true; + inventoryTaxPercent += tax.percent; + } + } } function calculateRoundOff(isRoundOffManual) { @@ -1717,13 +1789,27 @@ function calculateTotalDiscount() { if (!vm.isDiscountApplied) return; + var isTreatmentTaxed = false, isInventoryTaxed = false, treatmentTaxPercent = 0, inventoryTaxPercent = 0; dTreatment = treatmentTotal - vm.discount.treatment; dInventory = inventoryTotal - vm.discount.part; - dTreatmentTax = (vm.sTaxSettings.applyTax) ? (dTreatment * vm.sTaxSettings.tax / 100) : 0; - dInventoryTax = (vm.vatSettings.applyTax) ? (dInventory * vm.vatSettings.tax / 100) : 0; + vm.taxSettings.forEach(iterateTaxes); + + dTreatmentTax = (isTreatmentTaxed) ? (dTreatment * treatmentTaxPercent / 100) : 0; + dInventoryTax = (isInventoryTaxed) ? (dInventory * inventoryTaxPercent / 100) : 0; dTreatmentTax = (dTreatmentTax % 1 != 0) ? parseFloat(dTreatmentTax.toFixed(2)) : dTreatmentTax; dInventoryTax = (dInventoryTax % 1 != 0) ? parseFloat(dInventoryTax.toFixed(2)) : dInventoryTax; + + function iterateTaxes(tax) { + if (tax.isTaxApplied && tax.isForTreatments) { + isTreatmentTaxed = true; + treatmentTaxPercent += tax.percent; + } + if (tax.isTaxApplied && tax.isForInventory) { + isInventoryTaxed = true; + inventoryTaxPercent += tax.percent; + } + } } function changeForceStopCalCost(bool) { @@ -1733,9 +1819,10 @@ function calculateCost() { if (forceStopCalCost) return; - var totalCost = 0; + var totalCost = 0, taxes = 0; var discountedSubtotal = parseFloat(dTreatment + dTreatmentTax) + parseFloat(dInventory + dInventoryTax); - totalCost = (vm.isDiscountApplied && (vm.discount.total > 0)) ? Math.round(discountedSubtotal) : (parseFloat(vm.service.subtotal) + parseFloat(vm.service.taxes.serviceTax) + parseFloat(vm.service.taxes.vat)); + vm.taxSettings.forEach(iterateTaxes); + totalCost = (vm.isDiscountApplied && (vm.discount.total > 0)) ? Math.round(discountedSubtotal) : (parseFloat(vm.service.subtotal) + parseFloat(taxes)); serviceTcDc = totalCost; serviceTcRo = totalCost; if (vm.isRoundOffVal) { @@ -1745,38 +1832,9 @@ totalCost = (totalCost % 1 != 0) ? parseFloat(totalCost.toFixed(2)) : totalCost; totalCost = (totalCost % 1).toFixed(2) == 0.00 ? Math.round(totalCost) : totalCost; vm.service.cost = parseFloat(totalCost); - } - - function calculateServiceTax() { - if (vm.isDiscountApplied && (vm.discount.total > 0)) { - vm.service.taxes.serviceTax = dTreatmentTax; - return; - } - var totalTax = 0.0; - if ((vm.problem.amount > 0) && (vm.sTaxSettings && vm.sTaxSettings.applyTax) && vm.problem.tax) - totalTax += parseFloat(vm.problem.tax); - vm.service.problems.forEach(iterateProblems); - if (vm.serviceType == vm.serviceTypeList[1]) - vm.packages.forEach(iteratePackages); - totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : parseInt(totalTax); - vm.service.taxes.serviceTax = parseFloat(totalTax); - - function iterateProblems(problem) { - if ((vm.sTaxSettings && !vm.sTaxSettings.applyTax) || !problem.tax || !problem.checked) - return; - totalTax += parseFloat(problem.tax); - } - - function iteratePackages(package) { - if (!package.checked) - return; - package.selectedTreatments.forEach(ipt); - } - function ipt(treatment) { - if ((vm.sTaxSettings && !vm.sTaxSettings.applyTax) || !treatment.tax) - return; - totalTax += parseFloat(treatment.tax[vm.vehicle.type.toLowerCase().replace(' ', '-')]); + function iterateTaxes(tax) { + taxes += tax.tax; } } @@ -1814,7 +1872,7 @@ vm.problem.details = ''; vm.problem.amount = ''; vm.problem.rate = ''; - vm.problem.tax = ''; + vm.problem.tax = {}; calculate(); if (isFromAutocomplete || foundExisting.length != 0) vm.problemFocusIndex = (foundExisting.length == 0) ? vm.selectedProblems.length - 1 : vm.selectedProblems.indexOf(foundExisting[0]); @@ -1835,27 +1893,34 @@ if (found.length == 1) { var rate = found[0].rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; vm.problem.amount = (rate == '' || rate == undefined ? vm.problem.amount : rate); - if (vm.sTaxSettings.applyTax) { - if (vm.sTaxSettings.inclusive) { - vm.problem.rate = (vm.problem.amount * 100) / (vm.sTaxSettings.tax + 100); - vm.problem.tax = (vm.problem.rate * vm.sTaxSettings.tax / 100); - } else { - vm.problem.rate = (rate == '' || rate == undefined ? vm.problem.rate : rate); - vm.problem.tax = (vm.problem.rate * vm.sTaxSettings.tax / 100); - vm.problem.amount = vm.problem.rate + vm.problem.tax; - } - } else { - if (vm.sTaxSettings.inclusive) - vm.problem.rate = (rate == '' || rate == undefined ? vm.problem.rate : rate); - else - vm.problem.amount = (rate == '' || rate == undefined ? vm.problem.amount : rate); - } + var taxable = vm.problem.amount; + vm.problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + vm.problem.rate = taxable; vm.problem.checked = true; calculate(); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + vm.problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (vm.problem.rate * tax.percent / 100); + vm.problem.tax[tax.name] = temptax; + taxable = vm.problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + vm.problem.tax[tax.name] = temptax; + } + } + } } } else { vm.problem.rate = ''; - vm.problem.tax = 0; + vm.problem.tax = {}; vm.problem.amount = ''; vm.problem.checked = false; } @@ -1932,20 +1997,8 @@ vm.service['roundoff'] = vm.roundedOffVal; } vm.service.problems.forEach(iterateProblems); - if (vm.sTaxSettings != undefined) { - vm.service.serviceTax = { - applyTax: vm.sTaxSettings.applyTax, - taxIncType: (vm.sTaxSettings.inclusive) ? 'inclusive' : 'exclusive', - tax: vm.sTaxSettings.tax - }; - } - if (vm.vatSettings != undefined) { - vm.service.vat = { - applyTax: vm.vatSettings.applyTax, - taxIncType: (vm.vatSettings.inclusive) ? 'inclusive' : 'exclusive', - tax: vm.vatSettings.tax - } - } + vm.service.taxes = {}; + vm.taxSettings.forEach(iterateTaxes); if (vm.inventory.name) finalizeNewInventory(); vm.selectedInventories.forEach(iterateInventories); @@ -1966,6 +2019,17 @@ } amServices.saveService(vm.user, vm.vehicle, vm.service, options).then(success).catch(failure); + function iterateTaxes(tax) { + vm.service.taxes[tax.name] = { + tax: tax.tax, + type: (tax.inclusive) ? "inclusive" : "exclusive", + isTaxApplied: tax.isTaxApplied, + isForTreatments: tax.isForTreatments, + isForInventory: tax.isForInventory, + percent: tax.percent + } + } + function addMsToService(membership) { if (!membership.checked) return; @@ -1991,16 +2055,12 @@ } function iterateProblems(problem) { - if (!vm.sTaxSettings || (vm.sTaxSettings && !vm.sTaxSettings.applyTax && vm.sTaxSettings.inclusive)) - problem.rate = problem.amount; delete problem.checked; delete problem['amount']; delete problem['$$hashKey']; } function iterateInventories(inventory) { - if (!vm.vatSettings || (vm.vatSettings && vm.vatSettings.applyTax && vm.vatSettings.inclusive)) - inventory.rate = inventory.amount; delete inventory.checked; delete inventory['total']; delete inventory['amount']; diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index ab617e02..fa6719af 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -60,15 +60,15 @@ estimateno: undefined, problems: [], taxes: { - serviceTax: 0, - vat: 0 + inventory: 0, + treatments: 0 }, subtotal: 0 }; vm.problem = { details: '', rate: '', - tax: '', + tax: {}, amount: '' }; vm.manufacturers = []; @@ -85,7 +85,7 @@ vm.inventory = { name: '', rate: '', - tax: '', + tax: {}, qty: 1, amount: '', total: '' @@ -106,7 +106,8 @@ treatment: 0, part: 0, total: 0 - } + }; + vm.taxSettings = []; // named assignments to handle behaviour of UI elements vm.redirect = { @@ -133,18 +134,15 @@ vm.navigateToSubscribeMembership = navigateToSubscribeMembership; vm.goBack = goBack; vm.changeProblemRate = changeProblemRate; - vm.changeServiceTax = changeServiceTax; - vm.OnServiceTaxEnabledChange = OnServiceTaxEnabledChange; + vm.OnTaxEnabledChange = OnTaxEnabledChange; vm.convertPbToTitleCase = convertPbToTitleCase; vm.convertInToTitleCase = convertInToTitleCase; - vm.changeVat = changeVat; vm.onInventorySelected = onInventorySelected; vm.onInventoryDeselected = onInventoryDeselected; vm.changeInventoryRate = changeInventoryRate; vm.inventoryQuerySearch = inventoryQuerySearch; vm.updateInventoryDetails = updateInventoryDetails; vm.finalizeNewInventory = finalizeNewInventory; - vm.calculateVat = calculateVat; vm.changeQty = changeQty; vm.changeInventoryTotal = changeInventoryTotal; vm.populateRoD = populateRoD; @@ -164,14 +162,9 @@ vm.IsMembershipEnabled = IsMembershipEnabled; vm.IsPackageEnabled = IsPackageEnabled; vm.convertVehicleTypeToAF = convertVehicleTypeToAF; - vm.IsTreatmentAmountEditable = IsTreatmentAmountEditable; - vm.IsTreatmentAmountText = IsTreatmentAmountText; vm.IsPackageEnabled = IsPackageEnabled; vm.changeDisplayAsList = changeDisplayAsList; - vm.IsTreatmentRateDisplayed = IsTreatmentRateDisplayed; vm.changeInventoryAsList = changeInventoryAsList; - vm.IsInventoryTotalEditable = IsInventoryTotalEditable; - vm.IsInventoryTotalText = IsInventoryTotalText; vm.IsServiceStateSelected = IsServiceStateSelected; vm.selectServiceState = selectServiceState; vm.WhichServiceStateEnabled = WhichServiceStateEnabled; @@ -212,11 +205,84 @@ function calculate() { calculateSubtotal(); calculateTotalDiscount(); - calculateServiceTax(); - calculateVat(); + calculateTaxes(); calculateCost(); } + function calculateTaxes() { + vm.taxSettings.forEach(iterateTaxes); + vm.service.problems.forEach(iterateProblems); + iterateProblem(vm.problem); + if (vm.packages) + vm.packages.forEach(iteratePackages); + vm.selectedInventories.forEach(iterateProblems); + iterateProblem(vm.inventory); + if (vm.isDiscountApplied) + vm.taxSettings.forEach(iterateTaxesAfter); + + function iterateTaxesAfter(tax) { + if (tax.isTaxApplied && tax.isForTreatments) + tax.tax = (dTreatment * tax.percent / 100); + if (tax.isTaxApplied && tax.isForInventory) + tax.tax = (dInventory * tax.percent / 100); + tax.tax = (tax.tax % 1 != 0) ? parseFloat(tax.tax.toFixed(2)) : parseInt(tax.tax); + } + + function iterateTaxes(tax) { + tax.tax = 0; + } + + function iteratePackages(package) { + if (!package.checked) + return; + package.selectedTreatments.forEach(iterateProblems); + } + + function iterateProblems(problem) { + if (!problem.checked) + return; + if (problem.tax) { + Object.keys(problem.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += problem.tax[tax]; + } + } + } + } + + function iterateProblem(problem) { + if (problem.tax) { + Object.keys(problem.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + if (problem.tax[tax] > 0) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += problem.tax[tax]; + } + } + } + } + } + } + function OnDiscountStateChange() { calculateDiscount(); calculate(); @@ -332,7 +398,14 @@ } function IsSubtotalEnabled() { - return (vm.isDiscountApplied || vm.isRoundOffVal || (vm.sTaxSettings && vm.sTaxSettings.applyTax) || (vm.vatSettings && vm.vatSettings.applyTax)); + var isTaxEnabled = false; + vm.taxSettings.forEach(iterateTaxes); + return (vm.isDiscountApplied || vm.isRoundOffVal || isTaxEnabled); + + function iterateTaxes(tax) { + if (tax.isTaxApplied) + isTaxEnabled = true; + } } function isRoD() { @@ -457,22 +530,10 @@ return (vm.service.state == state); } - function IsInventoryTotalText() { - return (vm.vatSettings && !vm.vatSettings.inclusive && vm.vatSettings.applyTax); - } - - function IsInventoryTotalEditable() { - return (vm.vatSettings && (vm.vatSettings.inclusive || !vm.vatSettings.applyTax)); - } - function changeInventoryAsList(bool) { vm.displayInventoriesAsList = bool; } - function IsTreatmentRateDisplayed() { - return (vm.sTaxSettings && vm.sTaxSettings.applyTax && !vm.sTaxSettings.inclusive); - } - function changeDisplayAsList(bool) { vm.displayTreatmentAsList = bool; } @@ -481,14 +542,6 @@ return (vm.packages.length < 1); } - function IsTreatmentAmountText() { - return (vm.sTaxSettings && !vm.sTaxSettings.inclusive && vm.sTaxSettings.applyTax); - } - - function IsTreatmentAmountEditable() { - return (vm.sTaxSettings && (vm.sTaxSettings.inclusive || !vm.sTaxSettings.applyTax)); - } - function convertVehicleTypeToAF() { return (vm.vehicle.type.toLowerCase().replace(' ', '-')); } @@ -634,25 +687,6 @@ changeInventoryRate(inventory); } - function calculateVat() { - if (vm.isDiscountApplied && (vm.discount.total > 0)) { - vm.service.taxes.vat = dInventoryTax; - return; - } - var totalTax = 0.0; - if ((vm.vatSettings && vm.vatSettings.applyTax) && vm.inventory.tax && (vm.inventory.amount > 0)) - totalTax += parseFloat(vm.inventory.tax * vm.inventory.qty); - vm.selectedInventories.forEach(iterateInventories); - totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : totalTax; - vm.service.taxes.vat = totalTax; - - function iterateInventories(element) { - if ((vm.vatSettings && !vm.vatSettings.applyTax) || !element.tax || !element.checked) - return; - totalTax += parseFloat(element.tax * (element.qty ? element.qty : 1)); - } - } - function finalizeNewInventory(isFromAutocomplete) { vm.inventory.name = vm.inventory.name.trim(); vm.inventoryFocusIndex = -1; @@ -712,42 +746,43 @@ name: vm.inventory.name }, true); if (found.length == 1) { - var rate; - if (vm.vatSettings.applyTax) - rate = (vm.vatSettings.inclusive) ? found[0].amount : found[0].rate; - else - rate = found[0].rate; - vm.inventory.amount = (rate == '' || rate == undefined ? vm.inventory.amount : rate); - if (vm.vatSettings.applyTax) { - if (vm.vatSettings.inclusive) { - vm.inventory.rate = (vm.inventory.amount * 100) / (vm.vatSettings.tax + 100); - vm.inventory.tax = (vm.inventory.rate * vm.vatSettings.tax / 100); - } else { - vm.inventory.rate = (rate == '' || rate == undefined ? vm.inventory.rate : rate); - vm.inventory.tax = (vm.inventory.rate * vm.vatSettings.tax / 100); - vm.inventory.amount = Math.round(vm.inventory.rate + vm.inventory.tax); - } - } else { - if (vm.vatSettings.inclusive) - vm.inventory.rate = (rate == '' || rate == undefined ? vm.inventory.rate : rate); - else - vm.inventory.amount = (rate == '' || rate == undefined ? vm.inventory.amount : rate); - } + var taxable = vm.inventory.amount; + vm.inventory.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + vm.inventory.rate = taxable; vm.inventory.total = vm.inventory.amount * vm.inventory.qty; vm.inventory.checked = true; calculate(); + + function iterateTaxes(tax) { + if (!tax.isForInventory) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + vm.inventory.rate = (taxable * 100) / (tax.percent + 100); + temptax = (vm.inventory.rate * tax.percent / 100); + vm.inventory.tax[tax.name] = temptax; + taxable = vm.inventory.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + vm.inventory.tax[tax.name] = temptax; + } + } + } } } else { vm.inventory.checked = false; vm.inventory.rate = ''; - vm.inventory.tax = 0; + vm.inventory.tax = {}; vm.inventory.amount = ''; vm.inventory.total = ''; } } function changeQty(inventory) { - inventory.total = ((vm.vatSettings.applyTax ? inventory.amount : inventory.rate) * inventory.qty); + inventory.total = (inventory.amount * inventory.qty); calculate(); } @@ -781,13 +816,13 @@ function success(res) { vm.inventories = res; addExistingInventories(existingInventories); - getVatSettings(); + getInventoryTax(); } function failure(err) { vm.inventories = []; addExistingInventories(existingInventories); - getVatSettings(); + getInventoryTax(); } } @@ -821,24 +856,25 @@ } } - function getVatSettings() { - amServices.getVatSettings().then(success).catch(failure); + function getInventoryTax() { + amServices.getInventoryTax().then(success).catch(failure); function success(res) { - vm.vatSettings = res; - vm.orgApplyVat = res.applyTax; - if (vm.service.vat != undefined) { - vm.vatSettings.applyTax = vm.service.vat.applyTax; - vm.vatSettings.tax = vm.service.vat.tax; - if (!res.applyTax) - vm.orgApplyVat = vm.service.vat.applyTax; - vm.vatSettings.inclusive = (vm.service.vat.taxIncType == 'inclusive') ? true : false; + res.forEach(iterateTaxes); + OnTaxEnabledChange(); + + function iterateTaxes(tax) { + tax.tax = 0; + var found = $filter('filter')(vm.taxSettings, { + name: tax.name + }, true); + + if (found.length == 0) + vm.taxSettings.push(tax); } - changeVat(); } function failure(err) { - vm.vatSettings = {}; vm.inventories.forEach(iterateInventories); function iterateInventories(inventory) { @@ -866,34 +902,46 @@ } function changeInventoryRate(inventory, force) { - if (vm.vatSettings.applyTax) { - if (vm.vatSettings.inclusive) { - inventory.rate = (inventory.amount * 100) / (vm.vatSettings.tax + 100); - inventory.tax = (inventory.rate * vm.vatSettings.tax / 100); - } else { - if (!inventory.rate) - inventory.rate = inventory.amount; - inventory.tax = (inventory.rate * vm.vatSettings.tax / 100); - inventory.amount = inventory.rate + inventory.tax; - } - } else if (force) { - if (vm.vatSettings.inclusive) - inventory.rate = inventory.amount; - else - inventory.amount = inventory.rate; - } else - inventory.rate = inventory.amount; + var taxable = inventory.amount; + inventory.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + inventory.rate = taxable; changeQty(inventory); calculate(); + + function iterateTaxes(tax) { + if (!tax.isForInventory) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + inventory.rate = (taxable * 100) / (tax.percent + 100); + temptax = (inventory.rate * tax.percent / 100); + inventory.tax[tax.name] = temptax; + taxable = inventory.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + inventory.tax[tax.name] = temptax; + } + } + } } function convertPbToTitleCase() { vm.problem.details = utils.autoCapitalizeWord(vm.problem.details); } - function OnServiceTaxEnabledChange() { + function OnTaxEnabledChange() { vm.service.problems.forEach(iterateProblems); + changeProblemRate(vm.problem, true); calculatePackageTax(); + vm.inventories.forEach(iterateInventories); + changeInventoryRate(vm.inventory, true); + + function iterateInventories(inventory) { + changeInventoryRate(inventory, true); + } function iterateProblems(problem) { changeProblemRate(problem, true); @@ -911,57 +959,51 @@ } function changeProblemRate(problem, force) { - if (vm.sTaxSettings.applyTax) { - if (vm.sTaxSettings.inclusive) { - if (!problem.amount) { - var r = parseFloat(problem.rate) - problem.amount = Math.round(r + (r * vm.sTaxSettings.tax / 100)); + var taxable = problem.amount; + problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + problem.rate = parseFloat(taxable); + calculate(); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (problem.rate * tax.percent / 100); + problem.tax[tax.name] = temptax; + taxable = problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + problem.tax[tax.name] = temptax; } - problem.rate = (problem.amount * 100) / (vm.sTaxSettings.tax + 100); - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - } else { - if (!problem.rate) - problem.rate = problem.amount; - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - problem.amount = problem.rate + problem.tax; } - } else if (force) { - if (vm.sTaxSettings.inclusive) { - if (!problem.amount) - problem.amount = parseFloat(problem.rate); - problem.rate = problem.amount; - } else - problem.amount = problem.rate; - } else - problem.rate = problem.amount; - calculate(); + } } - function getServiceTaxSettings() { - amServices.getServiceTaxSettings().then(success).catch(failure); + function getTreatmentsTax() { + amServices.getTreatmentsTax().then(success).catch(failure); function success(res) { - vm.sTaxSettings = res; - vm.orgApplyTax = res.applyTax; - if (vm.service.serviceTax != undefined) { - vm.sTaxSettings.applyTax = vm.service.serviceTax.applyTax; - vm.sTaxSettings.tax = vm.service.serviceTax.tax; - if (!res.applyTax) - vm.orgApplyTax = vm.service.serviceTax.applyTax; - vm.sTaxSettings.inclusive = (vm.service.serviceTax.taxIncType == 'inclusive') ? true : false; - } - vm.service.problems.forEach(iterateProblems); - changeServiceTax(); - OnServiceTaxEnabledChange(); + res.forEach(iterateTaxes); + OnTaxEnabledChange(); + + function iterateTaxes(tax) { + tax.tax = 0; + var found = $filter('filter')(vm.taxSettings, { + name: tax.name + }, true); - function iterateProblems(problem) { - if (problem.checked) - delete problem.amount; + if (found.length == 0) + vm.taxSettings.push(tax); } } function failure(err) { - vm.sTaxSettings = {}; + console.warn(err); } } @@ -1229,21 +1271,27 @@ function changeTreatmentTax(treatment, force) { var cvt = vm.vehicle.type.toLowerCase().replace(' ', '-'); - if (vm.sTaxSettings.applyTax) { - if (treatment.tax == undefined) - treatment.tax = {}; - if (vm.sTaxSettings.inclusive) { - treatment.rate[cvt] = (treatment.amount[cvt] * 100) / (vm.sTaxSettings.tax + 100); - treatment.tax[cvt] = (treatment.rate[cvt] * vm.sTaxSettings.tax / 100); - } else { - treatment.tax[cvt] = (treatment.rate[cvt] * vm.sTaxSettings.tax / 100); - treatment.amount[cvt] = treatment.rate[cvt] + treatment.tax[cvt]; + var taxable = problem.amount; + problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + problem.rate = parseFloat(taxable); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (problem.rate * tax.percent / 100); + problem.tax[tax.name] = temptax; + taxable = problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + problem.tax[tax.name] = temptax; + } } - } else if (force) { - if (vm.sTaxSettings.inclusive) - treatment.rate[cvt] = treatment.amount[cvt]; - else - treatment.amount[cvt] = treatment.rate[cvt]; } } @@ -1349,10 +1397,10 @@ isManualRoundOff = vm.isRoundOffVal; vm.roundedOffVal = parseFloat(res.vehicle.service.roundoff); } - if (res.vehicle.service.serviceTax != undefined) - vm.service.serviceTax = res.vehicle.service.serviceTax; - if (res.vehicle.service.vat != undefined) - vm.service.vat = res.vehicle.service.vat; + if (res.vehicle.service.taxes) { + vm.taxSettings = []; + Object.keys(res.vehicle.service.taxes).forEach(iterateTaxes); + } vm.servicestatus = (res.vehicle.service.status == 'paid'); getRegularTreatments(res.vehicle.service.problems); getInventories(res.vehicle.service.inventories); @@ -1381,6 +1429,20 @@ } } + function iterateTaxes(tax) { + var t = res.vehicle.service.taxes[tax]; + + vm.taxSettings.push({ + tax: t.tax, + inclusive: (t.type == "inclusive"), + isTaxApplied: t.isTaxApplied, + isForTreatments: t.isForTreatments, + isForInventory: t.isForInventory, + percent: t.percent, + name: tax + }); + } + function iterateMemberships(membership) { res.memberships[membership].name = membership; vm.membershipChips.push(res.memberships[membership]); @@ -1550,19 +1612,27 @@ }); if (found.length == 1) { var rate = found[0].rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; - if (vm.sTaxSettings.applyTax) { - if (vm.sTaxSettings.inclusive) { - problem.amount = (rate == '' || rate == undefined ? problem.amount : rate); - problem.rate = (problem.amount * 100) / (vm.sTaxSettings.tax + 100); - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - } else { - problem.rate = (rate == '' || rate == undefined ? problem.rate : rate); - problem.tax = (problem.rate * vm.sTaxSettings.tax / 100); - problem.amount = problem.rate + problem.tax; + var taxable = problem.amount; + problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + problem.rate = parseFloat(taxable); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (problem.rate * tax.percent / 100); + problem.tax[tax.name] = temptax; + taxable = problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + problem.tax[tax.name] = temptax; + } } - } else { - problem.rate = (rate == '' || rate == undefined ? problem.rate : rate); - problem.amount = (rate == '' || rate == undefined ? problem.rate : rate); } } } @@ -1593,7 +1663,7 @@ function loadTreatmentIntoProblems(existingProblems) { vm.treatments.forEach(iterateTreatment); existingProblems.forEach(iterateProblem); - getServiceTaxSettings(); + getTreatmentsTax(); function iterateProblem(problem) { var found = $filter('filter')(vm.service.problems, { @@ -1656,6 +1726,8 @@ } function populateRoD() { + var isTreatmentTaxed = false, isInventoryTaxed = false, treatmentTaxPercent = 0, inventoryTaxPercent = 0; + vm.taxSettings.forEach(iterateTaxes); if (vm.isDiscountApplied) { var noTerminate = true, tp = 0.5, ip = 1 - tp; if (treatmentTotal == 0) { @@ -1666,7 +1738,7 @@ tp = 1; } while (noTerminate) { - var xt = (tp * vm.service.cost * 100) / (vm.sTaxSettings.tax + 100); + var xt = (tp * vm.service.cost * 100) / (treatmentTaxPercent + 100); if (treatmentTotal < xt) { tp -= 0.1; ip = 1 - tp; @@ -1675,7 +1747,7 @@ continue; } vm.discount.treatment = treatmentTotal - xt; - var xi = (ip * vm.service.cost * 100) / (vm.vatSettings.tax + 100); + var xi = (ip * vm.service.cost * 100) / (inventoryTaxPercent + 100); if (inventoryTotal < xi) { ip -= 0.1; tp = 1 - ip; @@ -1686,11 +1758,23 @@ vm.discount.part = inventoryTotal - xi; noTerminate = false; } + vm.discount.total = parseFloat(vm.discount.treatment) + parseFloat(vm.discount.part); vm.discount.total = (vm.discount.total % 1 != 0) ? parseFloat(vm.discount.total.toFixed(2)) : parseInt(vm.discount.total); calculate(); } else if (vm.isRoundOffVal) vm.roundedOffVal = vm.service.cost - serviceTcRo; + + function iterateTaxes(tax) { + if (tax.isTaxApplied && tax.isForTreatments) { + isTreatmentTaxed = true; + treatmentTaxPercent += tax.percent; + } + if (tax.isTaxApplied && tax.isForInventory) { + isInventoryTaxed = true; + inventoryTaxPercent += tax.percent; + } + } } function calculateRoundOff(isRoundOffManual) { @@ -1727,13 +1811,27 @@ function calculateTotalDiscount() { if (!vm.isDiscountApplied) return; + var isTreatmentTaxed = false, isInventoryTaxed = false, treatmentTaxPercent = 0, inventoryTaxPercent = 0; dTreatment = treatmentTotal - vm.discount.treatment; dInventory = inventoryTotal - vm.discount.part; - dTreatmentTax = (vm.sTaxSettings && vm.sTaxSettings.applyTax) ? (dTreatment * vm.sTaxSettings.tax / 100) : 0; - dInventoryTax = (vm.vatSettings && vm.vatSettings.applyTax) ? (dInventory * vm.vatSettings.tax / 100) : 0; + vm.taxSettings.forEach(iterateTaxes); + + dTreatmentTax = (isTreatmentTaxed) ? (dTreatment * treatmentTaxPercent / 100) : 0; + dInventoryTax = (isInventoryTaxed) ? (dInventory * inventoryTaxPercent / 100) : 0; dTreatmentTax = (dTreatmentTax % 1 != 0) ? parseFloat(dTreatmentTax.toFixed(2)) : dTreatmentTax; dInventoryTax = (dInventoryTax % 1 != 0) ? parseFloat(dInventoryTax.toFixed(2)) : dInventoryTax; + + function iterateTaxes(tax) { + if (tax.isTaxApplied && tax.isForTreatments) { + isTreatmentTaxed = true; + treatmentTaxPercent += tax.percent; + } + if (tax.isTaxApplied && tax.isForInventory) { + isInventoryTaxed = true; + inventoryTaxPercent += tax.percent; + } + } } function changeForceStopCalCost(bool) { @@ -1743,9 +1841,10 @@ function calculateCost(isDbp) { if (forceStopCalCost) return; - var totalCost = 0; + var totalCost = 0, taxes = 0; var discountedSubtotal = parseFloat(dTreatment + dTreatmentTax) + parseFloat(dInventory + dInventoryTax); - totalCost = (vm.isDiscountApplied && (vm.discount.total > 0)) ? Math.round(discountedSubtotal) : (parseFloat(vm.service.subtotal) + parseFloat(vm.service.taxes.serviceTax) + parseFloat(vm.service.taxes.vat)); + vm.taxSettings.forEach(iterateTaxes); + totalCost = (vm.isDiscountApplied && (vm.discount.total > 0)) ? Math.round(discountedSubtotal) : (parseFloat(vm.service.subtotal) + parseFloat(taxes)); serviceTcDc = totalCost; serviceTcRo = totalCost; if (vm.isRoundOffVal) { @@ -1755,38 +1854,9 @@ totalCost = (totalCost % 1 != 0) ? parseFloat(totalCost.toFixed(2)) : totalCost; totalCost = (totalCost % 1).toFixed(2) == 0.00 ? Math.round(totalCost) : totalCost; vm.service.cost = parseFloat(totalCost); - } - function calculateServiceTax() { - if (vm.isDiscountApplied && (vm.discount.total > 0)) { - vm.service.taxes.serviceTax = dTreatmentTax; - return; - } - var totalTax = 0.0; - if ((vm.problem.amount > 0) && (vm.sTaxSettings && vm.sTaxSettings.applyTax) && vm.problem.tax) - totalTax += parseFloat(vm.problem.tax); - vm.service.problems.forEach(iterateProblems); - if (vm.serviceType == vm.serviceTypeList[1]) - vm.packages.forEach(iteratePackages); - totalTax = (totalTax % 1 != 0) ? totalTax.toFixed(2) : parseInt(totalTax); - vm.service.taxes.serviceTax = parseFloat(totalTax); - - function iterateProblems(problem) { - if ((vm.sTaxSettings && !vm.sTaxSettings.applyTax) || !problem.tax || !problem.checked) - return; - totalTax += parseFloat(problem.tax); - } - - function iteratePackages(package) { - if (!package.checked) - return; - package.selectedTreatments.forEach(ipt); - } - - function ipt(treatment) { - if ((vm.sTaxSettings && !vm.sTaxSettings.applyTax) || !treatment.tax) - return; - totalTax += parseFloat(treatment.tax[vm.vehicle.type.toLowerCase().replace(' ', '-')]); + function iterateTaxes(tax) { + taxes += tax.tax; } } @@ -1804,7 +1874,7 @@ }, true); if (found.length == 1) { found[0].checked = true; - found[0].rate = (vm.sTaxSettings && vm.sTaxSettings.applyTax) ? vm.problem.rate : vm.problem.amount; + found[0].rate = vm.problem.rate; found[0].tax = vm.problem.tax; found[0].amount = vm.problem.amount; if (foundExisting.length == 0) @@ -1814,7 +1884,7 @@ } else { vm.service.problems.push({ details: vm.problem.details, - rate: (vm.sTaxSettings && vm.sTaxSettings.applyTax) ? vm.problem.rate : vm.problem.amount, + rate: vm.problem.rate, tax: vm.problem.tax, amount: vm.problem.amount, checked: true @@ -1825,6 +1895,7 @@ vm.problem.details = ''; vm.problem.amount = ''; vm.problem.rate = ''; + vm.problem.tax = {}; if (isFromAutocomplete || foundExisting.length != 0) vm.problemFocusIndex = (foundExisting.length == 0) ? vm.selectedProblems.length - 1 : vm.selectedProblems.indexOf(foundExisting[0]); else @@ -1844,27 +1915,34 @@ if (found.length == 1) { var rate = found[0].rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; vm.problem.amount = (rate == '' || rate == undefined ? vm.problem.amount : rate); - if (vm.sTaxSettings.applyTax) { - if (vm.sTaxSettings.inclusive) { - vm.problem.rate = (vm.problem.amount * 100) / (vm.sTaxSettings.tax + 100); - vm.problem.tax = (vm.problem.rate * vm.sTaxSettings.tax / 100); - } else { - vm.problem.rate = (rate == '' || rate == undefined ? vm.problem.rate : rate); - vm.problem.tax = (vm.problem.rate * vm.sTaxSettings.tax / 100); - vm.problem.amount = vm.problem.rate + vm.problem.tax; - } - } else { - if (vm.sTaxSettings.inclusive) - vm.problem.rate = (rate == '' || rate == undefined ? vm.problem.rate : rate); - else - vm.problem.amount = (rate == '' || rate == undefined ? vm.problem.amount : rate); - } + var taxable = vm.problem.amount; + vm.problem.tax = {}; + vm.taxSettings.forEach(iterateTaxes); + vm.problem.rate = taxable; vm.problem.checked = true; calculate(); + + function iterateTaxes(tax) { + if (!tax.isForTreatments) + return; + if (tax.isTaxApplied) { + if (tax.inclusive) { + var temptax = 0; + vm.problem.rate = (taxable * 100) / (tax.percent + 100); + temptax = (vm.problem.rate * tax.percent / 100); + vm.problem.tax[tax.name] = temptax; + taxable = vm.problem.rate; + } else { + var temptax = 0; + temptax = (taxable * tax.percent / 100); + vm.problem.tax[tax.name] = temptax; + } + } + } } } else { vm.problem.rate = ''; - vm.problem.tax = 0; + vm.problem.tax = {}; vm.problem.amount = ''; vm.problem.checked = false; } @@ -1928,20 +2006,8 @@ if (vm.isRoundOffVal) { vm.service['roundoff'] = vm.roundedOffVal; } - if (vm.sTaxSettings != undefined) { - vm.service.serviceTax = { - applyTax: vm.sTaxSettings.applyTax, - taxIncType: (vm.sTaxSettings.inclusive) ? 'inclusive' : 'exclusive', - tax: vm.sTaxSettings.tax - }; - } - if (vm.vatSettings != undefined) { - vm.service.vat = { - applyTax: vm.vatSettings.applyTax, - taxIncType: (vm.vatSettings.inclusive) ? 'inclusive' : 'exclusive', - tax: vm.vatSettings.tax - } - } + vm.service.taxes = {}; + vm.taxSettings.forEach(iterateTaxes); if (vm.inventory.name) vm.selectedInventories.push(vm.inventory); vm.selectedInventories.forEach(iterateInventories); @@ -1973,6 +2039,17 @@ } amServices.saveService(vm.user, vm.vehicle, vm.service, options).then(success).catch(failure); + function iterateTaxes(tax) { + vm.service.taxes[tax.name] = { + tax: tax.tax, + type: (tax.inclusive) ? "inclusive" : "exclusive", + isTaxApplied: tax.isTaxApplied, + isForTreatments: tax.isForTreatments, + isForInventory: tax.isForInventory, + percent: tax.percent + } + } + function addMsToService(membership) { if (!membership.checked) return; @@ -1998,16 +2075,12 @@ } function iterateProblems(problem) { - if (!vm.sTaxSettings || (vm.sTaxSettings && !vm.sTaxSettings.applyTax && vm.sTaxSettings.inclusive)) - problem.rate = problem.amount; delete problem.checked; delete problem['amount']; delete problem['$$hashKey']; } function iterateInventories(inventory) { - if (!vm.vatSettings || (vm.vatSettings && vm.vatSettings.applyTax && vm.vatSettings.inclusive)) - inventory.rate = inventory.amount; delete inventory.checked; delete inventory['total']; delete inventory['amount']; diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 6beae693..d9747dbe 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -2,7 +2,7 @@ * Factory that handles database interactions between services database and controller * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -30,21 +30,91 @@ getLastInvoiceNo: getLastInvoiceNo, getPackages: getPackages, getMemberships: getMemberships, - getServiceTaxSettings: getServiceTaxSettings, getServices: getServices, getInventories: getInventories, - getVatSettings: getVatSettings, getInventoriesSettings: getInventoriesSettings, getLastEstimateNo : getLastEstimateNo, getLastJobCardNo: getLastJobCardNo, getDefaultServiceType: getDefaultServiceType, - getFilterMonths: getFilterMonths + getFilterMonths: getFilterMonths, + getTreatmentsTax: getTreatmentsTax, + getInventoryTax: getInventoryTax } return factory; // function definitions + function getTreatmentsTax() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); + } + + function getSettingsObj(res) { + var result = []; + if (res.settings && res.settings.tax) + Object.keys(res.settings.tax).forEach(iterateTaxes); + tracker.resolve(result); + + function iterateTaxes(tax) { + var t = res.settings.tax[tax]; + if (t.isForTreatments) { + result.push({ + inclusive: (t.type == "inclusive"), + isTaxApplied: t.isTaxApplied, + isForTreatments: t.isForTreatments, + isForInventory: t.isForInventory, + percent: t.percent, + name: tax + }); + } + } + } + + function failure(err) { + tracker.reject(err); + } + } + + function getInventoryTax() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); + } + + function getSettingsObj(res) { + var result = []; + if (res.settings && res.settings.tax) + Object.keys(res.settings.tax).forEach(iterateTaxes); + tracker.resolve(result); + + function iterateTaxes(tax) { + var t = res.settings.tax[tax]; + if (t.isForInventory) { + result.push({ + inclusive: (t.type == "inclusive"), + isTaxApplied: t.isTaxApplied, + isForTreatments: t.isForTreatments, + isForInventory: t.isForInventory, + percent: t.percent, + name: tax + }); + } + } + } + + function failure(err) { + tracker.reject(err); + } + } + function getFilterMonths() { var tracker = $q.defer(); pdbCache.get(constants.pdb_cache_views.view_services).then(generateMonthsUsed).catch(failure); @@ -125,37 +195,6 @@ } } - function getVatSettings() { - var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - - function getSettingsObj(res) { - if (res.settings && res.settings.vat) { - tracker.resolve({ - applyTax: res.settings.vat.applyTax, - inclusive: (res.settings.vat.taxIncType == 'inclusive') ? true : false, - tax: res.settings.vat.tax - }); - } else - failure(); - } - - function failure(err) { - if (!err) { - err = { - success: false, - message: 'VAT Settings Not Found!' - } - } - tracker.reject(err); - } - } - function getInventories() { var tracker = $q.defer(); var response = []; @@ -188,37 +227,6 @@ tracker.reject(err); } } - - function getServiceTaxSettings() { - var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - - function getSettingsObj(res) { - if (res.settings && res.settings.servicetax) { - tracker.resolve({ - applyTax: res.settings.servicetax.applyTax, - inclusive: (res.settings.servicetax.taxIncType == 'inclusive') ? true : false, - tax: res.settings.servicetax.tax - }); - } else - failure(); - } - - function failure(err) { - if (!err) { - err = { - success: false, - message: 'Service Tax Settings Not Found!' - } - } - tracker.reject(err); - } - } // retrieve current treatment settings function getTreatmentSettings() { diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 771fbcdb..6af386f1 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -485,24 +485,14 @@ ) - Rate - Tax Amount - - {{vm.doRoundOff(problem.rate)}} - - - - {{vm.doRoundOff(problem.tax)}} - - - {{vm.doRoundOff(problem.amount)}} + @@ -513,16 +503,8 @@ - - {{vm.doRoundOff(vm.problem.rate)}} - - - - {{vm.doRoundOff(vm.problem.tax)}} - - - {{vm.doRoundOff(vm.problem.amount)}} + @@ -541,24 +523,14 @@ ) - Rate - Tax Amount - - {{vm.doRoundOff(problem.rate)}} - - - - {{vm.doRoundOff(problem.tax)}} - - - {{vm.doRoundOff(problem.amount)}} + @@ -569,16 +541,8 @@ - - {{vm.doRoundOff(vm.problem.rate)}} - - - - {{vm.doRoundOff(vm.problem.tax)}} - - - {{vm.doRoundOff(vm.problem.amount)}} + @@ -598,29 +562,20 @@ Rate Qty - VAT Amount - - - - - - + + - - {{vm.doRoundOff(inventory.tax * inventory.qty)}} - - - {{vm.doRoundOff(inventory.total)}} + @@ -631,22 +586,14 @@ - - - - - - + + - - {{vm.doRoundOff(vm.inventory.tax * vm.inventory.qty)}} - - - {{vm.doRoundOff(vm.inventory.total)}} + @@ -666,29 +613,20 @@ Rate Qty - VAT Amount - - - - - - + + - - {{vm.doRoundOff(inventory.tax * inventory.qty)}} - - - {{vm.doRoundOff(inventory.total)}} + @@ -699,22 +637,14 @@ - - - - - - + + - - {{vm.doRoundOff(vm.inventory.tax * vm.inventory.qty)}} - - - {{vm.doRoundOff(vm.inventory.total)}} + @@ -738,11 +668,11 @@ Manage Payments - Service Tax -  % - - VAT -  % + + + {{tax.name}} + +  % Round Off @@ -763,10 +693,10 @@   Round Off: Rs.   - Service Tax: Rs. {{vm.service.taxes.serviceTax}} -   - VAT: Rs. {{vm.service.taxes.vat}} -   + + {{tax.name}}: Rs. {{tax.tax}} + +   Total Cost: Rs. {{vm.service.cost}}
diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index a625d3c8..ec6d43c5 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -138,7 +138,7 @@ getIvAlignMargins(); getAllTaxSettings(); // changeInvoiceTab(true) // testing purposes amTODO: remove it - changeTaxTab(true); // testing purposes amTODO: remove it + // changeTaxTab(true); // testing purposes amTODO: remove it // default execution steps [END] From a89d1bdaf4fa59aca428b24b47a90156244f885f Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 10:55:03 +0530 Subject: [PATCH 26/91] Enhancement #143 & Feature #192 | Fixes 143. Do nothing exits app 192. New taxes are disabled (by default) in edit services --- .../services/services-edit.controller.js | 2 ++ src/main.js | 17 ++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index fa6719af..4a6e21d5 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -865,6 +865,7 @@ function iterateTaxes(tax) { tax.tax = 0; + tax.isTaxApplied = false; var found = $filter('filter')(vm.taxSettings, { name: tax.name }, true); @@ -993,6 +994,7 @@ function iterateTaxes(tax) { tax.tax = 0; + tax.isTaxApplied = false; var found = $filter('filter')(vm.taxSettings, { name: tax.name }, true); diff --git a/src/main.js b/src/main.js index 6612c895..abf2c164 100644 --- a/src/main.js +++ b/src/main.js @@ -118,13 +118,16 @@ }, openCorrectFileUrl); } - function openCorrectFileUrl() { - var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); - if (newPath) { - ammPreferences.storePreference('automint.userDataPath', newPath[0]); - restartApp(); - } else - app.exit(0); + function openCorrectFileUrl(buttonIndex) { + if (buttonIndex == 0) { + var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); + if (newPath) { + ammPreferences.storePreference('automint.userDataPath', newPath[0]); + restartApp(); + return; + } + } + app.exit(0); } } From fd301b2971aba30e198454785948e46a3ba14695 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 11:23:26 +0530 Subject: [PATCH 27/91] Feature #192 | Decimal Points upto 2 in Tax --- src/app/components/services/services-add.controller.js | 2 ++ src/app/components/services/services-edit.controller.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 651cc724..50fa442d 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -270,6 +270,7 @@ if (!found[0].tax) found[0].tax = 0; found[0].tax += problem.tax[tax]; + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); } } } @@ -291,6 +292,7 @@ if (!found[0].tax) found[0].tax = 0; found[0].tax += problem.tax[tax]; + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); } } } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 4a6e21d5..9605fed0 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -255,6 +255,7 @@ if (!found[0].tax) found[0].tax = 0; found[0].tax += problem.tax[tax]; + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); } } } @@ -276,6 +277,7 @@ if (!found[0].tax) found[0].tax = 0; found[0].tax += problem.tax[tax]; + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); } } } From c94d67d7a104f9be86cd6fe88c7cdc0eabda96a6 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 12:52:31 +0530 Subject: [PATCH 28/91] Feature #188 | Global Currency Support --- .../customers/customers-edit.controller.js | 14 ++++ .../components/customers/customers.factory.js | 24 +++++- .../components/customers/customers_edit.html | 6 +- .../dashboard/dashboard.controller.js | 14 ++++ .../components/dashboard/dashboard.factory.js | 30 +++++++- src/app/components/dashboard/dashboard.html | 4 +- .../invoices/invoices-view.controller.js | 76 ++++++++++++++----- .../components/invoices/invoices.factory.js | 26 ++++++- .../components/invoices/invoices_view.html | 36 ++++----- .../services/services-add.controller.js | 16 ++++ .../services/services-edit.controller.js | 14 ++++ .../services/services-viewall.controller.js | 16 +++- .../components/services/services.factory.js | 24 +++++- src/app/components/services/services_add.html | 14 ++-- .../components/services/services_viewAll.html | 4 +- .../tmpl/dialog_discount.controller.js | 7 +- .../services/tmpl/dialog_discount.html | 6 +- .../tmpl/dialog_partialpayment.controller.js | 5 +- .../services/tmpl/dialog_partialpayment.html | 4 +- .../settings/settings.controller.js | 32 ++++++++ .../components/settings/settings.factory.js | 59 +++++++++++++- src/app/components/settings/settings.html | 6 ++ .../memberships-viewall.controller.js | 16 +++- .../treatments/treatments.factory.js | 26 ++++++- .../treatments/treatments_master.html | 2 +- 25 files changed, 400 insertions(+), 81 deletions(-) diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 50a2693f..c5249a57 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -54,6 +54,7 @@ vm.customerTypeList = ['Customer', 'Agency']; vm.paymentDone = 0; vm.paymentDue = 0; + vm.currencySymbol = "Rs."; // function maps vm.changeVehicle = changeVehicle; @@ -79,6 +80,7 @@ // default execution steps // $state.params.id = ($state.params.id == undefined) ? "usr-anand-kacha-772d071e-852c-4a45-aaaf-089d80f73449" : $state.params.id; if ($state.params.id != undefined) { + getCurrencySymbol(); getMemberships(getRegularTreatments, getVehicleTypes, getCustomer); setTimeout(focusCustomerMobile, 300); } else { @@ -88,6 +90,18 @@ // function definitions + function getCurrencySymbol() { + amCustomers.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + function editVehicle(id) { changeVehicle(id); $mdDialog.show({ diff --git a/src/app/components/customers/customers.factory.js b/src/app/components/customers/customers.factory.js index b1dd03c8..c79209c6 100644 --- a/src/app/components/customers/customers.factory.js +++ b/src/app/components/customers/customers.factory.js @@ -26,13 +26,35 @@ getMemberships: getMemberships, getRegularTreatments: getRegularTreatments, getVehicleTypes: getVehicleTypes, - getCustomerByMobile: getCustomerByMobile + getCustomerByMobile: getCustomerByMobile, + getCurrencySymbol: getCurrencySymbol }; return factory; // function definitions + function getCurrencySymbol() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.currency) + tracker.resolve(res.currency); + else + failure('No Currency Symbol Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } + function getCustomerByMobile(mobile) { var tracker = $q.defer(); pdbCustomers.query(mapView, { diff --git a/src/app/components/customers/customers_edit.html b/src/app/components/customers/customers_edit.html index 0e3b49c2..235805c0 100644 --- a/src/app/components/customers/customers_edit.html +++ b/src/app/components/customers/customers_edit.html @@ -178,11 +178,11 @@
- Rs.{{vm.paymentDone}} + {{vm.currencySymbol}} {{vm.paymentDone}}
- Rs.{{vm.paymentDue}} + {{vm.currencySymbol}} {{vm.paymentDue}}
@@ -227,7 +227,7 @@ Vehicle Date - Amount (Rs.) + Amount ({{vm.currencySymbol}}) Payment diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index cd227f55..2968af63 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -82,6 +82,7 @@ vm.nextServiceDueCustomers = []; vm.nsdcTimeRange = ['Today', 'This Week', 'This Month', 'All']; vm.nsdcTime = vm.nsdcTimeRange[0]; + vm.currencySymbol = "Rs."; // function maps vm.addNewService = addNewService; @@ -102,9 +103,22 @@ getFilterMonths(); $amRoot.ccViews(); processPreferences(); + getCurrencySymbol(); // function definitions + function getCurrencySymbol() { + amDashboard.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + function IsNoNextDueReminders() { return (vm.nextServiceDueCustomers.length == 0); } diff --git a/src/app/components/dashboard/dashboard.factory.js b/src/app/components/dashboard/dashboard.factory.js index a998896d..43d153a9 100644 --- a/src/app/components/dashboard/dashboard.factory.js +++ b/src/app/components/dashboard/dashboard.factory.js @@ -2,7 +2,7 @@ * Factory that handles database interactions between dashboard dataset and controller * @author ndkcha * @since 0.6.0 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').factory('amDashboard', DashboardFactory); - DashboardFactory.$inject = ['$q', '$filter', 'utils', 'constants', 'pdbCache', 'pdbCustomers']; + DashboardFactory.$inject = ['$q', '$filter', '$amRoot', 'utils', 'constants', 'pdbConfig', 'pdbCache', 'pdbCustomers']; - function DashboardFactory($q, $filter, utils, constants, pdbCache, pdbCustomers) { + function DashboardFactory($q, $filter, $amRoot, utils, constants, pdbConfig, pdbCache, pdbCustomers) { // initialize dashboard factory and funtion mappings var factory = { getTotalCustomerServed: getTotalCustomerServed, @@ -21,13 +21,35 @@ getFilterMonths: getFilterMonths, getNextDueCustomers: getNextDueCustomers, deleteServiceReminder: deleteServiceReminder, - changeServiceReminderDate: changeServiceReminderDate + changeServiceReminderDate: changeServiceReminderDate, + getCurrencySymbol: getCurrencySymbol } return factory; // function definitions + function getCurrencySymbol() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.currency) + tracker.resolve(res.currency); + else + failure('No Currency Symbol Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } + function changeServiceReminderDate(cId, vId, nextdue) { var tracker = $q.defer(); pdbCustomers.get(cId).then(getCustomerDoc).catch(failure); diff --git a/src/app/components/dashboard/dashboard.html b/src/app/components/dashboard/dashboard.html index f065e149..5baae372 100644 --- a/src/app/components/dashboard/dashboard.html +++ b/src/app/components/dashboard/dashboard.html @@ -100,7 +100,7 @@
Income
- Rs.$$$Rs.{{vm.totalRevenueEarned}} + {{vm.currencySymbol}} $$${{vm.currencySymbol}} {{vm.totalRevenueEarned}}
@@ -110,7 +110,7 @@
Due Payments
- Rs.{{vm.totalPendingPayments}} + {{vm.currencySymbol}} {{vm.totalPendingPayments}}
diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 3288faf1..989f57ec 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -31,6 +31,8 @@ vm.isFabOpen = true; vm.fabClass = ''; vm.subtotal = 0; + vm.currencySymbol = "Rs."; + vm.taxSettings = []; // function maps vm.printInvoice = printInvoice; @@ -51,6 +53,7 @@ vm.IsInvoiceAvailable = IsInvoiceAvailable; vm.IsSubtotalEnabled = IsSubtotalEnabled; vm.calculateSubtotal = calculateSubtotal; + vm.IsTaxEnabled = IsTaxEnabled; // default execution steps if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { @@ -58,6 +61,7 @@ $state.go('restricted.services.all'); return; } + getCurrencySymbol(); fillInvoiceDetails(); loadInvoiceWLogo(); getIvAlignMargins(); @@ -67,8 +71,38 @@ // function definitions + function IsTaxEnabled() { + var iTe = false; + vm.taxSettings.forEach(iterateTaxes); + return iTe; + + function iterateTaxes(tax) { + if (tax.isTaxApplied) + iTe = true; + } + } + + function getCurrencySymbol() { + amInvoices.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + function IsSubtotalEnabled() { - return (vm.isDiscountApplied || vm.isRoundOff || (vm.sTaxSettings && vm.sTaxSettings.applyTax) || (vm.vatSettings && vm.vatSettings.applyTax)); + var isTaxEnabled = false; + vm.taxSettings.forEach(iterateTaxes); + return (vm.isDiscountApplied || vm.isRoundOffVal || isTaxEnabled); + + function iterateTaxes(tax) { + if (tax.isTaxApplied) + isTaxEnabled = true; + } } function calculateSubtotal() { @@ -171,17 +205,7 @@ function iterateInventories(inventory) { if (inventory.qty == undefined) inventory.qty = 1; - if (vm.vatSettings.applyTax) { - inventory.amount = inventory.rate; - if (vm.vatSettings.inclusive) { - inventory.rate = (inventory.amount * 100) / (vm.vatSettings.tax + 100); - inventory.tax = (inventory.rate * vm.vatSettings.tax / 100); - } else { - inventory.tax = (inventory.rate * vm.vatSettings.tax / 100); - inventory.amount = Math.round(inventory.rate + inventory.tax); - } - } - inventory.total = (inventory.rate * inventory.qty) + ((inventory.tax ? inventory.tax : 0) * inventory.qty); + inventory.total = inventory.rate * inventory.qty; inventory.total = (inventory.total % 1 != 0) ? inventory.total.toFixed(2) : parseInt(inventory.total); } } @@ -204,18 +228,28 @@ vm.service = res.service; vm.isRoundOff = (vm.service.roundoff != undefined); vm.isDiscountApplied = (vm.service.discount != undefined); - vm.sTaxSettings = { - applyTax: res.service.serviceTax.applyTax, - inclusive: (res.service.serviceTax.taxIncType == 'inclusive'), - tax: res.service.serviceTax.tax - }; - vm.vatSettings = { - applyTax: res.service.vat.applyTax, - inclusive: (res.service.vat.taxIncType == 'inclusive'), - tax: res.service.vat.tax + if (vm.isDiscountApplied) { + vm.discountValue = (vm.service.discount.amount || vm.service.discount.total); } + if (res.service.taxes) + Object.keys(res.service.taxes).forEach(iterateTaxes); + console.log(vm.taxSettings); calculateInventoryValues(); calculateSubtotal(); + + function iterateTaxes(tax) { + var t = res.service.taxes[tax]; + + vm.taxSettings.push({ + tax: t.tax, + inclusive: (t.type == "inclusive"), + isTaxApplied: t.isTaxApplied, + isForTreatments: t.isForTreatments, + isForInventory: t.isForInventory, + percent: t.percent, + name: tax + }); + } } function failure(err) { diff --git a/src/app/components/invoices/invoices.factory.js b/src/app/components/invoices/invoices.factory.js index 1f92fb9c..3454f76f 100644 --- a/src/app/components/invoices/invoices.factory.js +++ b/src/app/components/invoices/invoices.factory.js @@ -2,7 +2,7 @@ * Factory that handles database interactions between invoices database and controller * @author ndkcha * @since 0.5.0 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -20,13 +20,35 @@ getWorkshopDetails: getWorkshopDetails, getIvSettings: getIvSettings, getIvAlignMargins: getIvAlignMargins, - saveCustomerEmail: saveCustomerEmail + saveCustomerEmail: saveCustomerEmail, + getCurrencySymbol: getCurrencySymbol } return factory; // function definitions + function getCurrencySymbol() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.currency) + tracker.resolve(res.currency); + else + failure('No Currency Symbol Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } + function saveCustomerEmail(userId, email) { var tracker = $q.defer(); pdbCustomers.get(userId).then(getUserObject).catch(failure); diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index 25e1b5b0..e92a87c6 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -199,55 +199,49 @@

{{vm.workshop.name}}

- {{treatment.name}} - Rs. {{treatment.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}} + {{vm.currencySymbol}} {{treatment.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}} {{problem.details}} - Rs. {{problem.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}} + {{vm.currencySymbol}} {{problem.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}} {{inventory.name}} - Rs. {{inventory.rate | number: (vm.vatSettings.applyTax) ? ((vm.vatSettings.inclusive) ? 2 : 0) : 0}} + {{vm.currencySymbol}} {{inventory.rate | number: (vm.vatSettings.applyTax) ? ((vm.vatSettings.inclusive) ? 2 : 0) : 0}} {{inventory.qty}} - Rs. {{inventory.total}} + {{vm.currencySymbol}} {{inventory.total}} Sub Total - - - Rs. {{vm.subtotal}} - - - Service Tax ({{vm.sTaxSettings.tax}}%) - Rs. {{vm.calculateServiceTax()}} + {{vm.currencySymbol}} {{vm.subtotal}} - - VAT ({{vm.vatSettings.tax}}%) + + Discount ({{vm.service.discount.percent}}%) - Rs. {{vm.calculateVat()}} + {{vm.currencySymbol}} {{vm.discountValue}} - - Discount ({{vm.service.discount.percent}}%) + + {{tax.name}} ({{tax.percent}}%) - Rs. {{vm.service.discount.amount}} + {{vm.currencySymbol}} {{tax.tax}} - + Round Off - Rs. {{vm.service.roundoff}} + {{vm.currencySymbol}} {{vm.service.roundoff}} - + Total - Rs. {{vm.service.cost | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}} + {{vm.currencySymbol}} {{vm.service.cost | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}} diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 50fa442d..debebaa2 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -108,6 +108,7 @@ total: 0 }; vm.taxSettings = []; + vm.currencySymbol = "Rs."; // named assignments to handle behaviour of UI elements vm.redirect = { @@ -201,6 +202,7 @@ setTimeout(focusUserName, 700); // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting buildDelayedToggler('service-details-left'); + getCurrencySymbol(); getDefaultServiceType(); getTreatmentDisplayFormat(); getInventoriesSettings(); @@ -217,6 +219,18 @@ // function definitions + function getCurrencySymbol() { + amServices.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + function calculate() { calculateSubtotal(); calculateTotalDiscount(); @@ -313,6 +327,7 @@ parent: angular.element(document.body), targetEvent: event, locals: { + currencySymbol: vm.currencySymbol, treatmentLength: (vm.selectedProblems.length ? vm.selectedProblems.length : 0) + ((parseFloat(vm.problem.amount) > 0) ? 1 : 0), partLength: (vm.selectedInventories.length ? vm.selectedInventories.length : 0) + ((parseFloat(vm.inventory.amount) > 0) ? 1 : 0), discountObj: vm.discount, @@ -358,6 +373,7 @@ parent: angular.element(document.body), targetEvent: event, locals: { + currencySymbol: vm.currencySymbol, totalCost: vm.service.cost, partialPayments: vm.service.partialpayment }, diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 9605fed0..78fccd2d 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -108,6 +108,7 @@ total: 0 }; vm.taxSettings = []; + vm.currencySymbol = "Rs."; // named assignments to handle behaviour of UI elements vm.redirect = { @@ -193,6 +194,7 @@ setCoverPic(); buildDelayedToggler('service-details-left'); changeServiceInfoState(true); + getCurrencySymbol(); getVehicleTypes(); getPackages(); getMemberships(); @@ -202,6 +204,18 @@ // function definitions + function getCurrencySymbol() { + amServices.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + function calculate() { calculateSubtotal(); calculateTotalDiscount(); diff --git a/src/app/components/services/services-viewall.controller.js b/src/app/components/services/services-viewall.controller.js index 2d8ac03c..8fc24548 100644 --- a/src/app/components/services/services-viewall.controller.js +++ b/src/app/components/services/services-viewall.controller.js @@ -2,7 +2,7 @@ * Controller for View Services module * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -32,6 +32,7 @@ vm.serviceStateList = ['Job Card', 'Estimate', 'Bill']; vm.currentTimeSet = []; vm.ddTimeSet = ''; + vm.currencySymbol = "Rs."; // function maps vm.addService = addService; @@ -50,11 +51,24 @@ // default execution steps $scope.$watch('vm.serviceQuery', watchServiceQuery); + getCurrencySymbol(); getFilterMonths(processPreferences); initCurrentTimeSet(); // function definitions + function getCurrencySymbol() { + amServices.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + function openTimeFilter(event) { $mdDialog.show({ controller: 'amCtrlSeTmFl', diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index d9747dbe..383347dc 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -38,13 +38,35 @@ getDefaultServiceType: getDefaultServiceType, getFilterMonths: getFilterMonths, getTreatmentsTax: getTreatmentsTax, - getInventoryTax: getInventoryTax + getInventoryTax: getInventoryTax, + getCurrencySymbol: getCurrencySymbol } return factory; // function definitions + function getCurrencySymbol() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.currency) + tracker.resolve(res.currency); + else + failure('No Currency Symbol Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } + function getTreatmentsTax() { var tracker = $q.defer(); $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 6af386f1..f340bcdf 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -687,24 +687,24 @@
- Subtotal: Rs. {{vm.service.subtotal}} + Subtotal: {{vm.currencySymbol}} {{vm.service.subtotal}}   - Discount: Rs. + Discount: {{vm.currencySymbol}}   - Round Off: Rs. + Round Off: {{vm.currencySymbol}}   - {{tax.name}}: Rs. {{tax.tax}} + {{tax.name}}: {{vm.currencySymbol}} {{tax.tax}}   - Total Cost: Rs. {{vm.service.cost}} + Total Cost: {{vm.currencySymbol}} {{vm.service.cost}}
  - Received: Rs. {{vm.service.partialpayment.total}} + Received: {{vm.currencySymbol}} {{vm.service.partialpayment.total}}   - Due: Rs. {{vm.calculateDue()}} + Due: {{vm.currencySymbol}} {{vm.calculateDue()}}
diff --git a/src/app/components/services/services_viewAll.html b/src/app/components/services/services_viewAll.html index dbc69aae..358f50fb 100644 --- a/src/app/components/services/services_viewAll.html +++ b/src/app/components/services/services_viewAll.html @@ -35,7 +35,7 @@ Customer Vehicle Date - Amount
(Rs.) + Amount
({{vm.currencySymbol}}) Payment @@ -48,7 +48,7 @@ {{service.srvc_status}} {{service.srvc_state}} - + delete diff --git a/src/app/components/services/tmpl/dialog_discount.controller.js b/src/app/components/services/tmpl/dialog_discount.controller.js index f60aa31e..18f92afa 100644 --- a/src/app/components/services/tmpl/dialog_discount.controller.js +++ b/src/app/components/services/tmpl/dialog_discount.controller.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlSeDc', DiscountController); - DiscountController.$inject = ['$mdDialog', 'treatmentLength', 'partLength', 'treatmentTotal', 'partTotal', 'discountObj']; + DiscountController.$inject = ['$mdDialog', 'treatmentLength', 'partLength', 'treatmentTotal', 'partTotal', 'discountObj', 'currencySymbol']; - function DiscountController($mdDialog, treatmentLength, partLength, treatmentTotal, partTotal, discountObj) { + function DiscountController($mdDialog, treatmentLength, partLength, treatmentTotal, partTotal, discountObj, currencySymbol) { // initiailize view model var vm = this; @@ -23,7 +23,8 @@ vm.isTreatment = (treatmentLength > 0); vm.isPart = (partLength > 0); vm.isTreatmentSelected = false; - vm.isPartSelected = false; + vm.isPartSelected = false; + vm.currencySymbol = currencySymbol; // function maps to view model vm.cancel = cancel; diff --git a/src/app/components/services/tmpl/dialog_discount.html b/src/app/components/services/tmpl/dialog_discount.html index 24db08b9..232eab01 100644 --- a/src/app/components/services/tmpl/dialog_discount.html +++ b/src/app/components/services/tmpl/dialog_discount.html @@ -18,17 +18,17 @@
Treatments: - Rs. + {{vm.currencySymbol}}
Parts: - Rs. + {{vm.currencySymbol}}
Total: {{vm.totalDiscount}} - Rs. + {{vm.currencySymbol}}
diff --git a/src/app/components/services/tmpl/dialog_partialpayment.controller.js b/src/app/components/services/tmpl/dialog_partialpayment.controller.js index 589d8c26..db9a6eb1 100644 --- a/src/app/components/services/tmpl/dialog_partialpayment.controller.js +++ b/src/app/components/services/tmpl/dialog_partialpayment.controller.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlSePp', PartialPaymentController); - PartialPaymentController.$inject = ['$mdDialog', '$filter', 'totalCost', 'partialPayments']; + PartialPaymentController.$inject = ['$mdDialog', '$filter', 'totalCost', 'partialPayments', 'currencySymbol']; - function PartialPaymentController($mdDialog, $filter, totalCost, partialPayments) { + function PartialPaymentController($mdDialog, $filter, totalCost, partialPayments, currencySymbol) { // initialize view model var vm = this; @@ -20,6 +20,7 @@ vm.partialPayments = []; vm.tc = totalCost; vm.paymentFocusIndex = -1; + vm.currencySymbol = currencySymbol; // function mappings to view model vm.getDate = getDate; diff --git a/src/app/components/services/tmpl/dialog_partialpayment.html b/src/app/components/services/tmpl/dialog_partialpayment.html index e400d8c8..9c1f8099 100644 --- a/src/app/components/services/tmpl/dialog_partialpayment.html +++ b/src/app/components/services/tmpl/dialog_partialpayment.html @@ -49,7 +49,7 @@
{{$index+1}}. Received:   -  Rs. +  {{vm.currencySymbol}}
@@ -65,7 +65,7 @@
Remaining:   -  Rs. +  {{vm.currencySymbol}}
diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index ec6d43c5..622cf116 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -65,6 +65,7 @@ vm.label_ivAlignTopAlignment = 'Enter Top Margin:'; vm.label_ivAlignBottomAlignment = 'Enter Bottom Margin:'; vm.taxSettings = []; + vm.currencySymbol = "Rs."; // named assignments to keep track of UI [END] // function maps [BEGIN] @@ -118,6 +119,7 @@ vm.saveTax = saveTax; vm.autoCapitalizeName = autoCapitalizeName; vm.IsTaxFocused = IsTaxFocused; + vm.saveCurrencySymbol = saveCurrencySymbol; // function maps [END] // default execution steps [BEGIN] @@ -137,6 +139,7 @@ loadInvoiceWLogo(); getIvAlignMargins(); getAllTaxSettings(); + getCurrencySymbol(); // changeInvoiceTab(true) // testing purposes amTODO: remove it // changeTaxTab(true); // testing purposes amTODO: remove it @@ -144,6 +147,35 @@ // function definitions + function getCurrencySymbol() { + amSettings.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } + + function saveCurrencySymbol() { + if ((vm.currencySymbol == '') || (vm.currencySymbol == undefined)) + vm.currencySymbol = "Rs."; + amSettings.saveCurrencySymbol(vm.currencySymbol).then(success).catch(failure); + + function success(res) { + if (res.ok) + utils.showSimpleToast('Currency saved successfully!'); + else + failure(); + } + + function failure(err) { + utils.showSimpleToast('Could not save Currency at moment! Try Again Later!'); + } + } + function IsTaxFocused(index) { return (currentTaxFocusIndex == index); } diff --git a/src/app/components/settings/settings.factory.js b/src/app/components/settings/settings.factory.js index 7f4a0843..c236b172 100644 --- a/src/app/components/settings/settings.factory.js +++ b/src/app/components/settings/settings.factory.js @@ -2,7 +2,7 @@ * Factory to fetch and retrieve service tax settings from database * @author ndkcha * @since 0.6.4 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -15,13 +15,68 @@ function SettingsFactory($q, $amRoot, utils, pdbConfig) { var factory = { getDefaultServiceType: getDefaultServiceType, - saveDefaultServiceType: saveDefaultServiceType + saveDefaultServiceType: saveDefaultServiceType, + getCurrencySymbol: getCurrencySymbol, + saveCurrencySymbol: saveCurrencySymbol } return factory; // function definitions + function getCurrencySymbol() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.currency) + tracker.resolve(res.currency); + else + failure('No Currency Symbol Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } + + function saveCurrencySymbol(currency) { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); + } + + function getSettingsObject(res) { + res.currency = currency; + pdbConfig.save(res).then(success).catch(failure); + } + + function writeSettingsObject(err) { + var doc = { + _id: utils.generateUUID('sttngs'), + creator: $amRoot.username, + currency: currency + } + pdbConfig.save(doc).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + function getDefaultServiceType() { var tracker = $q.defer(); $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 85f859d4..69ab9002 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -131,6 +131,12 @@ + + + Currency: + + +
diff --git a/src/app/components/treatments/memberships/memberships-viewall.controller.js b/src/app/components/treatments/memberships/memberships-viewall.controller.js index 371eff8c..acadc5f4 100644 --- a/src/app/components/treatments/memberships/memberships-viewall.controller.js +++ b/src/app/components/treatments/memberships/memberships-viewall.controller.js @@ -2,7 +2,7 @@ * Conroller for View All Memberships component * @author ndkcha * @since 0.5.0 - * @version 0.6.0 + * @version 0.7.0 */ /// @@ -24,6 +24,7 @@ }; vm.selectedMembership = undefined; vm.isExpandAll = false; + vm.currencySymbol = "Rs."; // function maps vm.changeExpandValues = changeExpandValues; @@ -34,9 +35,22 @@ vm.IsMembershipVisible = IsMembershipVisible; // default execution steps + getCurrencySymbol(); getMemberships(changeExpandValues); // function definitions + + function getCurrencySymbol() { + amTreatments.getCurrencySymbol().then(success).catch(failure); + + function success(res) { + vm.currencySymbol = res; + } + + function failure(err) { + vm.currencySymbol = "Rs."; + } + } function changeExpandValues() { vm.isExpandAll = !vm.isExpandAll; diff --git a/src/app/components/treatments/treatments.factory.js b/src/app/components/treatments/treatments.factory.js index b7b0eeeb..e130d0ce 100644 --- a/src/app/components/treatments/treatments.factory.js +++ b/src/app/components/treatments/treatments.factory.js @@ -2,7 +2,7 @@ * Factory that handles database interactions between treatments database and controller * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -31,10 +31,32 @@ getMemberships: getMemberships, getMembershipInfo: getMembershipInfo, deleteMembership: deleteMembership, - saveMembership: saveMembership + saveMembership: saveMembership, + getCurrencySymbol: getCurrencySymbol } return factory; + + function getCurrencySymbol() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.currency) + tracker.resolve(res.currency); + else + failure('No Currency Symbol Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } // retrieve vehicle types from config database function getVehicleTypes() { diff --git a/src/app/components/treatments/treatments_master.html b/src/app/components/treatments/treatments_master.html index 6474167c..67ea7d02 100644 --- a/src/app/components/treatments/treatments_master.html +++ b/src/app/components/treatments/treatments_master.html @@ -147,7 +147,7 @@
{{membership.name}} -
Rs. {{membership.amount}}
+
{{msVm.currencySymbol}} {{membership.amount}}
From d26b0ad172209131bd1383b220530b593f96f4a8 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 13:23:55 +0530 Subject: [PATCH 29/91] Enhancement #167 Tax behaviour while Estimate & Bill --- .../services/services-add.controller.js | 75 ++++++++++++++++++- .../services/services-edit.controller.js | 75 ++++++++++++++++++- src/app/components/services/services_add.html | 2 +- 3 files changed, 147 insertions(+), 5 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index debebaa2..c3766660 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -30,6 +30,7 @@ var serviceTcRo = 0, serviceTcDc = 0; var treatmentTotal = 0, inventoryTotal = 0; var dTreatmentTax, dInventoryTax, dTreatment, dInventory; + var taxSettingsSnap = [], lastServiceState; // vm assignments to keep track of UI related elements vm.vehicleTypeList = []; @@ -109,6 +110,7 @@ }; vm.taxSettings = []; vm.currencySymbol = "Rs."; + vm.areTaxesHidden = false; // named assignments to handle behaviour of UI elements vm.redirect = { @@ -244,8 +246,8 @@ iterateProblem(vm.problem); if (vm.packages) vm.packages.forEach(iteratePackages); - vm.selectedInventories.forEach(iterateProblems); - iterateProblem(vm.inventory); + vm.selectedInventories.forEach(iterateInventories); + iterateInventory(vm.inventory); if (vm.isDiscountApplied) vm.taxSettings.forEach(iterateTaxesAfter); @@ -267,6 +269,29 @@ package.selectedTreatments.forEach(iterateProblems); } + function iterateInventories(inventory) { + if (!inventory.checked) + return; + if (inventory.tax) { + Object.keys(inventory.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += (inventory.tax[tax] * (inventory.qty ? inventory.qty : 1)); + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); + } + } + } + } + function iterateProblems(problem) { if (!problem.checked) return; @@ -312,6 +337,29 @@ } } } + + function iterateInventory(inventory) { + if (inventory.tax) { + Object.keys(inventory.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + if (inventory.tax[tax] > 0) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += (inventory.tax[tax] * (inventory.qty ? inventory.qty : 1)); + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); + } + } + } + } + } } function OnDiscountStateChange() { @@ -522,6 +570,29 @@ function selectServiceState(state) { vm.service.state = state; vm.label_invoice = (vm.service.state == vm.serviceStateList[2]) ? 'Invoice' : 'Send'; + if (vm.service.state != vm.serviceStateList[2]) { + vm.areTaxesHidden = true; + if ((lastServiceState == vm.serviceStateList[2]) || !lastServiceState) { + taxSettingsSnap = []; + vm.taxSettings.forEach(iterateTaxes); + } + } else { + vm.areTaxesHidden = false; + vm.taxSettings = []; + taxSettingsSnap.forEach(restoreTaxes); + } + lastServiceState = vm.service.state; + OnTaxEnabledChange(); + + function restoreTaxes(tax) { + vm.taxSettings.push(tax); + } + + function iterateTaxes(tax) { + var t = $.extend({}, tax) + taxSettingsSnap.push(t); + tax.isTaxApplied = false; + } } function IsServiceStateSelected(state) { diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 78fccd2d..6be67bc2 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -32,6 +32,7 @@ var wasNextDueService = false; var treatmentTotal = 0, inventoryTotal = 0; var dTreatmentTax, dInventoryTax, dTreatment, dInventory; + var taxSettingsSnap = [], lastServiceState; // vm assignments to keep track of UI related elements vm.vehicleTypeList = []; @@ -109,6 +110,7 @@ }; vm.taxSettings = []; vm.currencySymbol = "Rs."; + vm.areTaxesHidden = false; // named assignments to handle behaviour of UI elements vm.redirect = { @@ -229,8 +231,8 @@ iterateProblem(vm.problem); if (vm.packages) vm.packages.forEach(iteratePackages); - vm.selectedInventories.forEach(iterateProblems); - iterateProblem(vm.inventory); + vm.selectedInventories.forEach(iterateInventories); + iterateInventory(vm.inventory); if (vm.isDiscountApplied) vm.taxSettings.forEach(iterateTaxesAfter); @@ -252,6 +254,29 @@ package.selectedTreatments.forEach(iterateProblems); } + function iterateInventories(inventory) { + if (!inventory.checked) + return; + if (inventory.tax) { + Object.keys(inventory.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += (inventory.tax[tax] * (inventory.qty ? inventory.qty : 1)); + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); + } + } + } + } + function iterateProblems(problem) { if (!problem.checked) return; @@ -297,6 +322,29 @@ } } } + + function iterateInventory(inventory) { + if (inventory.tax) { + Object.keys(inventory.tax).forEach(iterateTaxes); + + function iterateTaxes(tax) { + if (inventory.tax[tax] > 0) { + var found = $filter('filter')(vm.taxSettings, { + name: tax + }, true); + + if (found.length == 1) { + if (!found[0].isTaxApplied) + return; + if (!found[0].tax) + found[0].tax = 0; + found[0].tax += (inventory.tax[tax] * (inventory.qty ? inventory.qty : 1)); + found[0].tax = (found[0].tax % 1 != 0) ? parseFloat(found[0].tax.toFixed(2)) : parseInt(found[0].tax); + } + } + } + } + } } function OnDiscountStateChange() { @@ -540,6 +588,29 @@ function selectServiceState(state) { vm.service.state = state; vm.label_invoice = (vm.service.state == vm.serviceStateList[2]) ? 'Invoice' : 'Send'; + if (vm.service.state != vm.serviceStateList[2]) { + vm.areTaxesHidden = true; + if ((lastServiceState == vm.serviceStateList[2]) || !lastServiceState) { + taxSettingsSnap = []; + vm.taxSettings.forEach(iterateTaxes); + } + } else { + vm.areTaxesHidden = false; + vm.taxSettings = []; + taxSettingsSnap.forEach(restoreTaxes); + } + lastServiceState = vm.service.state; + OnTaxEnabledChange(); + + function restoreTaxes(tax) { + vm.taxSettings.push(tax); + } + + function iterateTaxes(tax) { + var t = $.extend({}, tax) + taxSettingsSnap.push(t); + tax.isTaxApplied = false; + } } function IsServiceStateSelected(state) { diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index f340bcdf..230ad3e1 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -668,7 +668,7 @@ Manage Payments - + {{tax.name}} From 40672832dd4862b94055ab7cf0f9a430bdf348a8 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 15:08:09 +0530 Subject: [PATCH 30/91] Feature #161 | Enhancement 1. Need to have provision to remove individual 'Received' entry. --- .../services/services-edit.controller.js | 2 ++ .../tmpl/dialog_partialpayment.controller.js | 15 +++++++++++++++ .../services/tmpl/dialog_partialpayment.html | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 6be67bc2..8716c5d0 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -360,6 +360,7 @@ parent: angular.element(document.body), targetEvent: event, locals: { + currencySymbol: vm.currencySymbol, treatmentLength: (vm.selectedProblems.length ? vm.selectedProblems.length : 0) + ((parseFloat(vm.problem.amount) > 0) ? 1 : 0), partLength: (vm.selectedInventories.length ? vm.selectedInventories.length : 0) + ((parseFloat(vm.inventory.amount) > 0) ? 1 : 0), discountObj: vm.discount, @@ -405,6 +406,7 @@ parent: angular.element(document.body), targetEvent: event, locals: { + currencySymbol: vm.currencySymbol, totalCost: vm.service.cost, partialPayments: vm.service.partialpayment }, diff --git a/src/app/components/services/tmpl/dialog_partialpayment.controller.js b/src/app/components/services/tmpl/dialog_partialpayment.controller.js index db9a6eb1..34e61f1b 100644 --- a/src/app/components/services/tmpl/dialog_partialpayment.controller.js +++ b/src/app/components/services/tmpl/dialog_partialpayment.controller.js @@ -30,6 +30,8 @@ vm.calculateRemaining = calculateRemaining; vm.calculateReceived = calculateReceived; vm.IsPaymentFocusIndex = IsPaymentFocusIndex; + vm.deleteAllEntries = deleteAllEntries; + vm.deleteEntry = deleteEntry; // default execution steps setTimeout(setViewportHeight, 400); @@ -39,6 +41,19 @@ // function definitions + function deleteAllEntries() { + vm.partialPayments = []; + calculateRemaining(); + addPayment(); + } + + function deleteEntry(index) { + vm.partialPayments.splice(index, 1); + calculateRemaining(); + if (vm.partialPayments.length == 0) + addPayment(); + } + function feedExistingPayments() { var keys = (partialPayments) ? Object.keys(partialPayments) : undefined; if (partialPayments && keys && (keys.length > 1)) { diff --git a/src/app/components/services/tmpl/dialog_partialpayment.html b/src/app/components/services/tmpl/dialog_partialpayment.html index 9c1f8099..6cb24a4f 100644 --- a/src/app/components/services/tmpl/dialog_partialpayment.html +++ b/src/app/components/services/tmpl/dialog_partialpayment.html @@ -50,6 +50,10 @@ {{$index+1}}. Received:    {{vm.currencySymbol}} + + delete + Delete this entry! +
@@ -66,6 +70,10 @@ Remaining:    {{vm.currencySymbol}} + + delete_sweep + Delete all entries! +
From 52163743967fe0b0c574dba00548c7f107689bb2 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 6 Aug 2016 15:56:44 +0530 Subject: [PATCH 31/91] Enhancement #168 | 1st UI Change 1. Keep the Side Pan Tab highlighted when it's clicked and opened. --- src/app/app.controller.js | 9 ++- src/app/app.directive.js | 3 +- src/app/app.states.js | 57 ++++++++++++------- src/app/appbar/sidebarView.html | 25 ++++++-- .../components/services/services_viewAll.html | 2 +- 5 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 5692b9da..2388e9c6 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -20,7 +20,7 @@ .controller('appSideBarCtrl', SidebarController); HeaderbarController.$inject = ['$rootScope', '$scope', '$state', '$timeout', '$mdSidenav', 'amRootFactory']; - SidebarController.$inject = ['$scope', '$state', '$http', '$mdSidenav']; + SidebarController.$inject = ['$rootScope', '$scope', '$state', '$http', '$mdSidenav']; LockScreenController.$inject = ['$rootScope', '$state', '$window', 'amRootFactory', 'utils']; function LockScreenController($rootScope, $state, $window, amRootFactory, utils) { @@ -129,7 +129,7 @@ } } - function SidebarController($scope, $state, $http, $mdSidenav) { + function SidebarController($rootScope, $scope, $state, $http, $mdSidenav) { var vm = this; // objects passed to view model @@ -159,6 +159,7 @@ // map functions to view model vm.openState = openState; vm.doUpdate = doUpdate; + vm.isSelected = isSelected; // default execution steps ipcRenderer.on('automint-updated', listenToAutomintUpdates); @@ -166,6 +167,10 @@ // function definitions + function isSelected(index) { + return ($rootScope.sidebarItemIndex == index); + } + function listenToAutomintUpdates(event, arg) { vm.isAutomintUpdateAvailable = arg; $scope.$apply(); diff --git a/src/app/app.directive.js b/src/app/app.directive.js index 2992f95e..b5d4c81d 100644 --- a/src/app/app.directive.js +++ b/src/app/app.directive.js @@ -2,7 +2,7 @@ * Closure for root level directives * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -53,6 +53,7 @@ function callback() { var checkTitle = (toState.data && toState.data.pageTitle); + $rootScope.sidebarItemIndex = (toState.data && (toState.data.sidebarItemIndex != undefined)) ? toState.data.sidebarItemIndex : -1; $rootScope.module_title = checkTitle ? toState.data.pageTitle : ''; $rootScope.page_title = checkTitle ? toState.data.pageTitle + ' - ' + default_title : default_title; } diff --git a/src/app/app.states.js b/src/app/app.states.js index 3470d07a..32d5a9fa 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -62,7 +62,8 @@ deps: ['$ocLazyLoad', loadDashboardDeps] }, data: { - pageTitle: 'Dashboard' + pageTitle: 'Dashboard', + sidebarItemIndex: 0 } }) // customers @@ -83,7 +84,8 @@ deps: ['$ocLazyLoad', loadCuRADeps] }, data: { - pageTitle: 'All Customers' + pageTitle: 'All Customers', + sidebarItemIndex: 1 } }) .state('restricted.customers.add', { @@ -95,7 +97,8 @@ deps: ['$ocLazyLoad', loadCuCIDeps] }, data: { - pageTitle: 'Add a Customer' + pageTitle: 'Add a Customer', + sidebarItemIndex: 1 } }) .state('restricted.customers.edit', { @@ -111,7 +114,8 @@ deps: ['$ocLazyLoad', loadCuUIDeps] }, data: { - pageTitle: 'Edit Customer' + pageTitle: 'Edit Customer', + sidebarItemIndex: 1 } }) // treatments @@ -135,7 +139,8 @@ deps: ['$ocLazyLoad', loadTrMasterDeps] }, data: { - pageTitle: 'All Treatments' + pageTitle: 'All Treatments', + sidebarItemIndex: 2 } }) .state('restricted.treatments.add', { @@ -147,7 +152,8 @@ deps: ['$ocLazyLoad', loadTrCIDeps] }, data: { - pageTitle: 'Add a Treatment' + pageTitle: 'Add a Treatment', + sidebarItemIndex: 2 } }) .state('restricted.treatments.edit', { @@ -162,7 +168,8 @@ deps: ['$ocLazyLoad', loadTrUIDeps] }, data: { - pageTitle: 'Edit Treatment' + pageTitle: 'Edit Treatment', + sidebarItemIndex: 2 } }) // services @@ -183,7 +190,8 @@ deps: ['$ocLazyLoad', loadSeRADeps] }, data: { - pageTitle: 'All Services' + pageTitle: 'All Services', + sidebarItemIndex: -1 } }) .state('restricted.services.add', { @@ -198,7 +206,8 @@ deps: ['$ocLazyLoad', loadSeCIDeps] }, data: { - pageTitle: 'Add a Service' + pageTitle: 'Add a Service', + sidebarItemIndex: -1 } }) .state('restricted.services.edit', { @@ -216,7 +225,8 @@ deps: ['$ocLazyLoad', loadSeUIDeps] }, data: { - pageTitle: 'Edit Service' + pageTitle: 'Edit Service', + sidebarItemIndex: -1 } }) // settings @@ -232,7 +242,8 @@ deps: ['$ocLazyLoad', loadSettingsDeps] }, data: { - pageTitle: 'Settings' + pageTitle: 'Settings', + sidebarItemIndex: 4 } }) // invoices @@ -259,7 +270,8 @@ deps: ['$ocLazyLoad', loadIvRIDeps] }, data: { - pageTitle: 'View Invoice' + pageTitle: 'View Invoice', + sidebarItemIndex: -1 } }) // packages @@ -280,7 +292,8 @@ deps: ['$ocLazyLoad', loadPkCIDeps] }, data: { - pageTitle: 'Add Package' + pageTitle: 'Add Package', + sidebarItemIndex: 2 } }) .state('restricted.packages.edit', { @@ -295,7 +308,8 @@ deps: ['$ocLazyLoad', loadPkUIDeps] }, data: { - pageTitle: 'Edit Package' + pageTitle: 'Edit Package', + sidebarItemIndex: 2 } }) .state('restricted.memberships', { @@ -315,7 +329,8 @@ deps: ['$ocLazyLoad', loadMsCIDeps] }, data: { - pageTitle: 'Add Membership' + pageTitle: 'Add Membership', + sidebarItemIndex: 2 } }) .state('restricted.memberships.edit', { @@ -330,7 +345,8 @@ deps: ['$ocLazyLoad', loadMsUIDeps] }, data: { - pageTitle: 'Edit Membership' + pageTitle: 'Edit Membership', + sidebarItemIndex: 2 } }) .state('restricted.inventory', { @@ -350,7 +366,8 @@ deps: ['$ocLazyLoad', loadInRADeps] }, data: { - pageTitle: 'All Inventories' + pageTitle: 'All Inventories', + sidebarItemIndex: 3 } }) .state('restricted.inventory.add', { @@ -362,7 +379,8 @@ deps: ['$ocLazyLoad', loadInCIDeps] }, data: { - pageTitle: 'Add Inventory' + pageTitle: 'Add Inventory', + sidebarItemIndex: 3 } }) .state('restricted.inventory.edit', { @@ -377,7 +395,8 @@ deps: ['$ocLazyLoad', loadInUIDeps] }, data: { - pageTitle: 'Edit Inventory' + pageTitle: 'Edit Inventory', + sidebarItemIndex: 3 } }); diff --git a/src/app/appbar/sidebarView.html b/src/app/appbar/sidebarView.html index cfdde584..506db448 100644 --- a/src/app/appbar/sidebarView.html +++ b/src/app/appbar/sidebarView.html @@ -1,13 +1,28 @@ + -
-
-
{{ item.icon }}
-
{{ item.name }}
-
+
+   +
{{ item.icon }}
+
{{ item.name }}
diff --git a/src/app/components/services/services_viewAll.html b/src/app/components/services/services_viewAll.html index 358f50fb..e7926439 100644 --- a/src/app/components/services/services_viewAll.html +++ b/src/app/components/services/services_viewAll.html @@ -48,7 +48,7 @@ {{service.srvc_status}} {{service.srvc_state}} - + delete From d5223bb0ee08846bd3c554b4bec6fd832ae62065 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 7 Aug 2016 12:38:31 +0530 Subject: [PATCH 32/91] Enhancement #158 | Beta 1. Multiple page support in invoices print --- .../invoices/invoices-view.controller.js | 259 +++++++++++++++++- .../components/invoices/invoices.factory.js | 24 +- .../components/invoices/invoices_view.html | 57 ++-- .../services/services-add.controller.js | 14 +- .../services/services-edit.controller.js | 14 +- src/app/components/services/services_add.html | 2 +- .../settings/settings-invoices.factory.js | 67 ++++- .../settings/settings.controller.js | 31 +++ src/app/components/settings/settings.html | 12 + 9 files changed, 423 insertions(+), 57 deletions(-) diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 989f57ec..d57f928c 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -26,6 +26,7 @@ var vm = this; var oCustomerEmail; + var rowCount = 0, membershipTreatmentCount = 0, packageTreatmentCount = 0, treatmentCount = 0, inventoryCount = 0, packageCount = {}, membershipCount = {}; // named assignments to keep track of UI elements vm.isFabOpen = true; @@ -33,6 +34,9 @@ vm.subtotal = 0; vm.currencySymbol = "Rs."; vm.taxSettings = []; + vm.invoicePageSizeList = ['Single Page', 'A4']; + vm.invoicePageSize = vm.invoicePageSizeList[0]; + vm.pages = []; // function maps vm.printInvoice = printInvoice; @@ -54,6 +58,8 @@ vm.IsSubtotalEnabled = IsSubtotalEnabled; vm.calculateSubtotal = calculateSubtotal; vm.IsTaxEnabled = IsTaxEnabled; + vm.IsHeaderAvailable = IsHeaderAvailable; + vm.IsLastPage = IsLastPage; // default execution steps if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { @@ -65,19 +71,227 @@ fillInvoiceDetails(); loadInvoiceWLogo(); getIvAlignMargins(); + getInvoicePageSize(); // electron watchers eIpc.on('am-invoice-mail-sent', OnInvoiceMailSent); // function definitions + function IsLastPage(index) { + return (index == (vm.pages.length - 1)); + } + + function doPagination() { + vm.pages = []; + if (rowCount > 15) { + var pageCount = Math.ceil(rowCount / 15); + var mtc = membershipTreatmentCount, ptc = packageTreatmentCount, tc = treatmentCount, ic = inventoryCount; + for (var i = 0; i < pageCount; i++) { + if (mtc > 15) { + var ms = 0, mst = 0; + Object.keys(membershipCount).forEach(iterateMemberships); + pushPages(ms, 0, 0, 0); + mtc -= mst; + continue; + + function iterateMemberships(membership) { + mst += membershipCount[membership]; + if (mst < 15) { + ms++; + delete membershipCount[membership]; + } + } + } + var total = 0; + total = mtc + ptc; + if (total > 15) { + var ps = 0, ms = 0, pst = 0, mst = 0; + Object.keys(membershipCount).forEach(iterateMemberships); + Object.keys(packageCount).forEach(iteratePackages); + ptc = 15 - mtc; + pushPages(ms, ps, 0, 0); + mtc = 0; + continue; + + function iterateMemberships(membership) { + mst += membershipCount[membership]; + if (mst < 15) { + ms++; + delete membershipCount[membership]; + } + } + + function iteratePackages(package) { + pst += packageCount[package]; + if (pst < 15) { + ps++; + delete packageCount[package]; + } + } + } + total = mtc + ptc + tc; + if (total > 15) { + var ps = 0, ms = 0, pst = 0, mst = 0; + Object.keys(membershipCount).forEach(iterateMemberships); + Object.keys(packageCount).forEach(iteratePackages); + tc = 15 - mtc - ptc; + pushPages(ms, ps, tc, 0); + mtc = 0; + ptc = 0; + continue; + + function iterateMemberships(membership) { + mst += membershipCount[membership]; + if (mst < 15) { + ms++; + delete membershipCount[membership]; + } + } + + function iteratePackages(package) { + pst += packageCount[package]; + if (pst < 15) { + ps++; + delete packageCount[package]; + } + } + } + total = mtc + ptc + tc + ic; + if (total > 15) { + var ps = 0, ms = 0, pst = 0, mst = 0; + Object.keys(membershipCount).forEach(iterateMemberships); + Object.keys(packageCount).forEach(iteratePackages); + ic = 15 - mtc - ptc - tc; + pushPages(ms, ps, tc, ic); + mtc = 0; + ptc = 0; + tc = 0; + ic = inventoryCount - ic; + continue; + + function iterateMemberships(membership) { + mst += membershipCount[membership]; + if (mst < 15) { + ms++; + delete membershipCount[membership]; + } + } + + function iteratePackages(package) { + pst += packageCount[package]; + if (pst < 15) { + ps++; + delete packageCount[package]; + } + } + } + var ps = 0, ms = 0, pst = 0, mst = 0; + Object.keys(membershipCount).forEach(iterateMemberships); + Object.keys(packageCount).forEach(iteratePackages); + pushPages(ms, ps, tc, ic); + + function iterateMemberships(membership) { + mst += membershipCount[membership]; + if (mst < 15) { + ms++; + delete membershipCount[membership]; + } + } + + function iteratePackages(package) { + pst += packageCount[package]; + if (pst < 15) { + ps++; + delete packageCount[package]; + } + } + } + } else { + var ps = 0, ms = 0, pst = 0, mst = 0; + Object.keys(membershipCount).forEach(iterateMemberships); + Object.keys(packageCount).forEach(iteratePackages); + pushPages(ms, ps, treatmentCount, inventoryCount); + + function iterateMemberships(membership) { + mst += membershipCount[membership]; + if (mst < 15) { + ms++; + delete membershipCount[membership]; + } + } + + function iteratePackages(package) { + pst += packageCount[package]; + if (pst < 15) { + ps++; + delete packageCount[package]; + } + } + } + + function pushPages(pmtc, pptc, pagetc, pic) { + var index = vm.pages.length; + vm.pages.push({ + index: index, + membershipTreatmentCount: pmtc, + packageTreatmentCount: pptc, + treatmentCount: pagetc, + inventoryCount: pic + }); + } + } + + function IsHeaderAvailable() { + return ((vm.ivSettings && vm.ivSettings.display && vm.ivSettings.display.workshopDetails) || (vm.ivSettings && vm.ivSettings.display && vm.ivSettings.display.workshopLogo)); + } + + function getInvoicePageSize() { + amInvoices.getInvoicePageSize().then(success).catch(failure); + + function success(res) { + vm.invoicePageSize = res; + switch (res) { + case "A4": + A4Size(); + doPagination(); + break; + default: + normalPageSize(); + vm.pages.push({ + index: 0, + membershipTreatmentCount: membershipTreatmentCount, + packageTreatmentCount: packageTreatmentCount, + treatmentCount: treatmentCount, + inventoryCount: inventoryCount + }); + break; + } + } + + function failure(err) { + vm.invoicePageSize = vm.invoicePageSizeList[0]; + normalPageSize(); + } + } + + function normalPageSize() { + vm.pageWidth = "100%"; + vm.pageHeight = "auto"; + } + + function A4Size() { + vm.pageWidth = "210mm"; + vm.pageHeight = "297mm"; + } + function IsTaxEnabled() { var iTe = false; vm.taxSettings.forEach(iterateTaxes); return iTe; function iterateTaxes(tax) { - if (tax.isTaxApplied) + if (tax.isTaxApplied && (tax.tax > 0)) iTe = true; } } @@ -222,21 +436,47 @@ } function fillServiceDetails(res) { + rowCount = 0, membershipTreatmentCount = 0, packageTreatmentCount = 0, treatmentCount = 0, inventoryCount = 0; vm.user = res.user; oCustomerEmail = res.user.email; vm.vehicle = res.vehicle; vm.service = res.service; vm.isRoundOff = (vm.service.roundoff != undefined); vm.isDiscountApplied = (vm.service.discount != undefined); - if (vm.isDiscountApplied) { + if (vm.isDiscountApplied) vm.discountValue = (vm.service.discount.amount || vm.service.discount.total); - } if (res.service.taxes) Object.keys(res.service.taxes).forEach(iterateTaxes); - console.log(vm.taxSettings); calculateInventoryValues(); + if (vm.service.memberships) + Object.keys(vm.service.memberships).forEach(iterateMemberships); + if (vm.service.packages) + Object.keys(vm.service.packages).forEach(iteratePackages); + if (vm.service.problems) { + rowCount += vm.service.problems.length; + treatmentCount += vm.service.problems.length; + } + if (vm.service.inventories) { + rowCount += vm.service.inventories.length; + inventoryCount += vm.service.inventories.length; + } + console.log(rowCount); calculateSubtotal(); + function iteratePackages(package) { + var tempp = vm.service.packages[package]; + packageCount[package] = Object.keys(tempp.treatments).length; + rowCount += Object.keys(tempp.treatments).length; + packageTreatmentCount += Object.keys(tempp.treatments).length; + } + + function iterateMemberships(membership) { + var tempm = vm.service.memberships[membership]; + membershipCount[membership] = Object.keys(tempm.treatments).length; + rowCount += Object.keys(tempm.treatments).length; + membershipTreatmentCount += Object.keys(tempm.treatments).length; + } + function iterateTaxes(tax) { var t = res.service.taxes[tax]; @@ -352,7 +592,7 @@ amInScTw.src = ic.toDataURL(); } } - var printObj = document.getElementById('am-invoice-body'); + var printObj = document.getElementById('am-invoice-mail-body'); ammPrint.doPrint(printObj.innerHTML); if (vm.workshop && vm.workshop.social && vm.workshop.social.enabled) { if (IsSocialFacebook()) { @@ -403,11 +643,6 @@ utils.showSimpleToast(vm.user.name + '\'s email has not been set. Email can not be sent!'); return; } - var t = document.getElementById('am-invoice-body'); - if (vm.alignmentMargins && vm.alignmentMargins.enabled) { - t.style.paddingTop = 0; - t.style.paddingBottom = 0; - } var amInScFb = document.getElementById('am-invoice-social-facebook'); var amInScIn = document.getElementById('am-invoice-social-instagram'); var amInScTw = document.getElementById('am-invoice-social-twitter'); @@ -448,10 +683,6 @@ } if (vm.ivSettings.display.workshopLogo) addInvoiceWLogo(); - if (vm.alignmentMargins && vm.alignmentMargins.enabled) { - t.style.paddingTop = vm.alignmentMargins.top + 'cm'; - t.style.paddingBottom = vm.alignmentMargins.bottom + 'cm'; - } } function goBack() { diff --git a/src/app/components/invoices/invoices.factory.js b/src/app/components/invoices/invoices.factory.js index 3454f76f..c56b4e82 100644 --- a/src/app/components/invoices/invoices.factory.js +++ b/src/app/components/invoices/invoices.factory.js @@ -21,13 +21,35 @@ getIvSettings: getIvSettings, getIvAlignMargins: getIvAlignMargins, saveCustomerEmail: saveCustomerEmail, - getCurrencySymbol: getCurrencySymbol + getCurrencySymbol: getCurrencySymbol, + getInvoicePageSize: getInvoicePageSize } return factory; // function definitions + function getInvoicePageSize() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.settings && res.settings.invoices && res.settings.invoices.pageSize) + tracker.resolve(res.settings.invoices.pageSize); + else + failure('No PageSize Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } + function getCurrencySymbol() { var tracker = $q.defer(); $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index e92a87c6..fa61627b 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -13,29 +13,28 @@ arrow_back - Back
email - Email + Email print - Print + Print sms - SMS + SMS edit - Edit Service + Edit Service
- -
-
+ +
+
@@ -51,19 +50,19 @@ .am-invoice-table td { font-size: small; - padding: 1rem; + padding: 12px; } .am-invoice-table .m-d { - padding: 1rem 1rem 0.3rem 1rem !important; + padding: 12px 12px 2px 12px !important; } .am-invoice-table .m-dt { - padding: 0.3rem 1rem 0.3rem 1rem !important; + padding: 2px 12px 2px 12px !important; font-size: 9pt; } .am-invoice-table .m-dtl { - padding: 0.3rem 1rem 1rem 1rem !important; + padding: 2px 12px 12px 12px !important; font-size: 9pt; } @@ -82,17 +81,17 @@ .am-invoice-table .total td { border-top: 1px solid #ECEFF1; font-weight: 800; - padding: 0.5rem 1rem 1rem 1rem !important; + padding: 8px 12px 12px 12px !important; } .am-invoice-table .subt td { border-top: 1px solid #ECEFF1; - padding: 0.5rem 1rem 0.5rem 1rem !important; + padding: 8px 12px 8px 12px !important; } .am-invoice-table .tax td { border-top: 2px solid #37474F; - padding: 1rem 1rem 0.5rem 1rem !important; + padding: 12px 12px 8px 12px !important; } .am-invoice-link { @@ -140,8 +139,8 @@ - -
+ +
@@ -155,8 +154,8 @@

{{vm.workshop.name}}

-
-
+
+
@@ -177,7 +176,7 @@

{{vm.workshop.name}}

- + @@ -189,7 +188,7 @@

{{vm.workshop.name}}

- + @@ -201,43 +200,43 @@

{{vm.workshop.name}}

- + - + - + - - + + - + - + - + diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index c3766660..6e09a381 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -243,11 +243,13 @@ function calculateTaxes() { vm.taxSettings.forEach(iterateTaxes); vm.service.problems.forEach(iterateProblems); - iterateProblem(vm.problem); + if (vm.problem.details != '') + iterateProblem(vm.problem); if (vm.packages) vm.packages.forEach(iteratePackages); vm.selectedInventories.forEach(iterateInventories); - iterateInventory(vm.inventory); + if (vm.inventory.name != '') + iterateInventory(vm.inventory); if (vm.isDiscountApplied) vm.taxSettings.forEach(iterateTaxesAfter); @@ -503,9 +505,11 @@ var totalCost = 0; treatmentTotal = 0, inventoryTotal = 0; vm.service.problems.forEach(iterateProblem); - totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; + if (vm.problem.details != '') + totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; vm.selectedInventories.forEach(iterateInventories); - totalCost += (vm.inventory.rate) ? (parseFloat(vm.inventory.rate) * parseFloat(vm.inventory.qty)) : 0; + if (vm.inventory.name != '') + totalCost += (vm.inventory.rate) ? (parseFloat(vm.inventory.rate) * parseFloat(vm.inventory.qty)) : 0; if (vm.serviceType == vm.serviceTypeList[1]) { vm.packages.forEach(iteratePackages); } @@ -816,7 +820,7 @@ vm.inventory.name = ''; vm.inventory.amount = ''; vm.inventory.rate = ''; - vm.inventory.tax = ''; + vm.inventory.tax = {}; vm.inventory.qty = 1; vm.inventory.total = ''; calculate(); diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 8716c5d0..3e546b60 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -228,11 +228,13 @@ function calculateTaxes() { vm.taxSettings.forEach(iterateTaxes); vm.service.problems.forEach(iterateProblems); - iterateProblem(vm.problem); + if (vm.problem.details != '') + iterateProblem(vm.problem); if (vm.packages) vm.packages.forEach(iteratePackages); vm.selectedInventories.forEach(iterateInventories); - iterateInventory(vm.inventory); + if (vm.inventory.name != '') + iterateInventory(vm.inventory); if (vm.isDiscountApplied) vm.taxSettings.forEach(iterateTaxesAfter); @@ -488,9 +490,11 @@ var totalCost = 0; treatmentTotal = 0, inventoryTotal = 0; vm.service.problems.forEach(iterateProblem); - totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; + if (vm.problem.details != '') + totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; vm.selectedInventories.forEach(iterateInventories); - totalCost += (vm.inventory.rate) ? (parseFloat(vm.inventory.rate) * parseFloat(vm.inventory.qty)) : 0; + if (vm.inventory.name != '') + totalCost += (vm.inventory.rate) ? (parseFloat(vm.inventory.rate) * parseFloat(vm.inventory.qty)) : 0; if (vm.serviceType == vm.serviceTypeList[1]) { vm.packages.forEach(iteratePackages); } @@ -815,7 +819,7 @@ vm.inventory.name = ''; vm.inventory.amount = ''; vm.inventory.rate = ''; - vm.inventory.tax = ''; + vm.inventory.tax = {}; vm.inventory.qty = 1; vm.inventory.total = ''; if (isFromAutocomplete || foundExisting.length != 0) diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 230ad3e1..ac65da1d 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -466,7 +466,7 @@ -
+
No Packages Found!
diff --git a/src/app/components/settings/settings-invoices.factory.js b/src/app/components/settings/settings-invoices.factory.js index eddbc959..5c1b13bc 100644 --- a/src/app/components/settings/settings-invoices.factory.js +++ b/src/app/components/settings/settings-invoices.factory.js @@ -2,7 +2,7 @@ * Factory to fetch and retrieve invoice settings from database * @author ndkcha * @since 0.5.0 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -25,12 +25,75 @@ saveIvAlignMargins : saveIvAlignMargins, getIvAlignMargins: getIvAlignMargins, changeLastJobCardNo: changeLastJobCardNo, - changeLastEstimateNo: changeLastEstimateNo + changeLastEstimateNo: changeLastEstimateNo, + saveInvoicePageSize: saveInvoicePageSize, + getInvoicePageSize: getInvoicePageSize } return factory; // function definitions + + function saveInvoicePageSize(pageSize) { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsDoc); + } + + function getSettingsObject(res) { + if (!res.settings) + res.settings = {}; + if (!res.settings.invoices) + res.settings.invoices = {}; + res.settings.invoices.pageSize = pageSize; + pdbConfig.save(res).then(success).catch(failure); + } + + function writeSettingsDoc(err) { + var doc = { + _id: utils.generateUUID('sttngs'), + creator: $amRoot.username, + settings: { + invoices: { + pageSize: pageSize + } + } + } + pdbConfig.save(doc).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function getInvoicePageSize() { + var tracker = $q.defer(); + $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); + } + + function getSettingsObject(res) { + if (res.settings && res.settings.invoices && res.settings.invoices.pageSize) + tracker.resolve(res.settings.invoices.pageSize); + else + failure('No PageSize Found!'); + } + + function failure(err) { + tracker.reject(err); + } + } // get workshop details from config database function getWorkshopDetails() { diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 622cf116..a92c7204 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -66,6 +66,8 @@ vm.label_ivAlignBottomAlignment = 'Enter Bottom Margin:'; vm.taxSettings = []; vm.currencySymbol = "Rs."; + vm.invoicePageSizeList = ['Single Page', 'A4']; + vm.invoicePageSize = vm.invoicePageSizeList[0]; // named assignments to keep track of UI [END] // function maps [BEGIN] @@ -120,6 +122,7 @@ vm.autoCapitalizeName = autoCapitalizeName; vm.IsTaxFocused = IsTaxFocused; vm.saveCurrencySymbol = saveCurrencySymbol; + vm.saveInvoicePageSize = saveInvoicePageSize; // function maps [END] // default execution steps [BEGIN] @@ -140,6 +143,7 @@ getIvAlignMargins(); getAllTaxSettings(); getCurrencySymbol(); + getInvoicePageSize(); // changeInvoiceTab(true) // testing purposes amTODO: remove it // changeTaxTab(true); // testing purposes amTODO: remove it @@ -147,6 +151,33 @@ // function definitions + function getInvoicePageSize() { + amIvSettings.getInvoicePageSize().then(success).catch(failure); + + function success(res) { + vm.invoicePageSize = res; + } + + function failure(err) { + vm.invoicePageSize = vm.invoicePageSizeList[0]; + } + } + + function saveInvoicePageSize() { + amIvSettings.saveInvoicePageSize(vm.invoicePageSize).then(success).catch(failure); + + function success(res) { + if (res.ok) + utils.showSimpleToast('Invoice settings saved successfully'); + else + failure(); + } + + function failure(err) { + utils.showSimpleToast('Could not save invoice settings! Please try again!'); + } + } + function getCurrencySymbol() { amSettings.getCurrencySymbol().then(success).catch(failure); diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 69ab9002..c7edea48 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -374,6 +374,18 @@ + + +
+ Print Settings + + Page Size:  + + {{page}} + +
+
+
From 080cc97a716a1cc390c3b2aa190329cf7053b61a Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 7 Aug 2016 13:38:42 +0530 Subject: [PATCH 33/91] Enhancement #158 | Page Numbers --- .../invoices/invoices-view.controller.js | 37 +++++++++++++------ .../components/invoices/invoices_view.html | 3 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index d57f928c..972e9ca6 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -60,6 +60,7 @@ vm.IsTaxEnabled = IsTaxEnabled; vm.IsHeaderAvailable = IsHeaderAvailable; vm.IsLastPage = IsLastPage; + vm.currentPage = currentPage; // default execution steps if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { @@ -78,6 +79,10 @@ // function definitions + function currentPage(index) { + return (index + 1); + } + function IsLastPage(index) { return (index == (vm.pages.length - 1)); } @@ -551,7 +556,7 @@ function success(res) { vm.ivSettings = res; if (res.display.workshopLogo) - addInvoiceWLogo(); + setTimeout(addInvoiceWLogo, 1000); } function failure(err) { @@ -705,20 +710,28 @@ } function addInvoiceWLogo() { - var elem = document.getElementById('am-invoice-w-logo-holder'); - if (elem.hasChildNodes()) - return; - var x = document.createElement("IMG"); - x.setAttribute("src", vm.invoiceWLogo); - x.setAttribute("width", "250"); - x.setAttribute("height", "125"); - elem.appendChild(x); + var elems = document.getElementsByName('am-invoice-w-logo-holder'); + elems.forEach(iterateElements); + + function iterateElements(elem) { + if (elem.hasChildNodes()) + return; + var x = document.createElement("IMG"); + x.setAttribute("src", vm.invoiceWLogo); + x.setAttribute("width", "250"); + x.setAttribute("height", "125"); + elem.appendChild(x); + } } function removeInvoiceWLogo() { - var elem = document.getElementById('am-invoice-w-logo-holder'); - while (elem.firstChild) { - elem.removeChild(elem.firstChild); + var elems = document.getElementsByName('am-invoice-w-logo-holder'); + elems.forEach(iterateElements); + + function iterateElements(elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } } } } diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index fa61627b..fc4cb5c2 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -151,7 +151,7 @@

{{vm.workshop.name}}

{{vm.workshop.label_phone}} {{vm.workshop.phone}}
-
+

@@ -273,6 +273,7 @@

{{vm.workshop.name}}

+
Page {{vm.currentPage(page.index)}} of {{vm.pages.length}}
From 7200b61c1646c860a36c0a850543b9abc71deb5b Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 7 Aug 2016 13:44:58 +0530 Subject: [PATCH 34/91] Enhancement #158 | Single Page, No Numbers --- src/app/components/invoices/invoices-view.controller.js | 5 +++++ src/app/components/invoices/invoices_view.html | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 972e9ca6..5bbf868f 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -61,6 +61,7 @@ vm.IsHeaderAvailable = IsHeaderAvailable; vm.IsLastPage = IsLastPage; vm.currentPage = currentPage; + vm.IsNotSinglePage = IsNotSinglePage; // default execution steps if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { @@ -79,6 +80,10 @@ // function definitions + function IsNotSinglePage() { + return (vm.pages.length > 1); + } + function currentPage(index) { return (index + 1); } diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index fc4cb5c2..5789fe49 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -273,7 +273,7 @@

{{vm.workshop.name}}

-
Page {{vm.currentPage(page.index)}} of {{vm.pages.length}}
+
Page {{vm.currentPage(page.index)}} of {{vm.pages.length}}
From 604f7c494fd4e28ae506aab8f88d0b0778d83108 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 7 Aug 2016 15:27:47 +0530 Subject: [PATCH 35/91] Enhancement #168 | UI Changes [cntd] 7. Show past reminders in Red color, in Next Due Service Reminder list. 8. Hyperlink on Cover Logo image to Dashboard. 11. put the version number at centre with no 'v'; only '0.7.0'. Rename 'APPLY UPDATE' to 'UPDATE NOW' and keep it right side of the version number. 12. Rename 'App Data' to 'App Data Location' in General Settings. 14. Processing gif should be displayed with changes after entering mobile number and pressing tab. 15. Delete button in Tax settings is not properly placed. Limit tax name before 'Applicable To' label. And change it to 'Applicable on'. Put Tax Percentage above Exclusive Inclusive switch. --- src/app/app.controller.js | 6 ++++ src/app/appbar/sidebarView.html | 6 ++-- .../dashboard/dashboard.controller-deps.js | 5 ++++ .../dashboard/dashboard.controller.js | 5 ++++ src/app/components/dashboard/dashboard.html | 6 ++-- .../tmpl/dialog_nextdueservices.tmpl.html | 8 +++--- src/app/components/services/services_add.html | 13 ++++++++- .../components/services/services_viewAll.html | 2 +- src/app/components/settings/settings.html | 28 +++++++++---------- 9 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 2388e9c6..b2760ee2 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -160,6 +160,7 @@ vm.openState = openState; vm.doUpdate = doUpdate; vm.isSelected = isSelected; + vm.goToDashboard = goToDashboard; // default execution steps ipcRenderer.on('automint-updated', listenToAutomintUpdates); @@ -167,6 +168,11 @@ // function definitions + function goToDashboard() { + $mdSidenav('main-nav-left').close() + $state.go(vm.items[0].state); + } + function isSelected(index) { return ($rootScope.sidebarItemIndex == index); } diff --git a/src/app/appbar/sidebarView.html b/src/app/appbar/sidebarView.html index 506db448..5419516f 100644 --- a/src/app/appbar/sidebarView.html +++ b/src/app/appbar/sidebarView.html @@ -15,7 +15,7 @@ } - + @@ -28,8 +28,8 @@
+ {{sidebarVm.automintVersion}} - Apply Update + Update Now - v{{sidebarVm.automintVersion}}
\ No newline at end of file diff --git a/src/app/components/dashboard/dashboard.controller-deps.js b/src/app/components/dashboard/dashboard.controller-deps.js index eff103b7..1610640a 100644 --- a/src/app/components/dashboard/dashboard.controller-deps.js +++ b/src/app/components/dashboard/dashboard.controller-deps.js @@ -231,12 +231,17 @@ vm.editCustomer = editCustomer; vm.deleteServiceReminder = deleteServiceReminder; vm.changeDate = changeDate; + vm.IsReminderInPast = IsReminderInPast; // default execution steps manageCustomers(dueCustomers); // function definitions + function IsReminderInPast(date) { + return (moment().format().localeCompare(moment(date).format()) > 0); + } + function changeDate(customer) { amDashboard.changeServiceReminderDate(customer.cstmr_id, customer.vhcl_id, moment(customer.vhcl_nextdue).format()).then(success).catch(failure); diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index 2968af63..9d134fc8 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -97,6 +97,7 @@ vm.IsNextDueFieldLong = IsNextDueFieldLong; vm.openNextDueServices = openNextDueServices; vm.IsNoNextDueReminders = IsNoNextDueReminders; + vm.IsReminderInPast = IsReminderInPast; // default execution steps initCurrentTimeSet(); @@ -107,6 +108,10 @@ // function definitions + function IsReminderInPast(date) { + return (moment().format().localeCompare(date) > 0); + } + function getCurrencySymbol() { amDashboard.getCurrencySymbol().then(success).catch(failure); diff --git a/src/app/components/dashboard/dashboard.html b/src/app/components/dashboard/dashboard.html index 5baae372..5dd90a20 100644 --- a/src/app/components/dashboard/dashboard.html +++ b/src/app/components/dashboard/dashboard.html @@ -177,9 +177,9 @@
Qty Amount
{{key}}  
{{package.name}} {{vm.currencySymbol}} {{treatment.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}}
{{problem.details}} {{vm.currencySymbol}} {{problem.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}}
{{inventory.name}} {{vm.currencySymbol}} {{inventory.rate | number: (vm.vatSettings.applyTax) ? ((vm.vatSettings.inclusive) ? 2 : 0) : 0}} {{inventory.qty}} {{vm.currencySymbol}} {{inventory.total}}
Sub Total {{vm.currencySymbol}} {{vm.subtotal}}
Discount ({{vm.service.discount.percent}}%)
Discount {{vm.currencySymbol}} {{vm.discountValue}}
{{tax.name}} ({{tax.percent}}%) {{vm.currencySymbol}} {{tax.tax}}
Round Off {{vm.currencySymbol}} {{vm.service.roundoff}}
Total
- - - + + +
{{vm.getNxtDueField(customer.cstmr_name)}}{{customer.cstmr_name}}{{vm.getNxtDueField(customer.cstmr_mobile)}}{{customer.cstmr_mobile}}{{vm.getNextDueVehicle(customer.vhcl_manuf,customer.vhcl_model,customer.vhcl_reg)}}{{vm.getNextDueVehicle(customer.vhcl_manuf,customer.vhcl_model,customer.vhcl_reg, true)}}{{vm.getNxtDueField(customer.cstmr_name)}}{{customer.cstmr_name}}{{vm.getNxtDueField(customer.cstmr_mobile)}}{{customer.cstmr_mobile}}{{vm.getNextDueVehicle(customer.vhcl_manuf,customer.vhcl_model,customer.vhcl_reg)}}{{vm.getNextDueVehicle(customer.vhcl_manuf,customer.vhcl_model,customer.vhcl_reg, true)}}
diff --git a/src/app/components/dashboard/tmpl/dialog_nextdueservices.tmpl.html b/src/app/components/dashboard/tmpl/dialog_nextdueservices.tmpl.html index 9b97c43a..5ecd657a 100644 --- a/src/app/components/dashboard/tmpl/dialog_nextdueservices.tmpl.html +++ b/src/app/components/dashboard/tmpl/dialog_nextdueservices.tmpl.html @@ -53,12 +53,12 @@ - {{customer.cstmr_name}} - {{customer.cstmr_mobile}} - {{customer.vhcl_manuf + ' ' + customer.vhcl_model}} ({{customer.vhcl_reg}}) + {{customer.cstmr_name}} + {{customer.cstmr_mobile}} + {{customer.vhcl_manuf + ' ' + customer.vhcl_model}} ({{customer.vhcl_reg}}) - {{vm.getDate(customer.vhcl_nextdue)}} + {{vm.getDate(customer.vhcl_nextdue)}} diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index ac65da1d..80a322cd 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -1,4 +1,15 @@ + + +
+ Which country are you from ? +
+ + {{country.name}} + +
+
+ or Enter your currency: + +
+
+
+ + done + Done + Save Settings + +
+
+
\ No newline at end of file diff --git a/src/assets/css/automint.css b/src/assets/css/automint.css index 99740701..d4c714ea 100644 --- a/src/assets/css/automint.css +++ b/src/assets/css/automint.css @@ -5,6 +5,13 @@ * @version 0.7.0 */ + #page_preloader { + width: 100%; + height: 100%; + z-index: 99999; + background: white; + } + input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; diff --git a/src/assets/img/loader.gif b/src/assets/img/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..6b19313b925140ed50772fd3bac4873a75a9839f GIT binary patch literal 98814 zcmbT7cT`hL->7#>vQvNnp|^zI2}QaRLKo0T6A>gJLBK+2il`7m2oX?2Q4m`Y6%}hZRB)!xR2}fk1uxzd*?F!nyM@61#8|td-Utumy?})3Kl2tYphO~9kjlrfRhw= zweM7M&f*#NR^tzD?ccun_vD8!AKp;3DFY`CzkB&iP`K>zjmy)2ep9K`FK=F)?K&|1 z*H2>;lkMX5w=WKU{rdI!?aMR0JiO?R>2cB4RFgN4Z!cpfef#u5-@xGEr2*Nw)6I=_ z?_a+#H#T^3=lZ^uhKTU6=}#{{y?AnExNmK4_KWdn_Lem9dcpL^=Svo|Up%?@=jYU( z4dn}2vki4AixOjR49ngzf$p@mDN1H8txyvNvy0oS~sndNgzF>Y%fY<>BVqwkGlWv3v5~BU2w= zuSkq4%TCeJ)!npenTvznzMaiaN6-EG@!iDOaBV^E*^cd|RBBgyYhB$YZA~(XNK!t% zsj03;*3>eknyRU5Y}&L*Pftf*SJ&9kke#}SMx#yt^P)`?=Y+(?1_iQ0+??q!2mpZU zP+~NT8k|;`ruy$6P|QtRyp&T&P3J7hUNO`BwB}*6kWS9rcqWTy67BA;6=M<(yIBH=X0FdjI!f7jx=ANeY+F zG!Ojy3+mrx2Ib~+s56{rI4!2Txl+A+oLy&l__(?{Qr+mT?k=u$7dIa#S65#TFJD(r zRTcm9GFO$BpONVs6CC;RG|wt5^Kx*%>fm*Wz{Dab9#U(8V@xA=Ev z)o=HIFX-=ts;lvxo1d+!l(c2Rxr>XIb5;~a1kW^AedC;wo#E?~p6SV1?B?o3_wZQk z#Bo*qN%wZ+IAyqbxq5PDaD2SnGyi>_|1G~eGiXLgh+D8npjU{iYlx?}msdzokZZ8F zXP|4aN08UQ^G2*FC`?*tTD@87;oe);@K`SHX1cW>Xko_O{0#rX4QV~VFw z9zS~c;Qqb4cW&RhdE>hL+SMzUFI^lRxiCC5IMClGJAdx%nbWN$~;yfA4&;{1g8xY(F^thsZdqatUk zo{6C$!9mPGM!>9@{(inb-d>(FJlx$>-P760(ZSx%*2db((!!iZH8V9aHZnBO*VEOZ zXlrSbHPqEeL;@bgVKE2{sh%~o9|#5k$^b{zV+Z(qL;!Hpyi$IJpi)>RT3=mLyJ6!d zab0~wmS* zefDVQ?D_bMm#-#Xzj^!a{fCdr zPoKX`e*N}+>c`JtzyJC(4ZsBBaH*uP0@b!p9+9>VRFN%aZx8R$8A8Bw%y?W-< zmsj$($X#>J_I`bP@7U6(SI?gQ{!uYdV>b8PnIB&!Zg!?#J9qZicjfa-yXKxh_t&qf zFK?e-JAZz98o=5rc!+M6q8zpEQ1D5f6N(CQsO^}5l8`l4sh`;~CNy3-F;+zt+CCFm zZpnJK-gaNdvuei^6VGbs!?w?BJ?>;Z-{AeS&!2dEl01PA?l!0dYnnBRH zdh20|?c{a7X-PMIxfy_SRLbC03}qi`^9ZDHCc1=(mIr{-{#u#|PGhV|ft7vuRgPSo z@fyec3no2zmAZP{#RFQ^3AGC#4+$POjt%$CA8)M!QmNb-839>RSceen8Vs*hj#X%rYqDllVBr1gWd{mv49Y9Vr;;mvAlf`=uza;?8)RmB9B(+H!A_$0{c>zomBG;X4JRrQMmd2b zb}{D3;0)P}ZHLC-9V{LxN0+bth^;-3E5lFnWV|8uE#b(cB@H{RLyL|0p14}A|W!`W!N-Y2Ge}`|v?$elK8zf`O zD-G7><90g+sfT|;Xr#c#b!ebzj$&~BMh;{+!6cT3(d|)1QE5lH?#n7jx7eJpWQ1*C zP7h>}7>J$i*o6)%jMKlqHDx~!wUh>IDw3$HHS?&0T>i(`Y0W!XfdRNXe4OMkMW5dH z8O2_JB;=h7?WougT|P0G!Q*g{dy0Ejr&M5+e161sJqtV@=CC%lbKH0pcH?XiqyOk;f{|30 zJSM4R2QGtGmdBJbF=LL+CcBjc!ggS5WK=&)B`1$vL{RN!CKAP}B+`il1?TV=iiBr( zZxsO`_o*{OD|LcFQfn-@%Nh`pMCxGZRG$`ArZE34LlL%gT0oNkYLn$H^GEm~i3=D4 zigQy|3;_)Q2y1!$T8sL5MH2Y>5O0W{nK$7`^&`qJk={7A= zSm2p@e~Cr|gTMq{IUH7--kOe0eIK6?ges06^lrWMQ1ec6JUnMFCsfc<%} zntyG%CXZGUa#)0;Ncm7jjezNC6^oRT-CoVm$zjLa2uVax~8V*Vfd^trfPsXsJy`2Y-znxc(TGB4ORPT}D z<|z5w9xhB$KDhf$D(u>-01ZVeHRE`%*PRbi(-aMyRJ;0Vk5!_ZIrsBB$J0L-g z(Ff9%f(uE%jm>&w!+~?Yv^@O%@*Cybu_P9@A-9i&l>`EfWccX0?MAj!_E(#(PVV!2 zEVNk~c<+Q&IYy?yhkXxSUiR1I;WdA~#gxs^aaJC{FcftQE(k;Wu6{dl`tc)Lcn=9t z04Uk#{Vg6i=jqk&=YE-_U1Lg&x>?w1gM$h<^ujN<$v@wR{rOM@4a)+eGa+9cU6Z_Qeu$W!})77Wv%L$uATcf5FOmD!G|J{MB>@h zzZQG5KR%p${-=(0#S;y4+{E1q@Zu(tdQ{P0Q`gF5!wcqEZKrPHh%u9^NL&o}=PKT% zS6Dx}L!}b44Z+wbyy0+C4Al!QE`Kl|i({h(($sab;CVC3`+$X22?gdSll7bV7Ht?v zkzXpo=3(epZ1@9o$WFe*`{Xno?}bFj>?2sXOV9l5r;49In8m9+M(Q0O3)gb!WuC`Z z1vjqt=phoMxC8vP&plWAu>htNAki^qN-$RGsUZ)GkjzT-7Er5#?6{sp1yDB(uW5jp z+%+Y1kIo`Y3O7=q#KyDfRPRa|8;Ahhs3++hu8$7@dS(_fs2F!DG>Z$xGu(3~isrL@ zFbags_FXj}#gpN=5-^w>n8S(CVgvK7&`5ELi3BIK6rJS)5sU(s7_pIna&0^+M*Ci#PCqIbta8g{$8dID`e=$M~yJD{qAp)#V$F^&mGkh9trp$grEeH=bxl)D?lv z0LWxutWf$27S@joncc)~E829N4lj9`N0Y+Ydr(I)NWlU@P2k52)K~)8gosV3%H+SBKNI>HW@&X?_uA?YAe1Gjox;HJ> z?N89k%&>$29`v(#^NY;r!TG3#bkXQDzat6Gwl|%9q|;y(9eR+Nb8}=%H-rDH2M*_; zIRIeI1#=_lmh&+V(x6Hi;)*uE80K5CQBwurCz<6rK)2%poYy!nDJIy86UTye!nR6f z(A_NVlDJr*6ttzn4=p(>7$G>;B9sbXMJ>Inp_uF~kdzgp3)M7i6)3lljwZ(|kaKoK z*^jN`D?tY?O5pCuo+^)!Le41YCo8(~+Lc8G^X*W)1YD}zv2QpWV9g=0L@_HvKe+Ea zA_Eh690Z4f#0x8AAdW76eh1|6AWk+Av|yLTO)x@HTFL^6vfAfZj2joxMj!_XZtu5} zIU>m+E>h;rHDO`ahEJpBWZdL}oC$nNscOt2?=Y^WmoVm3vN0vpjfxXti&&_YxGp9E z%T%tXA0TvBZPsAyEtN20*`TVd8_D(c6LuGtS1Lf{r~W_ZG;_m;NqpEM;TL z?A$mEWFm2In}qp(L0r6S2cL?Oucs5g(AA^>k&0lHEz1c0*@mqSye&(7=^RB;hA*=0 z0qDfSq%a(MgI43(RNXtWf)3ssO6JILB^L>y?1kf@81s1a)SiZT7C=%!=xba$8Ss&9 z{1m@u^9xAku^%0Xtay!cWkXB?#!d+UbO^;m=}It@4Iw3TJ@$S+wJ~NTc%b({Cwm%t zxgf`c3NMX7!;wWaDOO#+K}d%+8O)I5INAiRJ>p>F0Z=;w$ODRPB*4#5RGYqAvA61i zFRUePYcvEEy#zz*o_#N|TGaBNJ{^*+5J8SIU+lWF8PoR_YgBz?g&ecd2St@-t@Z#S zLyg(MQpujpzJZ3Uc6>x3mKcgV#AC4a6y_IeP68@47|v)Q$P@L?-U_WP0>~w z9!(Z?Z{z~IFfA$EgJjumlU_Kc=2$yVN;sXwkzs4{%a8)F>pPmv#ro{WWxvHSq~gdm z3xlYT06;W(TV}L$ZIbNvSh!!Hgl+v|G2+fe3kf+?#AE4nP9A_0ut8mKx;i1QP61@y zhZeFH_(*$}=-ivs1H!DQ|Ir6pLsF?!AO+i=p!cp|g_?St1UIi$W7`cMU)@;jL%2jXUn5V)s745-Pv*3nN0Sdj3Z>s_ehj~@Zc zz^(gPo}z5{xJm}ZV$b;DK{3R>H4QG6BLXR68-7qb86~pXgBbl{ZmHg7X@vywGd_SJ zpSC)Kx1`5Ef8%<$zn?FLogeivjqpuYb3*M=ljeT?djr?*0tutRNj`Y>Nxx$=P+#R~ zyhT~`T7oeZx`c9QXOf%>56+>VZEP75bB`L_f^1oNTTXSSJEE{00G14Irq9eFvG}YJ z6voUqy3oM`%;mu16SxR&)#x+FhcRy5`*NJR&&93jbFs&h83RZqB)1w330lf_T+uz8 zXJxSz6N0W^w)s%dP(Ak|95h=1T&nj&qH{*~vp`=d2+}cFiRwXs3zh>A7wR;J?>WNp z)LCqKl|%3}E7TEBJUE>44mX<$$H}2Lwa%C8o#Vw#UYojn^}X)3 zi5P5z7;=*?f4BD9+soH}Y`^w_B*!{kTP2q9WneI?-7^wzc)4q*-{lk*?(4E^uVZk2 zT;Nm99Cx5+MWSB34*#=|kJ#=i7FaIF zMk@gSb+<$hmW8gnwN(oY-*D?|C`jSi8A>6dGE1ES2VDW=q+46-;e>U!x!QN;{&nl? z9VGbdEzViU)e-IX3()ewfB6;Ky!xcR#4hadGOQB2$Nt8E45^FsiuS;FCZCVsVZQ;> zz|YS9L>oL-c0qBIUz(z(v!mCRH*XVwXz;e&t>5)o45moWL{N9uap7l4(O13h-%?`Y z{pb;rLynQ6TfqSySJd%T1nVuk(uq>(yF;|Q52UEgQyf^Qzy@)TZD4@1szZa>8a@{j zwWLQk$UrRVxWeesEg6`>9U4l(XfjT%mmhu7pO`{EHz@^Y8{N;9p?ERu7C)F&{rDOk zY)|o+i;;@xFj7O;Qy@`~)D5fYaa<@+ak!EWE|BEm+ITyio~l}sA2DI65}*TMXHVxc zx=+nef^ew@N(FZ=YGzdM z5N-vK^Xe-X2qMcu;;}a5mu0im$UMk(?+eMP9oSEpBkR|0(F14wnYhaWu~N*$87D%4 zo1PT*p(o;6#k;Kq@lT*27O?om>qjyOkV3uBT?VkuGw)(B3SdVm8RlV_Tx^hH>l5ph zd1Tl4ptlM(lE_Q0O#rM}fRFMZZ5=sz6sty_UN~_Fqd|o>ynpu$fQ}Y?*jJ#bBL;ny zaLH%bSNwi_*2l?3AHS7;oZ9*E=edu+pM3oD=ObXIgaVXEsuEkKM0Y6(=arP zU)%z|c%**uEc@cU>x=LCFaA%z%$okfFq>osOa`Y;hL%l+@0y%_elqIms zfUj|>UlYo{Chq#0bpC7d)31xBzp~A~r3ZY=Nd1;s_AP7Ix196emOcHpV)`4`?0bH| z_rlcgE6ctY@A_VH{`=ae-%F>z^US9B0aJq1DPb95O0;XL`ux;)Q}lo0Q4ml8Qyk1I zEB~+KWUvk}2UAtYr+H=n1DjUEbza)@LbfzDkEYV9CaTeQw?Lyx3O8`mvS*hYc$2r&8^IBvbyRhPJg*8UQFjc?IB@%!9U)Z$U zF;d+@4f4|~H-tV7iqdAEJe(3lrTo6$hK|jg z8S(8PUb8#$B-U2f8{e-`QzLMTJs92{6(mnC+SkhH<4GW`mX!N#VH!FYg%|hOav7tK z?@E`hNOZMK->qwh&8E{aK|K6n3`gA0R6~P-1LQZXy=WV$tgu#+N5$1qg4nu0S6>78 z_Y?z1)}-%^tT|cDxc-yccEC|Rl5*L->(v?Bxzs(Pr`LArKxz(}dqx(#mcCl#WE>1K zV7u*^;ifhsR$v(=AzWk#G_$ZJCl5(BmJ2)FA~AqOPOT~vhy~Pw1rz4QtMT)MA?x7P#bl!to8PjHX~JMx zV3;#Sd}pO(MaG*Wojue+a*}sT66iLwM|S89rK_Sj3C)`m7UNBgHcw#S-kqq@Zw=Hl za^}kk4}{%)2I6!x!#4k&aNW>tc*AbhM zh;XjQWBPEAaPm4+k5tP%Ui>tK8c5`b9zT_dESKL@U#S5dFvxw186>&rOPRWZ?*@*V zCmrmhTwCo>Ib^Wzb@Hq5(h%sj}C=$l0Mu{Svh&uvH^!ttx*uT3# z{iCncg#tNcYN`3sa$G{^T%!ag1!&uC+NX8oSLrLEET^=_Ce$mRc54t&#| zkOWPWViL))Y6^caoESur^ijY@ZV+j5AuIXpwrhOe;yGDTfUtNs6V<(2y%`B5r!1(W zF~ty`l#Xk~Z=f0X2xlg8)pWWQrsVX#ubW?1?0sF0^_!~F+^8hL1|w#CX5+;|fDe&* z2!Sea@6K@HYX&1k7C;G>f#y7L359$1q;I`%PLG?Hep z*@>Qhr~FSEsg4r8Hn(9{`$LtNS}!sVlf`>Y%p-6=f)c)!YaV9}2>DqISXTkyB(E{v z_W+3X$#aHMffSKP7NVkH$B-9J8K?TyyN_;iOJ<`f^gvjo#LarQZ@SpEIbr`%GVaI> zx3c-(uow1vmAbT!z>$tO|6$`YQ5w#l2mU_iZb=@jt!wvRfU~a>btI3O74>8g5UN1O zdrZT-x$pcp7&Jhgn(sME9L`$9_+_A|i~$gX8?WT|r{i>bFD^=wUxAv(h+y+RpI4(I zND>*og2&Vnij62NHg=NK(ey~)bP}oD;3N;yGv#4d;wM`Z4^&Z;#JHoA< zq2c=sinm(hC)6x`sNw}(x5~FYw!~*81{+upX|-i9KkSmEsw1gL*W1}L+1-krBDf>| zjY!sWD@cwyS|;z6yS`c|@qwXUM^)gWf*M%xem4{svTHFZiJ5K?aW?x~TAdZs>AT|a{B7#VibcUfRSC-?F6vb&m_DgFw;f~W4I)>@(AEekFy%C#sphiS$# zOj)>0hyxM3;6P+;F(GnqeHG494Z5Z^h*1{O3^sqdutJ{+B=nUVkAs#=d}LY)mqj31 zg`JhT4vn~ZPVZVleMN@_S&3=g*hA6+WO*vOQT=vUn_G{XNvmlb(P(J zJmwFcjOYW6?Q8G{fkicm>q%E-w3aJWQn+XaB4w1rlh?tm3o+`+qnfr;Kgd*ZKWVL3 zrG}(f)66?i6fzxE33a?8Ge%_Cwc0|hwIodsM!UseVP#bwf`q;gClDST3OFc3OcE=o z-*|JtaX_71{N8M*oaZk{=;V<5kK;dKDB%)~X^{i~!pqkY(??>GhYUv^Z`7YyZ}HrF zP;ixPcwg5X;fN|i>ExS%A{z0u({cktw$dO=f$Ym0nXS(ki^f-C8fOUbTh5C7ZBLpQ zNaFnp7l{feY>g`2yNePcMMy^A;k|pAa5Ed-_x6q^62U~2q3E{j9hy$mpEUr`v*&~(ufqHwki!71ASPhAf)1qIM&J$-fImNt{xUTYP zZTXHpk^FbmJz|*J_slGieYAJ}7`9%`*QAtQ0p0Fe5=c_5Q4&vEII{pZA`^^PmiRoD zS2k&y!)hHBZ}16~+ot<8@;E~(V0(Kdph$WxuZYE!?^}0vH`o%%Bmeos!Y%WLXB{5G z=(A8nf2WQf6>3-tnimtI^)>Twk4<&*Hb`UvKu!Km6v(PVr&`k)jUE60N(PNrfSl&MqSq!Uk8j( zV$Q(g3SP;ujT+Vl_3109WLQ^{!EoHX63MJy%}N0kTtuBMBx9--Dpa_P&jPKv#g!6V z=1X-fyYf}A=L8FZ$bwy98m_6t_cm^gjC(Z3M-yC9Ee#YYfr`x14N@FMrs*t(ezsHQ z0u_}CY&Bp6?p<88w&U4(4O*03lz&+dzh{4XfV?6CME&K|B%U ziEQsx^A$J;iST8HZd&_V?i6lq4_Yb#cX*;z)t4M5l_RP_n}_&^&4aniB(XJT+@eKdx&L&?J-%c*l>`A zoKGP8(PxkYW482h6IqUx)QH0u2v|k!F_}#o&d5ZLfq3MBlk~B1LXI4G-UNxKaQag0 ztne8*d%f`b=7+f}C!~wb#u!}+Y(26hE|FNu29#MSDCf5qCN+)VXki$q2x5^8J|f#> zOoQ^+Yo)w~O-IF@N^D^VYKfn>1`EAOMD@sZ-BR)Qm8;)NI7cK2m>;;YO!Vw4!Vz2R zvb`|R3X74J*B4bEWN)e_!#BdqPJCZ3Q0V>%L_ZdzY4-_f>1eQgV?UUftnK0^+tR7n zvYrh>)Dl0*bg}!V+}GH`z2zEC0EVU@FZWFIvqR%}jhlGjdoaO=F~^q|y5XL#$$fN% z%=N*RSW;a}QAA^ESYguskXSs?bpTzz8BJngMC%g`B#5?@c)JdsCBqHy>VtTiOUmmj zRH&Tom~xvyWn&tuct#1BE&DVoRI^sDlbvM8Px?+`DEd5m=ku*Qf084w^E8OMkyG5A z3ajiTfjhhQ?EJWXSBV&UzhKvPd}N6txD(Oa=0gmgin}Y_L0k#uaiLt+#)vVT4i9jb znD#P2buLm#28@5$L6Xu=GMrX`Uh_bsE=-Gi`HnSqt>bQ_=E z=34^9sz`;*fI-+}PH*#&W0wO!s(3e!-FEUN!pH&a-t376fJ8DD0QQC+hYS@!DjgFo zS1m+&A{OxdJHZZEr6MQctfeP20SW`oRYI#gEm0}#mu`^ufUum0#>xSgk%s}=CWeN`B0 z)rU%8u?V$OZWD^ZIzXTc{A~$=p(? z9Y2n>uV_S=01hO_k`>60tetA|LmxQc*Oj$=1xB9^!T^Gj0fGWqqFi;gDO!=F3$*9K zCxHh*VKo=(C*)YjMA)L0wZTd(H`XTE(yjD0LI@eDpg`C>gx5Rm?yfdlFXV7 z3``0cm`BW^D2fjQM-wDI;8(h*u_b5uB}n^x-%v4>X-W959djQIMa7us$W68cY%G_P8ASm5$o;jP|rg` zsTgn-gU{OF=M=5-|K=@esutj8|LX#LlQ>idt}+(?H*aa{!iAM;m`YnsfqUcb|L~T& z@=};5{^c#z()#}KmUe`Us=TGM5kc2f-qMIkGma$0ud@@L_jgj2aR+xy~CFz(Av}7vb?!G*icEYThC{I8*??6yFLgrIM_@g{1i|h&X{+c zHRK+D`+2AVgw$Q;%$p&RxtqzqUz{eKnH;IDpB*BYr@8btv%;YY=!%%DrY;#wEh7V1 z=tq_`*)`Q)GK#gGTqQxv-f=@pzCLb_;C_0s1vOd{4)3bH{URhBixcSd?;`F~AUw=q z@yJ$Q=z&ezg&(xace*VCtsGe+)IBp2_b6KTbQel5fNC3ipOv*-rrbvSNEPds?kTaF zu(D|mw%?LOs}3h&d$WOk%^lo3hL;?@x5Sw2;RPP59=RCq^;PG4nvq*a32GoNc@;_+ z&FL5>Bpc?#G@PTuPHd)0TV|y9yX#dnJA{D(n}v6Wv_@~0n@1lem=ECOLMTY(Es?Go zWn>#3?w478=DHUBW>!A#Zi*sD84!6(lCf^_ZA19kQ=fU6B35gqm&xU~HPA-`eye-u zyf*tIx8zD9xys}-1BMC{FkPn|8I_l6yIF@_^y2E6CtX(53wp(kw1Lof@;jFS)8Ep| zM47xrgmv)#fgaewk_Q{b8Z-NN+dQrhS_8a<4caS%4&DM+ym|k9R*dJfPfurmorjCs z$Px?g38spx%q%yx zm|N`czmvPCnC`JFHCMm6C6{0ZhFEB8r^CkL|7I%k83Eod`l*H*7;Cv)Mc?e~;`NI1 z^7Ql`J}uG<${UNfOSyg5&Fxi+4Fp+Z)NFBPE^~E;{$(momlx+VM%LSpU1VjqVyXv( zw8PK@^5zD|_0YDupQ?as_c44F2 z{Ao%#>2>D#s2iu3b6R;J`LweMsYoJJPazmY{1Kd%lB>5ltRNT3cV7^|DE>~)lgkVA z8(_POg7DzImv_#fF=mU|{3np7A#*)HaBPEhR7k$1b+@n|eH21p+YG^t6&PE>!7b8I zjmkzNXP4H^+W zz~c@o$5}VFh6s~)Xv(U989=R>ajimzk6_FbO&f7WsDe_@P`kY@ zvkyv#H`X?$0ph@PuW@9K@vVlMxIIdlR_wn0ndYW~1;XMQ0e2Yr#gM}MGDaHHwKR&$ z10oc5aRz?g(pxQ}eLsYNp6!otFgf)8D{x7pYdW8nOdZ6oUpd~RJ7Yylb&0_&Hr(4{tD3%BP({unniYD83A)$+@iK+4@A!-wv(59+x zY6Z#&%?s`1OgO}%*5R(&+J<}RPJ=1ld0R#pZ+8o@eysAN?6~a`po#E1FIx*MFRVlY zE{)@`HjRC^&8(wnp>nnZvtMsPQ;8^1t%HSPetcal0Q@v!zD8+FBo}DwSSNwI?y*%*6SvFz75qbavV(KW#UaF& z{bP*!;-ET21J2#(Om@#=2y|?^ABws}myTEIT-Iuk>DC>2fm2xpSK|#F5~*trrPuO8 zbW;?2e_J^D%foT;#$%f_tp}Y(rZwjNaODGc^=|ZGY#;qP)1u9w^4)L0OVe?gKP3v+ zt0<%+z#mD&eI~CQiBs@!?{-9`vv|)!!ibPWrZxF#)(1-&v zCf}J%gVgCfY`(Yx6PH1zWdXzjE&z9Mg^ss)YVA}AClzaGpyp=gV%Wq`hS%)qBi^>* zVLc?+IRbOD%)|ksXUeMig*+6XV*n8YAt}=JXaH!@oHx@oQBYVfg}+F}V0bg2??#5d z%c=@>_KYI73V=pYY_ho*L}UW(Y003=>TtfP?tWb3yv(`?k)?%=!RSm&nnukaagj3% z8*YDen)KQMq3GsoH8cwABIu)*Hml3yt@AacwHaD${3UX2<_tezeTaTuW$YUo{`aqe z{5dLI=ugG@ORgAYmh%WCz`E~}g^L!W9J@UY)yonE9i}r|GRC(lF3X)$ZbzpJ+_zUu ziJWP9b<#nM+k9SsO24bXp7k8L&I^ST6abc`P%%Q8#yRUNA+-`PPk%i0;sUv0nx*BZ zZefswRexiggMP4;!BYwtM%Wua^ex7OvFwDN-3d0{f%`qun^HH^@NQ)5rKV&f4~7gr zdW-?ruDl_weBrQe0?lBuT6{?=zl_lbk$9l-Z^j;^jbR-1;ATU<<<2kD2mA5EYz;&T zSYL$#;m#J50|!DZX5Sbv6`cYI6!73q`~Dpx54P4sjhZK7GGSe%(4df@79?4ka;4WG z5Brsn#E8&a3mly>MpNf+aP)H@)c-U|i(NO;r+o{-jqD^^)NjDE4nKowBMgtfItL6) zDG%JVhdn`4$C_b@7FgK@GCDHX39XC*TbAvutZ$pg1uw*{G8 zNr_eC`R;B8+GQlb47b>5Uv^@;vR;?AQJmXH07xidOq*&Ujz%1I-y5fa{e(UQGhI$F z+E;f zYDu<1CO?_Y&hHo_8**H;4H1t09d58ZXJw4h^`+ak z8rR4B70W=j64W69D2f7VJ;^-EyFiVfI>JVy1C{=Tt)L-&h(hHV&3xVmuDb(y#S zmjJ_69sNdxF(7}Ln~{qNzoSYLJ|`!pq2vd=C~moGVhyy5MJ?O#UsalRAdVrT&|VHe zR;uoSVLzs0EfQ@TTLJ7nURB}rUkd}I3Kg5>&tXN09@;M_KfzqwjQJ#RW>0J@*m}w5 zeJ^@#R=-9L^4D_-^t+%NBC4cZ6(HQ=41gR}KX9|Z8_$4e)P0ra#*&)_%1b06?$2CFs( za|LFLSVxCx{D>ura#+yc)1qb$5_<;rSIsG`Y!sSkP4xSIuGf!n2qwq%Q~-@il#J3E ze`f^}^gT&bfB_A>s9afbr`!$YjrWDi^b&4~9J=#0@l$}BE#rId!^a=6T%NYATtMg2 z)Y2IJlyf}HGz##bnG}IGAc4alg*}x?m>?D4st>Xe3L7@_{0IQc7&z|ZqRr2M)pvVa z2|iK-bCnrnGcE_rc??qFuZL>4sPC#1f0!{9DepT#%Q z4)d^~-hwxG81EnVjzRv#-+@_V@FgqetxXo70Vw=~sJ#5WF2P#*Kr2x%->iPqA6mF| zcC}Ju;bPcta8GfUITeVghMPYHz-4PJAP3s5@(kNGMONz8Y+$enSioDeQIe+F&;28Z zAjaA<1*FOHh_uw(dK4y+*?eVLgt#>R_$+m@KSQPEif7YAXdnht!iBaaOrz;6pzk|M zE==22prTx9m8zW()=Zm$Ia|$<&t9mpSXDs_|2#)}w}2Vcie9mon*!+l3R{!2Y8V(> z8A67HePo=vWT`6yv36gH@1J3+f_~E2%kkI+K)g12nfNZKG8iw%rhxSMo8+Jy4>Io2 z4dj{ql0#~&(h4Q4(Z=`aDC%RP-g1nm&4LnUk${ER$YN`_7;8YkinUThA>xaj79`>f zr6xb;tvk)dXe6LIR76i9tP*?Vd|8N-Bp<#FDMQdmDmW7*F}fgv2Yaa5Hbjn*ju3la zBCF|Hq9Uz0DH*pLEs;X4HY&euJ)Z*+#-~9@S}P<&>x(v!0++X6C>5v-0~F6uA&XYX z2H1F(y_A1{nxT-6N;*HYG2uLtqk;x^0 z^pYvf)DkjMDG`(I$LguJ)3v}Qt9{g1$aN2uCJ6XectvoNY3reYt>~d1lT=B%?Eo}Z zfR@MsM$a@F#i)8^3l_SmU}baNaV+%*PGwk$SlD(nm#l)l?HCNNI#-&plMlzsx13QX zSCGx^7DF3G(E#2op&VDlYA*f(c;9DvR8*7bh*|>HmRDUvoxk-Su0*^wk_x|GvYFL| z@KMseFqN_ie(EUQdLO;9b$yZ)Q^^IEht6N7uyT|G&$J;(9NM*Ihk$``_e3E{y+DCY zp>NyaT6Ok^&Y!kzd>MqH&We*lt4^8u(3`NK=+V&m>&Q4d18q2EX~)By1`wuvTRaw@ zL`AwU7|f#Aosq)rO%)YVxQZ)2Lx)cds#w+HLMuUD7BJ5i-LtiIlgiX7e@!eV(+Kpm zv+_A*;;kfJQ4$-~ zy@O=j!*4iWWv!FSPC03y!bx^Y-S6=XhcKvl67t3|Y{7N3z@q7wqFrqZoZP?jaD2!0d1+^ri7ya|ofj%i z->%sYlDUAUf)ud?_c1-9xjWpy&ajtT-^&FyWK5$rKj2%Q=Cf$}Jt}Zl)hWKtJ{^}g zpB?%7jk$#;v%Kr@*bU7SmPgXAAF0#S{E~!znq8jIzK?i-Naad4k#(upkC*@$JxM^U zn;Am}6;T58>BWbe-~=ktCQ@6a#6>rwPwx|R0G}#)GoK8MOZ1%AfUdXo9x0DLVPV`@ z824XC^VrAUO5wZ8qvxNg1ySJ?sbS!jW6$ImYcAx+Q+vxj7QPS@a@u(A!sBk@!ZKd; zW3k>NR<|Fp^ZeIVp&W9M6CITss(?jw4IONA8{`8k2WTe#XVVPO?})iS!Lbugr+UA}MzAqglh6?MHW8!pS5X@K9xh1_G``$Gk{_s0DZjk4Z*+i=2=53M zV8&QuEC$uZ{?E(%i9+$g)!}v$aH0zdm4eHq7&p58nie?hz#^&)JGX^OzX6a`Gj;%s z<>4M*F!!2|p;V`-_D1sj(R5eDj*GS7Vzd3Tq)cJxw_UUC4U*V%=U8W41AR@%I79jH z<-M^&`?wPx6_s+xhC68I3BG7XtG;UXzA`T(yKMT6`{vzrS`MYi_H?e`NTS1t7 ziLY2+LS6r+0kP&?k{JUFz7R*!f$gFwf7hOC?ZRee^BexzS7{k$Y_u2_f-# zzV(Jb-dwYzB8e=?hu^q$=>SFwZx6;ixCQM{tym@Chi~!?)P-~8fDQMURoZNoqwkL0R1fVtQvSt)H-qeaSSKS(21>9m32Okyrf8dNgug`7ahtngt&=S6pEjSJl~q0%#Jg_vg$$LoQ^b%IA6ag#9*Cj+!2XMgCtuKF>{|`B^O$`Em0NR<#zAXJfd3`J8;!5 zzTF$?Pp+aLj-SYhn`b<)uEHjy5bR6}iuyDP04SraR?mYM7~T4R*n6+2CIWtacV?2A zOdvo&x|9Uz%}@hVK7Z`d|301%r6%kM|*g!=M2nyJuVn>Y%3f5pj zRFpIBZ~xam``n!Cv)19tOO^zkd4KQwJa8htYh22z0IzBB0)yBsyn;X#-N)Xi@1-$_ z@PQ=S0DLcD;Jwu8=X&NxCzR)g734k$JTH3$CL_R#)51IOVwQ>7d@rf2zaKR6>P=GH zP$sGr@}&|b!_DD*XQ^05Qk@p8__7o!z52vm*sR#)dO5_Av)>Rb8sws`+h0P%So8KT zck5VeAxy8k-v>vG(Y(OO50SzXdmWQKF0-w9!o?nOjPby5A6mivEjxbe_4!?UR;GY_ z;H5^;C*||i3+A{kqw4j}0|v%(H~GM-`7Z;PTnz$Q3%8X@caPOAtN~>7M_--{{HQK^ zO)@(6)U$biSs->Y%!`-LATtADu|_K2UCD(-%be(0R6y7}qVGOP-YXmQ$^&n&Trgl+ zbANcWUt>`KhtauYDbl#>mQRnQPbPnhtEU6Ea>oT5=e5Hcc}xS=x|bHyIg@NZ`Q?Xv zF1;3~+hoodoZ_=UvqOiAsZImz|1k%iCgdY2s`S;9@5*CO&-*2g}Q{D=$R=W}7#KZtdLrC;y z?wG5)TVP7lAt(%mkRDKpY(F8l>pctj(TACTGhbmQsh90mrBJ6GZ1Z?rESa|+>epm8z)gDzAS&_rnL=RiWH|->603<0x zF@d+oMP;Pc(10zjX}r;*Z}Ag$6oT_|+lyQm#Y?=r>{(`>14 z^`xL$9z}@Kr~rkfKyXLj5UIighrnz7yn~xRIWd|YCqHE0eZXp-7PS79H3{0Hxg}k zYsoOgHP*92A|eU8xTT+Ki zu((w2vWb9KeOsTsM-|-Y z02LF?BQ0Vwy=~^Q>McQu)tI7TT?`sU?S~8*g!dMYv=>hHCoQ@0O8J87g`@*63c9SZ z#eTFC2D$qVMN^`+e$QT;_`pIVd8Nu9_+T6(d1o{i3t&|5p>E9@$a_I6d_mNCTRN#Z>7JHP(^IIjA4qC{7@LtWExaf8pqzeJZxzYzJ0Ej|rtlm%u~Ax`w!wwby?*}v4ayTCR-!FDPA#1USGDJBFol53 zTjxO=VPT0CiD4zryEw}kFfe&tumDJ+ zlU33(%sk*w0aOW**RYMeg)EmuTJ6<}uy3ag2h61F9l7TH~pP6Lu3qD%0-*>jJT!6^_)l3mM!q-fUAqcv7R zfD|pKS_rM?LVtpl@mZ%FMhNFxz*{C%BwR$aW)bjm zi7Eh^v+!RmFfk73F7igiKvqv__9U#z0=zI{ILEh17s{R_ZWb@&agv@XLHN3GNgUbo zzJ+^Rj7J&}=|%S85sZWw#~t2qSX+|^jqTc8O@)mcq9VDjl{AFPfEi-%&D>QL_~4V_ zTtB%wmJPgXK(!oW;k3gQ!E6xjSMPAHe(gE!CKMWAsP!d!{40@u0-gD04WCam*|S%lUj zifkTLDh^&X8H_%(f0HgS;1v94=h21Rrkl5lk|^;iEkAn6XKJ zcE0Z9L8wSfuz5f0@nZW@wJ`Gq^&FBjow0bh8z3%S(8@v<+v z2ov5uDz7shyG?Yt(3d>cYAW_b7jQ_yNaFWX!b%C^D%k4_Ccc=OC-r9QJTWF)Q+LSJ z5d*kgGY|Uu?f{sMvu85+he;-hE<5?47F_5PVr2QgzY-v-vzjZ;^cJzYJxxznvp~n* z$YoaOa?>kTzb+SZ1|*hQJZ|5@K!ozEQ(+}(lkeayFTw_is??g?CIgeV#^|bTI^L$_ zVCvNzQTyQr@T9?jdj8cS{?!G+7Ij#>WX<_P2CwW1WE*#_mP)Xe05&3xaV!nflXnf& z%JoCHKVlTxklR|%?XyRHfUTiZ^v=-3);vfO1G>eK3h{(ybsP%fLdZk_RJ=SHXCtt$JetFQ z%Mf=7*fm60%}6B*2*2t_YB0b#Nimf(T)-hZ{PF1=_dfV-lq)D<`H@xck*2f>ol zxuT1E76Hqr(1q;L8;i1v`18nSiPvTp*fs0TpaNjTy(<`yxK+nXhO9`%1`>N85W)6^ z4vEx@JPz>2#3ae}@s_@g`x-A-vp4R48vQ^Q-gVqapWm}XNR-$QT?kdY9mHNyi)$YQSR>-p!=cU+>%^&mb_ z8%$hB73VZx`Bbd^CFO{TaQmO1NDSR+<7hwe(t>axOU8s?!UvX12uc{mTuhv@bjzd> zW7@-}eNTqporiJm#h0#|i)oPgmgAd-&ud1jXz=PnhfONO2r|4Km1KAW6;3$|wsk0m zvEcXARhCpzEcHpwd1&$9*ACNewMUf~8L917hCU|5$M7nA=n-`>%CQ1q@aDwIX(aVm zIKJVh{*Bc?-}o*9G34#!UQ*HkfGBRyo6`hlU80VFv3&wry7o}sCKcz`tIF#Q9wxf%+o+xww7hC1C=es6z!^Xh4)8;_WgGAJK~!=IT%ONLgV(u-`M5 zkU010bNzz<$3$}z^dEwqjc#7i{~N(>cfsNRMX)=1{<)^{|~|LMEA}ACfIe{ ze)u1P-Q~yP{}AlTUrqfN!LIlH^nVfTYQO&cN3eS{1O6k}Ij@D}{t@ix9YwPL2zJ%- z0~I*I&NL(WAHnWfD&-%+E^*OvoM0CeKGJ{_?6!I&{Ug{#T)cr3?3P#BJ`iiC}XK{kvuPhTh zQH&*L>$jGn6~KkZ^7N?gtmiN49Rc zHMMGQnsModeOEsYAK~Wyom$B+c;^v!;H+jvTT+GjyU0U3JCOL6R)3%NxQ6I=5v|P$ zSL2T~*czdgHNkT`-@e$)#`Af|$M^FBBPdwf@Sdm!O{V5^r#HB5*i2@7A zH)-WnE$JtMk&&UKQ#Q+_&34`mxUlxjgI{GE&pL0qZdUy~H+x;rD`W3X=lu6on%BMS zbcnt1DU!UoH{zD>;$1V(f5%?>RWZEza_nDe2|ahN4}#RFUx>1mV)lSG)70XMTwW3j zotIsB1H)8lJY()T4FSy^S+tGIBFfFZbd}BHObR;bQ{fGQ#j0EYaG&EBDr&y9XXxc-U?4%>n#NM09}DzSD<01uQqnXHn$gx=B@Zcv z@phbj!dAWOLeD;Z@e0{QcmR`6*E_mjd!ZZGMaq&r?TK#U`}=AawfGOhmV5I17b@-w zW@KucyuU`V`@r@20<|Rdwj;C+u zS+j`RWwR1b*#K2;!EEuWWP||Bfq0ay%Fq9uM=N-sOCt!>0;CUE6)kLI6hR`E)U%$k z@2w4}lK_MC+X#QCcm69^F`2yrs!9+S$0S6pO$e;IhB_tXZ_C39$l-0M6Gcd{684eY z#2C`Zmv&Wsfh;^yEZqno)X@^6Ow7_5&gW2CMj=o`UE=;NUrGiA4CdH?nQDKo@*yJ0 zg9Xa)Sx}a!|1o-}&$1DN*7RJR2=5u#Ed1n5fTK9 zA(yxVfP7Tj=U-HhrkwzsJkN{OcHx)en@zQ)f8uRsjWnFZd@x>7PsfEukSY5>4E?}C zuN#HBev6UQ({+br*-W>sd`c8U7*J(#%|-D+`FkIL5JbEA#@QM1?+K?q4o3)XqX6er zqO>X|<%E~AcM`S37Gn8pkVEA~%wzi3V;_(xF?{i%epyA{lf~;E98MJ&SgG-dl8HY2 zrv|9s;y&FPM2Dr@jKgyhuJJpD}zr0qeXS0D8^7o0iQFBjgkS*Ks6ltkkjEK%O^ z!43aNYTQcmHnh;0lav_mIgV#01%6!|@zw@<>A`8 zF$T`Sod2TJ`5NEO-k04&ggF>#%NMcSomQcu)HFgby2szZ#8ljHg4H(qy}VhWVw?a~ zAOB3C8pgUz`zTvp2Og(gEO?o=4{AERI956wL*uEKyq{POWz$o>ZPU1Ij=$nk zT@EY-AvD~1{qQ{(=}4h1Z*xI%u^(-rk@JS$3w<(KZ^d=@W=iKTJH3*EQ?IB`g$?!` zzxk=0=13oZepi}8hofzs30FlYo!mIF2!Kg%q83sFwEKT}8Gg3uCA%ja;S6&F;z?eC zhx1PJ*Dn99YHvfwpJ}T3!p8NTP0v zi#C8Iu&0#UhLR*jzYUyHuREX*$@=U=b}SDX?WSEA;_WvpSP}5DQ3^U zESA!x zIL&e0O*oNc3~KuBsXC;6GK1zvTcdc#Dr-_IhqF~63Fqx0~p7s-dYA-lq0!O98u zOQfw!%N^M~UIi6$pd!Stt3C92M_!UhY}mrn(cT+vN_FI1i$=@YwJg=^LmkKr)Iqgx&N;*<3`TMAl+) z+qmwb{4`2Nt)CEhcw~=KUyK3`x|dnITNOmON4V^10R{cU20TpFzP?kohNeDYO6%?+>SCKLZT?DjerpGc6gEEu= zfJ;L{Y^}EZYvnNPK)d<%AP4&OmRhA(UCIMCg#jC|EqQEML4-5$to_;&uKJKILyjwl zfh<S(Qc(qY*CejtUY1Xb1}DWW*wBC3|?*&fmz&bC8Emy>&`8%Xy3YYmDp}SMc|ObJk{?iqHF?6kM)~L z`g2Di1%5{!5C0JM=WYd8^+;(@FPG5LrsGh$sS=ARY^1po8kXA$&ufI_XN&Ob9JI{M zK9koR+6=cHxvaKZ?%9N}qcM8@J;+-;Xox|U!P&HQ_k;1_n8SsnqJh1GtN3vQTOcWL zPI2Fv{zgW9*4SXM{>l{+aQPub=MaG@20-@>muoB9VK7v9;SufqU? z_(%c`w&w%V!tfO>_^RLSt15ZCeqiOmu@cthFYmz^F5!HVVj5np*b~r5#Vcbyu6x>d zorSF&)}weIZ4Zi9KI>gkQvobZk>f|^(P;C;c)w@}1GoDyK>mf`Rv6+Rbxd5(qn;AS z<>gUtl!*u_5|Rc*G0l?TwjTYIkdP?>2FFR}jBZi`?7th;5Ca#kJ8NEbqi_bk1F!-6 zcrG8vFSY$0>c3qGN!!Mza?Tcu!8^04V$Myz*q$J~o+pIkvk6PsV8OQDBKFX7=Ye=X z@+Y+Ru^vRAbhtM`D}oa@P?i8%nHhLuefTog zphl6q%SIm!A*jULZ~BL{ly#d;>2_EMMlU6|tP4lkz*~0I;jD4dsOuQ3(pd zYg6JKm)}P?a7f})^Q0BnDNgsLQtU>@$SKb=XJR?gYDs4-cif&`wbu5dqLtpA8WHHg z+PBX3W?DU|5G9M4rNaaF@ZRDzDY^JXr`sohBTv`{fDznxK}0srWdc;Hw=fO2EPMfAyj$}`m=XsgV!(Us&^8v`{x zT$P3Ucd+c!Wu&i}9ou*P!H+)BMDk!l8iu)pZ$CP?3hyl;gEo5tI@1PIo9>ORLgZPl z_WaSmW_N4F_*kd*)~Oed>IkzRgTiQFpQ`LP&2A~q6Z+c(+a@qS2err+)&NlHNIN9p zGvoU{3+LCpSb;iGGv*BkmZ$;9@t2I)a7XUb9lez;KVQz!t1JV?^CiiQI5MIN#_2Mxi@*QNdIEf zFl6bmlKzm#eL1g6sBg0Wu1P;UoXeXy!kjGOOqSAIt#6{0f7fVoNP(R2rPu2=F^Gl{ zHf1OX? z`YEWUdtHT9UGC|L_CJKxo5FsXy|C?ou1+>P@ChY{fl#62LAN4T1~UHmSfxwS%L>A_ ztZg_+al^Gvo3Er)8cCklf1bSCl|RgiBobtICsJeH=#pG6)i3Vef;uJE!Us#aR(cOp zECt8Q7+YLvTb;VMXk=K`ekZLb+ZlQ??YdKz?ILPue=(ewl<)~tf8C8Z2Re3uJgro@ z*zluiUv0mVlG!9z4nz{Y>=QlaCXX!yz}F=tM^O{=XA8gi!xAHCWs_}Or+Wq$wb_Ke zyt+}0jO9t-4S4`_73v6(W>%ouERf3zNYLJtskeD74X|Uc887P3t5SnpZtv^`hpcP4`5}JgO#HT}nN7`xrHsLzJ`oD`QGtLIZ4mR0VeWa|0IT(q_u3 zP$`jYiXSRvP%{~gy#E#0R_7Xql;@b~qt#0cBX`Bh*Wk*L!#29D|99m`wVFPA7u#Vq zZAY$}TWm#+3UYRM(6;@FJAp8v8~D7Xt`+@-PI(p z(sU#AxSVaUd+N_AvWrBse``MranGePy<*6tg{%Me7FYhqIdV}ZT(l;z)Y4!NtFdo< zIZO=K8bZ6;Zd0w**1CKlIA5(_LpN8XcExye zZs{87qOZ7lDV$NcgOv9-VMFj@llo0z&Pt{9NsJRX?sgeynvb$F!I=1NGyoJ=G#t z&6aCLdhtObBbP}az(bN!Ewjm4Pp!y3T)@e+Rbjj?Waf1#Ol7i)0E4Y}2FVchrI7SG z)aX{dJ5%5y6P2shN?;C%V=C7_h<|IEn;~(f<6fT)_PHT~x7s;~_uKtt`tu!=2Lm)m zCJ*sEjN>hL1|&=!Zd@O^x2`$u-PDm?E2Q4FwKS+7Zf|St=-76!^WD3n0^5=JW2YY^ zB>EkH-toS(x4$Owgb*KidD%0voGb+4yZmzc_XjU__Q`B=J65RGtBSfZyX0#mG5U@#`-9s0E807YU#z85VErp<>pv9R?unL3={ceK zMX;7R_i}ohp^!?tC9XcsYarEYXBApB4{Dp zme>e<>nps%#RtO@TJi@p%DIf!AvQYBLZCE*du3-( zU&bN2aZwC$zB&->gB85$_=jk;IzUg=&t*avB+@JnY7@dA>or8!59T*|abK3IM!A#| zn_?}`=)z&B8275B~ZI0~EHCCGp3i#qZxxdu-{-OQs=j|?st^*dr z!@E{6`iLH@>?a(`UDT%<>z>rSq%eCW5JD}wQ=%d}F2JaUrZ`7ZMo}c}lxgJjlZDNE zlM@49&I>{RqI@@1su;UM@jKn1DhF?SQ&j3quAH;1b9R@G zJk1(BA30!hLYZB}S*-Z=t_eo@aMjmcEtFr`NrX`RR0PaX3s2wG6&lnre@(AD`=k@< zn+M^o)ouz|r$sL4rf>8uq)k0{@`ntM9Q!h2;>+tF(cNX5f=13Ay`~lBCFoaN?Qv?N zz7Tu4XrI!N0%wBJD!_fnbjinW_l$RMvm9ltc6L|LXEF+`Q;PbEON#3FrhNCRpaJ}9 zlnmcDP8Xkj8aA+Fu_8NTm4_NzFy^AELg|}^(}NGVj|DqD!?jHFNW6&-m}F2eh(}B^N)9^5SQKE|b3iDLW?H^|_JjG$6-d3o(>5 zF1#xB%0~oGWsW_A-kPiq^iY6AqEnVyy4)ckzR03|f}g~9R*7YP^w2UwS;Tp5IY}3W zAb!&bvS^O)idYP6cE$k${1Wv7HpA^@&MFD0L*qEl)nT9%Qw+)tsfs6+pugWisVpi0 zJF;YCLq2D9iCOS6B|4EwkEe4Z(I9@l9)rF>iU-cOiHY82F@(ijm(E+fB5Lagt_mK! zv|KyG;2l>Y2%N`t2J-d2I4LE*Aiz9uS;<_ip!EnBnD9xhhmXOTal-k7bVR#f$8{;N zvIwFP)o8clV>zw{ae~Vx%Pbaf7_R4U8Cn{L3cwPrNkrP^q0D@Nr<>CkZ7{? z0Bmw47Gwx1vJ_7JVL*?zk#hja6axlr1v0gn#a5CNl+}^}tIdr?dIdW8HR%d{rJw}% z<7;tF=d-fex8N=e)Ng|_`+V_T7a?8N@V!vIXNqj0C_H3W2^wP@Omm&hZo55g4n;>J=#Uqv zFE)94nwD>-&=0DxB9e?I$uw*6g*zYn^@HAuGd4Au?kJhhs_ldP3 z%1JEt0_Il);#wzxcmbhj^V2bYVQ3qNlFUmWnPX62YhmweU6Wh|NuiOOkQmELczX+Y z>EiX0ICKw^BKDS3UZ=0qoCQtrpGc0aOyX$qd*+q6v0pR*aPjJUo9lN**%6Spf7CbH zl2}r`rk?;w+MfCylakc{Hu&Fjk=iL-o6K9X{a5BoMJ--{*^p{IOr4;u;aFBT9h2cR z`pMBx^QohaO0tg%x1Zn@kwO|Y^vpVQFdk+g5`t<+Atm*pv#mzK_+%byI|LxaZQ`xD z0ey;F`GvEwEYj}Cf;Ku8Ge~;(U_rPGK*JJOEfUMLg+5l_!$Tm^FT%kU`SK0(GKdAk z84jd=NA$(ZR+z*mzEe+Oql`{zqIGN}3mhe}oGYZvq;(M)cp|SMvZsO?qLY@;1LS|X= z6%vr*$D)unAta#v?UOm>VE^Zz@GU$HeqbSqUIpR+BT0bO9xkJB;md%4nUk))x1TTi zt>lwdoaKGwG z9q2-;>u7QyxX|yC4GrJNmeGi7^!8Esau+9^=S!{!L&Vi>&Qf3Ssl8wCdqLI34;z;M ze$st&<5}&9v4Dr%ZG+d>&W{vQn6^ZUb)U3Qn@u{Mj@O8CssKoqy5_S=z5+8}@lmdW(DtimI-3Pg1d&_?gbf@>hDQjN zK>F5NOa?KLkRJQcNqUG(<#;GD3%j}ri(Lzujq`pb&oHij3m$vSJ7RmgTSQ(rbyp243du*uZ&=pZVfLzlb#Q~wPka`P!8n60o*x7WXt7g;bkQmWeOQgGrV;!CV)ch z7f6^cE~^VKZ+MhS7uxCGgSL4^kP1j>M)^Tz#i2)_fqh# zir1yFBtWlM6{MD}@Wc8@#s+Iuk|PIoVukD6gOrtNrran(M0KWW!KZO+w7f7x0&I2% zy~P_Wxp9Bw2@1Si<)#`#S6G^gte};aX21rtb;TS|pX2Jy2f%}b96|MmhoC+`mhn2> z*sf3$t6mmS8o~#U*6rLeN{SUk0uZ>Tq;T$#-~L*nB@cR(?&uv+cQTCkNyTbm#u6+T zi6&=<@gNKjPy~pFaVTp?V%Lil3<=?H<`pS%1e;6@>U@CYUGjp{-|?i7xx zoG0c!b|_VAB8hetH4$ou0iIe@^W!{6Y*+PgQ&xLZX=W3JzpLw&L)|b?g^+^Y;$ap{ZqSri9G0vK`#I6-W)4pD3<;-vtcT};b39Ti*}MLuz$K~ zpSXPgyW#!6&N=>wFrPYSk&K7KUr~fw!}Q8?furUu*rg{_D2UH>nSGo@QS2(ko>7+i{ip(_W&tYZIy&T;w?8EK43SA#Bw1W*6N)+U=atBApV$q3G9;s1z=&EE zo>07r1?z5gslxzSHayi@jk~=_VlYq~sdFWK(ZVjzIq`N0RAdIw0(cYb`=DM)^GzDK30kl&^0BFECX7oNcgbl=J{S${JQ5~biNP94_UIMF1 zDspLOmOPF376K=QV2a4-sx!1eKR-`td8A3}_}wf;&pZ{(Mu)o>FPy`yGoeRwW7&W( z0S%&V8fHUVUs+%3M@x?_cA2?ih@k|P+k5v^2hz8ino`u;sYQ)wZCjbMn{9YMbq71t& zm)ddjr!}raa*>v1QHc>$VT1r!K}*@lCZ4KasL*h>#Do}C&!4;yZOK2s>jL4+?n@yH zAO%*9h6H}@p1sSbuTYE?{kEo4PY?M>nX`}x79uU|2mKH$72-zWJiIw>Ap_p^=}6yQ zV{|pY;H(OSKe4XUgY>rdB^@G#bI<_J@iV*3NkV6`1n?ZQrnBKc@1+QU(3EoVtd7uZ z8_}N%w>(u3=RrSxvo(1nwNO-w4Ig;0YVjHWx~E#2;%=5uPoQ z5S8KVR2E*$K%&M5r5Qrb9(XMWj;12+9O6R7g{s`44+gu|4Z;w$FNeBBHvK$)O!M_a z7jSE`Ou0u~Yn(KOmU>%#Q{eiijCkv#5;!|n*el=xz*zs{vHnA(to1Bp1(kSUB}8ac zXDVIG42REsAgNJD4l3=A0!)vzkwUl|%U__y_8sTp12}H z5Y5Cl4Y)gT(Tu7I7dMlg;vp+U{T3QTe?V^}2pXLsI7v3H;Q>ab>TATj_nxo|jiW&Y z#Tl-#B3NCxYym$BWD!gR1d9jOnN*N+s#RP$Fw;X!^BdgPaL15E2;>-PF(5?&U?jTf zp8_ruGG07NRT176FaSR)vYvV+LQEt*SxDWRJ>xZ0NCi4a?Odr~L?V7D0_6pq;X2~D z!>tL5je?nvDjWXk5E*BW;Tx*8e9{twCoB5ldLE#dW^bZ_SaxW)$?BVfC_n=#NVTR3%1rG$evGu&0Y79NGqrL_JBp5- zL4IDpr_ln29<$8m4f3h_{#0TB?MjWtFgQ14KG*Nexp*!HIBv#O(m&-(O)^Ru5gbRu zMer6Tadrq&SAH;D2Yq}`8p`%c*)vwo1w2YpqFC#x+&k*=ajinoPXKnlBHnfoMt|rx zoqHZ5S*S{Te(FeMkq|`qJB)%6St>bNIBayK1}D{2YCeb*g%|RXkReZApu2mu!=w(GWchc4i@KfvXq(3`VoBo<2a_@@~|XH>k|oYy6ti^aoWJ&o?iQ zzWurW+TG)pG6ASP-^Ju!b&Y^{EfdxD>hSq?T2WB}C_rsXjK91Cs4}?+848^#{5Q*A zKR$_9x_F2)2btgVbgdYc`Gf!OLwu41DRL*vI0GlIIc?%r6^W3z?I~7G6A`S~+MQLZ z{40s(mUyitkcG;#&O|bH)lv&(6(IxmyFxx-y@~lfM4HScsEQCzN&i|d@#ZQuT@3PB zyV#5e1x1n#Z#_1y&Aaw&`{%=?)Jssk1YWk=@^@tw2)wb|=VN(&p7iQsT^$Qj^j?mO z;M_UaS8PMVHcD%BzV{R%K0DQxMl&@<(47b}-qvv1?HEOUm@EEh5{&4Wl8cy@mVy?X z80Dh=juef$vCgQ7C+Y;(?PUoa+drAs`ds82Q8}-w!Dp#rZU~KFx`V7MfRbs~*{;EV zWhD>H3Df>$_(iR^$R+#g$bsi1ndPVnFuj;fuIaH<5xpqn#V*VQ_utf9$Obk5rZD$d zGY^o)a(qvv>D`O(3|N0e%Z9O~sPnbcaGE{V=MuObensK}& zN!%1$ESz;&JY6fAF*<}RbAAlBz~QTCrjm`uEMTL!^NbnsvX0|h1?G9!5E0FqWQOzD zcc`Dm1rnm9X0cFv?#q|o{huwg7R?P-&5bNx{HfsR&n zAlF@{Vb^4Tr zLD-2-O-rc`<=Vzlla1(^|908W-}>J&`;H|Fo6hHGW(D7+8fm`L3|(n;To3L&>`pa$o7lVy$ob+>;~yw*DWP zz1BFFRb`*7BFmDdEeacgEerL1X+_Z&rQ7$Nsg@5|rMB1QKQjB!MBlX8(bQNOc*BB# zxc2nLFG3G6UX8wApfgC?5UyL)rgHFl(W(Eq>}e$W@u5yTMo@$zxb}UZyk*zs>)Hly z){MRMRVy`$(t2QWs-W=oKbJis6H~e_IjqF7F`t6M3k;UzY2LgeoG(6;6w7=R{(iW? z@#4n5tJ|u%hC)ji_ zk@8Pwe@!VyeN*Qu5O=$`pm2%ah!7~7X0l1;dI>Ryl<9&=A87+3@K0tRUyrI-yT7j1 zi{|I^#&iY3?Oq<<#bjT`KbO6+HKwFORMGRWBp&c+c%&Ta0Ojh1MSE&U%R=?#JDQ8iLE?eiPHsC6CI#yK@u&96%tNQb>vbP)8wPjD* zFMVl}7r#_Fr}IA8svigNj@+R|@|ELv67WM98-R}XyfGdpBB;n&%_NQXLBOu7b^`;( z4x4yW!|52so0U8$=s%h2tYqNEA2>Od_LPpQ{$GT>cUu$jzpne8NhX;TVCWq}2LTO5 zx*9?k)X<~|8Uz#(FsLYkN(h815KsgaH53&K8W05)H53th>|hO6RMglID`)(zz4uyY z|8b7K0J*$EGI^fQbKfZ(z9i~JN&po;!W8N}ncLq-p6|@?K4tPkM*x%o*Za)xiFdmZ zkKb}O&<_z8AYDIg=OMZwptoP$y8T$6MpW9T3p&e>d>S-ZKmO?=quB2Aka=C&=S%+y zZ0)+nKVM;9wEJ?^`F`4$Yi_TPe7WxRef-M}7TNx*%vU@8>&-x`wy(EB{sp$20Q+xu zBBG|!zuk>q-uCTY-1_(5?sJRnCx(;i(kDhz4zx`?Nb7n(@sM}X{`;e~_tU>W&VJqY z{mI7f@4r9glc!J03$-&QM@y`ZPCnb>`eE|9C}28XEJbDfcv-*v=#N(o>p%Q>EiRt^ zbF8H<<9|b;yFUDUC%HKN*Ldgsj9>4&UmyMTq38RDUmvC98NU?++N*wl8nQa}`}0-T zkH5dj0%rXAdM9etpKrsySoj)KFgr5&rM4s1d9M&!LUr{~K#m%+lPoSKQcyciQ_8 z{2L){v5PY}(%x~r^Tf%n?o&JLyzNe(m-hDc4_r7NMza{ag5&6K$WCY_EtcH8KRoi_ z;eD-z3mqQ)r_ye=OG;FK{rxBzkj2%x%>q6< zSOwJN$Z*^WK_ObCthw2GrIFJ-_d?67p$qo3rb*SryRA3(hWD2l=C&TX@8FF_uXy{- zW~7TZ)eN_1y97hW7WWQ5z7mymcTYBfHr#aN*l0f%Q?F|0MuP`LX0OfK>m8ohS6Qrl z5?9%g+(V1t8O_tvC!fxywNA_(419B6QgJiMb2vz3x7f$$w$7RFiwoyqV=sV6> z`neMNh=m_tXXdM%3>#js>(S-981y-G`g<3hwCmu9izSa=T=ge%d9g|?V(c^A(X{#qrw?b>i)sPRA7RVQ_Z;$O_K^23R#e< zKdZQ8v}!?r1U9H@_g1qp!l1+TV#Fl8vnucww;idyV#bo7Qq_x`FC|YIKja206HAbO2WB+|UwgHO}x+^SR^h zs}{8FX5SurPX2*Cu`I}MZ?ZXF*Cd-CO3}?4GwE-RY?1jvY&*savYVGiC`J2J2~9h( za-`5|S>HguGDxcKLn1hh7Y^qa0Y5aV`(|%j_e3LJX-~X;cg!#@^Fmvna*nm8Ha!7W zC54DIyeZM4{JN78zF%tq6ZjuwR!F4E_AfFo9DA{S<%d&aA0FU6i`z7!UI=}_Tb=f% z!&}{sS0ssIeL5tTC_j3qDM`J#oJPC-0Gc{&*eM#&b(b?_ zN-^1MudMs=*4DD{&)lSZ)M`wXeX7^;2vajT4ht~U_MO(R9N&=8S8Yj5XG2zTS67nb zyGNu{nwM-pW%)Sg0^-&Mg!&mkpRK#{2xlNq;RfZ~Gg{@s`k(vTA}_0&D1;!H<()x} z7;7&esc&jdW1O`Bf=j)5wK00kHb(PJZ;mDevPl!>l7QlgIF~5Kj6QX8Kv;q)M75S;j}9EA)w3Y!I~vO81M|+&H1{-|HlsLVT!!CcwDw=Q zbz#6U^%=oNviGPM$3<}aU=3z3a&O4ien?DeDW{X8#R8@mFJ!T7k8XQ!A%TA-Zs45{ zje}8tUTRl~8e^-ndx=I32~$qZc7(h?ay9{_rm!?Su}jUhjgYlG(RjK-z~M3~ z1N32~xt|I(2E(FEx>>W73;4%(Y$-NhZRuhu5lrh8@9d#^fAeC%rb}0ogHb=#)4c1n zjXVuaCX3q2xu!M5B77up4hhnV+3ADo3mJr>Jv3@lc|y1$$vregPJFPQiESN-RTmDGLZ`ir1R+S+VnaW*+5OgUb=#k<-9Cc)8p*!c7iaIeS;{ zGb~bdW_9&{eoJ-ay=Rd4@ZkxQt}~AZ(rG6NhC@r?)@v(`e~%F*g9C)cfyt}u9Dt>U zH}DZBpDxI$r9Z_+e!zf=V=cjxLU5q*_E*@%S zSPS0IJzpZ~d;L>B?6L)hs%JgWw2Lh#m^U`Rs7MHOV~yzD?jx9G_fwtMTiy8K#?N&F z%4Pfl#~BHv89JY*KNRt)5iB}7#BXq77I-}TaXs4~zzQz{yBW2AAn zc-RVCC!@;v4x)TI4L_brm06nl{`;~Hf&EOON;d=EyFFR$wCH`t*3lsHB+%Z%2T#6L z`k2qpidd!~DLwLoO@;{9e_W zj$C^C-0D7zTN*xjzYkK`5O}8@!#fo71QoZZ{d(&dPnKNTvUG#2K=yuJn)yox8quV- z9pmKlX$@)&R)a}X zHe8cun4UvRUqJ#=bQu$}bo=wBh{aNV#eX}}uz@jp<^lltUZD@kv%;ghG0D$VfNiEVW zX#tC{*4pRrgWBR}w7kigDmg|)Pp9GH4$rLrBcc~Ey4|HM*T&H8-3?)-rQ+vwX+76yqiNSn#G%obSJ3y|>5v3$Eo z9;_rK&&PlVBQGtSh=`_OgMhSnuzL1nL1zca{lJ>WVpkgmfKE{Sd8EGw1oI~gIVIC| zUnRa{JLO2oo)W-R5cgexLjlA^p=p3*ffX&wE=nmW(vfD2Pe28UM4|*55mKlOLX)k@1Ygmags$%s1x=?yw%M~2Hd!_Z1G!1=d6fMvt=jgb(5sH{&)uox?o z>TH3B<_VY}G=gNqhuM6@N(3VV<>%#ihg5NAa#MtyScpF@iOKO2f;A7$5)ovRQ?&NT zidWf{D-Iw^oPR?ngMCP}8Fa&~vWrtWmR&VjKiz}}hq$129B@vUr^l-50x%5|{iqjF z4^9t~l;hd5*LOFzbOUh*RjodTwxjyBY38PSf&Ri|J(e&2HGiB_eHFi|8%^t7Sj^^f6> zb%D)EPG)P4s;y>i$XU)Kc^2gKHZfJztve@@&F@qdupm=k#Fi0lAOdUsC@hwV`mNl0 zF8DN&_pZjtlL6Rb$W(zRn3mUc$l9Q4QBV%P@JYg^N?Pv28&9RrO@z7H;tA!f-e{(sr%(g_EaOLam;b#YMONC(M!9|QY_LWFzd6~Jh=Y!kDVlmS;`x9wtT@tlemt)1;bXy|Ke;X7dzzts_yV%qa*XTY{Ziv?^?Fj_%;P6Z?@!1wf`z^oZd& z!Ba^A*>Rw62W&aDDu`nL@&h?*VwvgWOnULm&BM@osklPC-M4HDNwQ)afEIB`BE2mi zN*yvi_MSL76H!1e*?1;K$}!JTjsz;Kq|>*c$F(Y}M6hOAZJS`DV`}4?;*|+j8_RCB zm93s?bKct4RkTv|r*#hN=txeR*0iI;bB~Vek=YDnC>#{T=E#Kb$OcrCxg6!zZRS9A zn&MK5UCIF@UV+TrsjkHbAt1kmwIG=#@pZADtHqs^5Z&1j-aHxoYA53_OV1AT;EuON zL~9zPBQjIQpLiY;&23@h@(hbbxn>WE(E!3{Tow;W2?~<_q7L?=4xeXW39HlT9We3i zG_yT!{0k~MjbglVIXT{2mdjEQ!sUcYt28CqW z9a|QhldNd|?SUZ7GHu4jh0k-MtRWhobn-1ZF}VF)4Ux{IN8=_q7No=4Q~tFoH@J2K zkBASrj2ZJHB!}OcZesJHNEf&^_tcXE$a3B+l@So`yUxCa3^ecS@dZ2xz;+e5_V#h)cx$-q-1e<5g&;JJ>P@* zJ_@7=1LIrIz6c|!fmUrbs3m85EIvXE)Syq}p6_UKFyR1Y47^-viPz#frRUXn{e^sp zvUOdGu+O~_%Hi~-u7?OQxU91^rFdbNFRBSdC+@)kc62h+t2(+JXD=&B@ev--7&n^7 zE#9xaG%XelR?KPEhF3~1{7pF)wsBpEhH@bTu0OO6FPw7hP>91lu?~8sLthy=cm{Te zln^ZO{VNl>s$l}LPV9^7ADM~ooJ4E-4~;nn*S;m|fQy^rRvJpkOc%5?q}@3BvU?2b z&TJuulV&sM3{Jdq9TRRir78swt6GvDwtsoBkU`3zI>9|{Inrs1{iowq;~){X<#ZqZ zoovq`#Qc*Ja0YHzNM#e&+T5ANk=F*Dp}dJ?oNU%FBgtEmRF7LZn-LuBAA5*3l;8vl$JaE8=x5pAgJjw)wRofgiE& z{=z~I{^pWr$O<3GK?hOSXIaQ{{(wLNH(kB?vWYxX?jLTuShxk3ZL>o7q_FpEt_PxK zSoNu%E9d!H_>%-pvEuC*TsLv!Y%jh{xw*9-{_>7&i(RnSf0l zn!a)_$`b9jGbflz_7%t>Wl`|CFjB=v6E^qM;g#SpE1Bm)n2wP!8EFX)x|caWS_W4h zv^|cr*fc{io7NrI4AqzAoev|aa?X}1NIid;j-tW~!=UT&u+_pEfdWeMniZ}<6XWKY zy@|nBTD$9WawX>vGDwSg2A(4LVa~?f4(8o$fYkoCLh~ z|N7<=EV-EyMfMU^_&>JIkl)7x`WW#wvm`jfeCX_II9@i@t7r2JpJuI|n`y~9CgKwq z%7@4=$(*bHN-Ve&5JMU8LI#LrA+Z#@Na3D7gBD_ ziurD`RJzKad1r@+VEQN7>hI;vSgl=)w1x?4+NM9-HV}3&c*+o7&fl&qeRhT&@U0ES zM6Z9Wg^_VGCVlx*ur;ib=C6R1Wv8^)AG*KqR6;_O6)31GhG{(2UZe5;ofJ&*sQWCT z_HEKR!JO>(WU}JTnU3IZGf|9bXULk`=X|avgxJ1O`XoX&QjOoQU{R9#_W}&bnHFp* zw&|6^_(4Px7q$^u?YRx*Fdl@Pn9Z7eI=SIAhDNol1 zom`rP7f5@}(%aka+RL^VvWTk=iEkkk1Mmh}(--^qL)3+YK>(vnoUruH5p%TyL!s>t z;XjEd=2PZKXFokx6-N4e_@R6&|LWT?jx2Jg%%6CY{|WV5U|v4FV7wen~x((CX)~;>Lm3@K>C2w(4|nsy@Yk z>PQfmR47ldv%4^V$rP%R=Nu{Rsgab$FTNlUf4g3L{>voYfC*{vF8g=`1e=-p^M`oB zs*8&OmHD322Bhmq@gjKAU{=zlui0}@$FygTtgkg3V%k~TWbgwomW?N#F8KDfkc;fE z%rjG=n2O2OYoTc4AB&Df>CCFEo8zV&|L5K@7?q9fKaUb5kRKm1#)!)#Fq;9?WMpT=H}+v$(vdaCX-s)jvi}f&>{{V)HZEXkBF$)CfH!3%#!@aAA0FB z9@zd@S9H;U@gFWda3;Gyr6EcoZL4xmw8kQKH&`n6V;MHaZ6QjQcw+nO*Rz>QN`E0c z>Bl8X&<`S!Xe}9KZT^9NR_F9DArxXxY_CzN=3d&^&rS@s&*x)f9_Bl4y&c?M@W)6e zyDwqqe?at)E**dEsHK})vomv_&t-G4N4Hu|dR#l*EG~v6YCMEvquNX*oW{fVkA}DB zB(MH;%MKGYz2f{Bq{15) zMP|W!LN>$}8T#Kmks=-($@em8)R1sat$k_nn*Fwvv2COveC*B%mw9JSPQ8EFz4zb- zR`3bIGPl!W-+*NA56@jI+JH*%pQQ>y%T-=u!I*Tk5a zQ)*J<#k@^%jl~qQMgml{na(NHjIS@QOv(O}vRmzoD#cF?SM++r&A{Y>%6tvZpr&ue zy&bh_Cs$a&aD>iI{3u+)SEsx21;h>FAA9v9xug5Doa5y2bM0kQHihZ|UR*YL!_cKV zI!4DwNV+6R!Cv2Wz)v^XyxN!6@K?K-6tXl{AEQbAlZ*{hLWM>Vg}ju&s5oiap`MAH z-B4!F&KgN;oCv8ncW;nQIC?I}wi-7HhIHInxM(Nh8wX6=kG;w{a-uVtBQ1Y)Zmy6R zlz;fcX|EUwTk~tJevOu+8?Zc6n=Bna`FTJ40!8r5Rw0$O-W;SPE3TxI?eVv&-mUGT ze$0JhQHv3>4d7jo*>P{8=Qam}_Ka~zViQ0Bmnm)jo+)pc9(uBb&r)mvLT zE-Zq~TF>)CW8)b81mj2LPDxq{9HErVRO`FJS`r5N-3-uf|9a2mhJr-392X5>Nm2Nq z3o*b|7%xRvvphT_J2y? z3WHsH{$~fXmEn5$e<^`ab~WQERrlHdQcIlGi5nRD&#&}(oyCOfxDxo@{oz7lfW^oY zTnYTF#bSQK^RYK?-@Ua6pf4T&{N?Mn-VXuOCjJRR8v`p1RuI^#$MR71X)m<>89lxh zmG%vR?a#W3yenJWiRNNjIo`qe7JWju&o3l#Rdu*6SMUx-=q_?7A+ud~8eGbrzW>Hv zmg_zT^?@L)G;i*mtCJ3`w-0c%Ptj`HD;U#*omPKRKXLf}fe(K<$t)1Our2QVp?J^L z(<1ZXse_kib=0;!K0_m1ethMAQfv>OaJzq~OR}W5M7`N8F8gD`1(DgJ#E{6eB}m?Z zOl#{?PEU%qzy8n=%vf{WG?0_CxXtC-*r~;9ueZJ1Qc&j*68-M)q4gPC^`|YqJ#+Wf z0$m7OXIphSTdX(7;K3H7Cs+NnbeC;kxjyzY3NbeKt=Tc(Qx~2AA4+_ob1d3=Yb8*je%HFTgbCH;CPLsAxBqfEnJUDF_2o}Hrl zWdQapln?494sb?GHA}1cMR`!;Z9%WIiPXsOt16p1)!6#nS5NI(0PBhBdYbzD7M@>) z;kXn`VV*9foGKx+T9wGY2lwhY#@!4sHXITOo#z)_x{+TzX%fHUTs99^TXwhatAM>s z_Q!KN>KA5DzgApg{E&Mu7~fGuKi_T`D8JC)aE;7H)QK#D5zm)OR`GHZ`6w&9eJWPh zi^FGydYoyg`|{-YFvL&8!Mli2`YGC;VqoG;i#<0|thFeDpW?SUMA4_MXU7;Y#y31H zEhUM&uVJRM{zXK#9Uq=VM*J%79NwPU)+t-}Fx>#}c`hhhX>i7kNj>@fDj}vXmg+|y zb*MR%xcAEOp7D#gf&c+e_GaL#N8CoUsm=?NjOHl5+OHK`=Z-I9`zaGR#Us?r@Vp7QYK){(I>(3rE$J8`P!rHg{4oTdO|d@%*^ zB2Ikh5LG<{3_>|yLx;2r*TMqTP-~b30KrSaSF(2NHBRE$*J;A(YPbWkrcTN0Qs4a1 z(mwr@JoDTvF{0)M!xFMG#uejXS>FR?-1b<{g~IFemDgefnnWBgs_(<5Hx}t60h&9; zrj}_5G?n|5w8sRb%~ECBIk-Si&xia}qtSjNzwoJopj;>>Yg#^AsHf9t$>l+RRtN~e zI1KK);tYBFD9*o3^k}H3S{tFX^o~f>oJ-0}aL^CK-(!1POU!-cIXf85x|ICDxgk=@ za%&$MRn?hN0*X`H6SFg9z`Ezslv0p2Ai1%6=O~*&;mls+N3s%)&H;6eVG|QJ4~|MQ zC6L{`bsxB0aZG9??)y#AI4gq9y%WfZ^<3sW$`<3K1axa!A?1#u)W*EGMBB4p&8x8x zrK|2r3ubGt#)Qe#HM19BtTyUPBCUSwj7nn0}<;B@!kF z?oTeL7Ef_;-p1jZsZ{V4W`yQ#6Mn-BRzg)Y%Y_rwMY2*10vj*g2F8*7&S z9<$raBvKGRCGIoz;N_EkbJ+!BVbd;S`C&1$lHeR>ifZFbGJ8iSwgT4VeE^n zMaW6ph>J~4i9Yz3BYGnOwKO3r(iYq~;(nfLdeC-wS=4V4aFW@f@=J#uJI5B--?_af z*{xsW$xdaO4wr0a=R>87oDNLpLu$h?G4S9>64P6BIId8?)56Mk%-ayd_(pFj(I&po z(O#uKrKPcpmQ0#}?X(oCTBey5XANd#;Y+RAs;p@TQq-FJDA6>HIn2=6+36Y6Ufn(G z-NGgBJsXNqN6CaZsgVJePyDM#*4QGo8qI`4?t0R(o>8JU?}#bjqSz7E;a@t?)D^hw zrW4oXZ18RM{JexGsv@YTB3i#d$swA}Y6KR43ukZ4=OgC%eU|g{N%eYaj_Equl!J8+;4&xwz5q-Q~4U>#K)uaaepU0Gw@q=M2gR9C;QBrsd|*Vs_`}A5 zB_%Prz+_y!K2Lto;t>a-pp12vlUXK5IsD)l6WYuaR!8krJb`<0`pA_GeuqBGbC`mw zx%@_30~x|_gyOn?BUa1KYRqc{A;*lSUVa*DS~ zs9}NTomoN~E`USp`O1zsKeH9uaCGyxInh!%Xcd?l5`cqo-p9s8OjH(2o9+%qvKAVpxVE$V{>c~lg zMnmt_jNuJ89nTfdIx-rl;pt7_NMQKVDk91h=rE>#+D4b-9n3GFCZ0G+dp171$&4&5 z?5R;DzdxkQUpmKu;DW}`bi$QgM!t?r`5u{fV`wew$(U_kQXRtmtQ{YaI7g^72r7F*$f)mY+HFs*jWn z2*$i9k1xTV2`+UP5fjA>ga$oMj7&EKzN7=pHG_IxM?i=D*jBpvEC=l#()wx95g(xNl1xy6OO5uC4I{@TxTg79N7Gx@@W0tSzPxl`}O?Xi5ahS?e z*GK6od~DHY^_*<@%%tBkXXgke5$1s~=9Y6T|2vcDra;OH>!3njs3jk{pG~``GgaAy zuU%M#+2Xn0x1kL@(mY1)3pZRDN&aL_&EyjVd|)bO#pA9;>8x)$qEQaXnT5`g0V@`1 zIfEE5L2j`SHA@NKk?LLiXncZWjggONWM>6dxA1`ao9cRicPd5&@)=Lo*t4x`TIA$3 zvAta>C3P7JNm&K_d=>N=UG0&AfurR@l&@ODTyIe#-5Favr?T4W&zx z1R}(7W@;ILDdOlaH>Xo%fxGy1+zt2z40$Dw1mhW;vZCHy0bm}9G3^1(twpK4}4uVC}G8U4gG<2`UUo*Q!n^A}?a?Dv(pRCe?qDBR(?E5mWm@V4rQ{ zA%hDYev+HQn}ftt5zHMld^vzc*Y#_!flP@gY@31jU0oy`EyU)8$*3*^V8Qa4wWQ z4Nb)mp;>SR4-WsfZmDkJS3pz}FP!*1%O$0d8R$?cX|;W^wP<-kxwAvenxcLDdEO_$ z73T{5jvRKUXq;qIPVcYfkdq`KIt;>%LzGQ2*hfkV)SaT> zb`Hoc90eBt*ir^kPVh*Xwfo0LsuMF5J9I^F4$E;B7Uelc# z&qxO0^ctur#^dyHmeUs$`9*fJK^?Icrxjau?yko6ll6l45wEltG26>|kf#E=vJLrp zGSgC6Rt^kPzB`9uc&;x#E^U=akKQ>!Qf6!^1%`P$yb{ILTln?;!Xhi_!7ofWkPnUf zKpb0?Lp!naGyY(?8}GU2;Tt0v5#l5B6l7;U@<T> zx80D7i4X(d((JCcmB>(nK<6dGH8hdfXGtv zMrO`|x_<@EsteGK5!!OFB`A1@(q5R-4+TK%Ag zj2EQJvYuiF+)bkdb(}OHS>z=_F!%7ISQ3rd6T6Mj>VDRu7RL2HY(en}Zuu1+vQpfI zj}qA;BG`u-Xp?bCr4Gwr%$AZY=|0O1Fy98LN!sdUu#$Y4fCD=T7%Mr%+FkSp{n{Y+Jw}u@yZ5=BVZ(692%| zaRGpA6qgFcSI$1XvQvX0X+2*r4;7M;9EF&!fM?)~LaxDYsK?C}VJ~;Nf|APt1N_w?MOiuPgQ6Lp^;U%E2K7e34 z3+;@kGvLF_Pd)QW(KB~JI`^6o?<{3)MyLC>a&C6>uTR17{F9WG()a=qp?k|(TV8)D zu4HA%3Pi;D97n36yPI=yAGdd;0Zn5i7Bdo(Wd+)-!YjO+4F;9*;?6vAezAx=K7wj+ z%}v+C{vvupHVucDRs1GXS+c{OqyXN5JSmaQ3@Z^6@X!C63faKr#Czv9R^T?9`+hB@ z^1Ha@!AFs3cmb8$QE^XMv?0}M`%;GDX0Zg`dJWa(!ne~VMTCl0v?Uo>)+I%l&bgjEu{(1doSVN@^)&gM(^v zKM90l>q129mU4;6Ik}6{Ev3zrq)n6`Yfl%5FUBt&tmH!MH1wx0+S)qj;{B0dJU~3# z|0^DCk42YDZyQU<`!9zg8+y;iO{wC$Hkj#o@Oie!IYMi*M@^lbKGzA*4c9ds;K$*v zCUTd{+OCGU6qWpXb@N5sHw26CZNshw*;DPolWj8MeP8s)TI;svtOc%W8!_4(?%iTZ z|H1T|%go4dLcyKq3F%Zx97WobyxAU&l8Q{J9s$OZ-=Cv4 z!s2sltO8sk)Ll=_f89ZOeZ0lN=)3>yT#BFQp`WkAK|1BcdNlvt{vU>#SLUNhzn5&| zlS?F5s-!FZuhEwtnZk_u@T=8!Lp65n#h&cV9}YU5KK$VMsAbx`#S|?Tq{JgdGClZp zGlxE)v-#*k)k9C-Yq-orRryzvdfrzvs^?6+q+Yw3fstv7t5?^qV=+GwI1sH8aG5K# z&woPB^)0~G83A~yLcT`=0qN`j5i31V8YDaWnS8EDM3AoBH$0zuM%lH>?{((!Rj2l^ zo_631%lPZs4@}ZVA#0zdH%H6p-kA!d$&Yo}iiJz8)ppb-AjGM6Rsk#zqhs)17%!kc+ zKb@q$6(@h9{GUgsejcmZbam^B9V)+0=Kbof`dKm$HLpaYKcZAd|G{%F1ppSfKu70{ zZ=XXSooljE5u?nVpUSvaBAK|dN@-O*df+HJumr7stf?irS;Rx)rMD|N$hQ`9-=;sq zSa7iu%S0^3~;yZ2P+F6UHQ z>1@?xW>#?2(7kwITWeLB%+}qy0Ew-z&$H|J&(Zwg#Q`tvO_h#Qq@^M#Z+TS32IrxX z2bHAc$_NBAsM-v5R)s%&`wmxkJg{37QE8Hk00+wEg3V@nHhG6;I(|CJWyba7>|;1u z44#{_SuKep5eKtOVIIRjO+D`}esf*Uvo7 zwkRz%Fjb_aFua3}@=G4kL%GKf;)$)rhMTvcTeVu+H7(KMMK8=|CGvd@g98IY0!>=C z9SrxU-SW>EOVJ59dGzs_cY$7R`Um@K4~bgqIr*-sTi~s0=RRmQ@gBuHn7qsU==S}y z1$BYz-d-)16 zajQIsDp=<^lASyOZT3aVjD!PzDE^2OfZF3q27-p$tEUCnAtxzK5Bpk7*SXB}Rr73V z0qUyjz81hS^}M2@0=h{*MJXD}FEAOjPIQoMaSKzB5$BCu->NPGY}8=|6_Ui^e^0>_&2cl7w%61!vCIjC1eH;NgACnO)Zaxz|3KwPneeasCy-= z3Q}_~Gk8NSsW4IvIuy5}tp9?M3ro@vbX>id%A9lW(WNytSGKZzCJg4@!AiEeCu+rC zzGEJuZ{_5;`|_7p;jJ8D*yFjM$0A#2C9O8}{A{}Li1npIcT>LEMeDDedhTJszil6V zaP=3puRre|RbnjtMt!ngy>&_FEbhWqz<$SFWuPU%oEkgIDATrU*f{o;P|fsRw0pf8 z`#^iefzR)Ab!@Ie2aE#_GZQF<+B0}*4D1ysBPoTT+MAoY`Oo5O)~hna(o(}qH#BAd zgYHt8t(D>i#1-Yba#gV75@dRP;E0Nq(Tv;}fiZod+tcb7eZpz+>xKevvCFlrO;{#e zlnCqeX`+iw7<`JFjvx>Fd7n*-=wuYoaV%3KMafqxB{~rO)i72BKVs#o_WMST3~QNG z@81d{-|*|r05W6*a-P>2-Wkc)hu9`2!sOxg-nu5gqx}sE z)!tfZ#uhSLc6+bG*fi}1IbU_!-IrP+l_w~4qV0I-V8a~t&E>i|=Is}aRu*y&5U+(= z4G^sDq=S%`fvA9z|G5>>&z6qb8hG6~%_hKs+~sKQSYrUqqbR3WmpsO(38;J9V&mX; zM8wvusI&p98z)5s0swr7IA=_hkJw2>EI8Gi(PT`HlO-BLjIbs|cTC(*#LswE5**{i z7c}QA&djHzmo!#KEpK%Gc;cK_JAdi`O4uOOvh9)jT4oR2G}Cks>4TM>on?NyoWg1S zT3E;y{9GyDHZa-Fi(T-}Iy=}kgA!r_4KFrIpJ}lwqPkn(aii=KYCFUd zzKGhENm4OZd7%;FxGLicx@btYTD0K%%|3X~MuyLE7u=w&w}%gUpXPtp=|f<^{5h-Z z;gK`bo!ofv*qn8ei&M(8wto^@LF3qO#EailbYK1vy@2UuD^$?ZNY=!}$-o5g=oGA` zaY$k~P0^}6L*c7&9b@U&n!rs%Bab$A9*4Q3IiIDa6O-#{K!b}85yEuM`N$l%{6#L* zUP{9qiWw?j@7dG`GqX!gH)2Jf5BCvVBq}B2RRzbh`*0@kS;i@E6(n20NagtI-eRgv zbMqz$i{Y7~J{UK+LAh&%=7xM^l1IT-RRdKVF;(+LRU=7WNJsn7ut%Xr28vS3Ky`q9 zYm?0$Mj?%OOy#yZta8y>o9@g~3I836o)!bPHH##^)u%sO^@7bwIkeCjq2c}%U+Tvx zx~T-1Txu#rB1W^~Z?UzRoRa=)mz3hW8?5&5isUaHwjKa1YUhh)jNS;Q>sUm(DTK8` zW?;l{{#)uO|2Px|CcYFJ9?*fIKyS@|;qmtSvc-V;NiMO!WH>iN(luV^woNSNYCq)|b?7HR_Rjb>{Lq7E2K z1O!rGzLn-ZK!5RJuX+Lm3^;s^u~B%B?|u@78+`eP5Zgb#s&}khJ?(lmqsL(7Ptm6y zNHt!0AfSXFz?sQc-ptu??j8x|SM(7Te+z9ti5KAFUhPUOUw$MZrtD`;T`cd=E0;Qf=WxE^c*Fr)x0K9#XESyi zykG?EPF3>0#a?rthny&LrHuJ~G)fYiRII-5y-6l2I?Z})i=R)bU~Fh2csGJF_MK*j ziJk8lXo$IpDi6?c2Z=R9>inAkO{H(Ud@14PcE+TiDk(C>rmVl^t)dnHnzP!6uY^{G;QEn@NS2uFjWOWoQn~gzN zR7$?={C;)0$r1Ur<%^|zdb5_>eQCUwvE7N+AriiQA>X-24v^Vy&3bna4%T1q-0ynr z&HUU4gOC8z^q=(>M-q~3M0^T|_X-z&Lx2Hb&dU6K_X>&Dli@~}S*cV*8x^VY#L?XN zxAD7j?)b=jj#*6&j4ulqxzl&O!00olak=rCJIUzDj(~ZM)>W~1<>!m678Tmxs^7SG ze%I;y*FLP98dA!>wY6hgEjcoE?3@ahpu$z;o9c{~s{hE>${GJ4SaXm*@VrpZneSiV z`Fj+ray!#s$S|vIRL+#jHt9?FGkUm`M6FV_Be`a8^>$novwu{SZDCw`89n6U@1C{k zQ9jK;tge&NJ+S`zmp#881^a%!QT-P8v%9f~kj$ITn5&AF$f>zJnO@kRCx_J0w@=tlI4#N#&(?^@sSkG5;GciVeIt0`QLX3bbZJ^&!)}I604)5 zz!+WW0~QWXskX8@ON#L?I5|fLeB@5t5*)0@in7n!|& zW9hKP($$bqjhqJ~37-pa-is+09c(uHhwR4%mw<5eRce}uK%OX9mLsN^-Jp&gSq?7h z*wJNMt`Bdyl45(=8oq`X)Uqu=wuK}scjpnWTjN3&f*w9Fy%Ak`Z45~lRnC)C92+FK zOGzpc`20}C*XXLVV?c9~qAViMz7Hphk%=xq5(9_{Z~gx7y$#QhbHqr*(3P5#h0;G`?bunPncXjzLpGnIwMgPw$dlCSrnO9^E=PC8Wgk;#05gUH5C1SviN#=&Xv zFcQyzr#)laH+yMFm2g~?r=Pd;;8fxw_C{Bi1?oGz9JP~*yEC*Ug)Z&zf)t*im~176 zW-ziVq;NVfaNfGIdM&u`Q-S|+T-IV<4Iot>No&NyL`;qm!vY>5lNd>tD&h412;5-l zZm!tgaP*DYC3o|;E<3)gWxak*;dR!(_eY|{5YLsO$$+1l)g1c4YcGHy*`jhTk;H>H zC`6nrn2u)y+iY`Lq*uC1reb49F*)kg-l)>Wzc!fQH{9B*P2Rk&Y4J~8SSsG{B6j2; z_LuhF-!EPmgD78Ksg`3heZxO0)lawe4fmj%wQe)5z>gXVib0X`#!;J0j+m?r@@mES zjWu#`WPUDZ3cy&9%3UQVjD*L?8dwY4qs2G2MMFlM$88CCK$-^tJ^;WKIrW+?m%5V| zy(0SlM56RsoE0shIWf-S7IrFF_!dORw#4sjiA~*yFACzzkc7Fot#!d}CLx~F+^`Pu zkpoc?N)bU$7=ZYYnPhDl1`sjX_Q4u^26SW_8YY6&`HyZII1dz2b3g{E18F<75=-qXpBW&MTkdNt4aU@^TLZcM4prs z)=fC>aX6m47XOC}0c6F^xcu^80^I|MaeSf$lZb|t12KtsX!xU*#>Msp zib!g?1TBRyqLCmwn88_JU&Msm@pS5L;;v|t4G)}}hO)#^uB72x>io%6)QI0SJYr$C z3!cyG*sLIy#Uw`X*7R^7BA@6T45IlhL@~USv)9?AxMg_7<>$wR05E7r%?^^yW0xk1 zo9wTwh?TI5_^l=}iMJDBWjFj#O5p6`mb{B*!RQp@bJucWoI=+~+-csg{@+10^42(P0Dr}Qd$HbZ6rG+fQZe16Pe%i>P^Lxt?F|9p8-b|Dc zS{7l1hN+D7R^QLIpy9F~j@Wx`Ww2aU=MC5E*x?%;U_mdO9?46_!V8#iivDblgtWN} ztXK#8aL?Up?k<$VGkg3 z#UWMkm(1F4BYGIn9aXas13fO>^}{Sj46z}|2At82?v3jR{Gmrc#3#bz0G%uGXAx8+ zq}8xFer9i3LVDN|m+*+jkoR2uf0%pkwAUSY%hEKu6@p0`?}8geS7g2 zNHQUx=Xvh?4JfuB(8u?*!miqHj%&7_l^i|3oIO}1bB`A_i3JfnIvBvp(}~@@)mojz zc5-i8vJyLEaUN8VIk75Mq7Ywn`$xF-X1&2%wp6 z6`eaKQ!!*i+VRlHn?tt0dvCr83;#&U>s|f6x=5RI{^0ST-G4KXS{$6%=Y;tEzSU|j z5OfpXj9;+0U=o7@_D}EuOZ(4i$i)WHlV4?NK|U>1luNKeZ~0^J;zySb{5tg0y6fCk zHTK(!v7w|1v%5FHm_#u5?*@Pzp|36AA>aZ-+s+Sbly-Mqi78*O<~q_~Y0-1N`?*4v zCF|%(wz2L_m|K6z>i4;Of$BQK*_**`#N?ad-IFFtHTR1|Bu{ofz6xCDgpj182PlF~ z09haeH?*-X2IcD>Ve?49-zY>qb zNYhFvOV|-P$7}pn^!D5VswH5sg{gy-BoKplQ%u}|y|0Q)>{?(&RDGND3eJcOoky8E zKaj@-MT-y)`aHJ%=_RgO5IROviV16?t(4rDINIa8j>0n}SC+E{%vXyiB{fXgwRT|u zmnaYsue==<{s9WH!MF~Bk1ZwPiOE|p#EP{tuBk_SQo$J{SutQiz-4d z74#Vp#8`9d1w8p~-HJ`?4PQ2W3f{*Cqv_=E$AQi(-k8Lbt_4yoT^v;CszKIo$k(m*xmp zAWv`lSfZ`JFUSQ(Gc@S^P#L+l$O zC*J-;eDrQyVoMS;NY23ZGA6-T3fMi5AO2KXx&e*cY`*ux{y_Ix2bvLOCG$=$&>8e~ zrTXCgqqScILPGvU6E94<8!u>%zvav1<5}i0Y`Bk7a*z%F>kt)2-?f;#>dFXw%naXH zGH@d3=mJDnKDkhFda(+)Kl;^D=@7&w-&mfmZDn%vY{t-b(XAJ2Z_fU{l`Zm1Y`p(s z%?;&0V+a4Jg_6zCSKlw*6k=VAH-N@Ah&x*}u9N4}vK=-MgU?%Msl6ke=4!2g!?;!gYs1KO3iZ0dx`&UbD4kR} zB0h4mt)ugZwd=`~#_GXJ`%d&+I(~NOau=ygC8dHl8)H^JGUDT4a1$qW?F=S6DIF7g z&~@%SfAR8f9sB>i*p}<98NHc8{-AC7gMX^QHT<-wkN>i{BW?8MuoCh?X(~Z`QlKKh zz1-t}kNdkcgIVd>KK;A1HFRw-oIzMew?)l{oPjVi(!eP#7T&AdnVz^G?MiF60Rz&-stCcFU8*nDZ9M$ep(If z$o2ZHwWb;Cvioq-ra0;PMP1hA+4NC(aqq7ANd*TU^B1&In6dUnR-tgAunpsMP;^7T zsGo24|A-GPOr~UleN&5VM{QVV5}b=wgR|KIb}bu&-=&?&{T}PVqji5w-l3+J zmG?jwk!z&M3SRg#FwK8Dodtq5kd3hAoUVjd>EENnai1I$S-l%Cut8zp@hr z6Lrr{H)T_aV;nLgZ3NP=6-W-C2JEj*5&rM4Chd$7h?4lSO&;UBgXTXR1GS<`rm7dN zpcjM%lZN`BO-MkBX?TM$VNdd&oEkN)teF(k$PN*>C-ri8A9eMqha4R<8X$w3-?xegj`l~6_2I+S$U&gIx#Ie;~Ft>+i1Sm_>v9AVumOxEyHhp>E3LKk{3I{_Uu@Z&|4#3or9_VbJ_Z|apE?~02Q9v$|D*a zMkQ4BDCP%0eHPkJ{gqwtPQxrsei`|oXp`BCiaD7y{HV)^iNQ2A#GcdIrnEPwHyHh& zyM=T>4i^0<+W7z9EtI7j{-5lqed+#`|7J%WqXhf@KkTSMo7kxT&5pW!HCTTH-!0(m zs8Y??*r5MnM;&*LHBa~tJL)4pC{{H&{nOuQW8f+T#eLEHE3gltXaWnN#E{Ov>?nN~ zv<#rY3+EV%GMldKMu+AQEl@>cbS*Uqxz>K>56 zd4J&Mm&+>unWT>KI!^s_NzHnRRIdxrmXxR|_Xc*Suf%r?U9pd~KPXIE5$EE+fx~MJ zsmEoVt(#K^u9=r8{(QJfw}Z1zfJmXJbu zm8d*qX_*ojB2&Ln?4$e83vOEaYL17EkO2}U-djo(2UQ~bfcg2an%&kS@-bUfu=n}C z!2%b6?tk%>xvNz<_{cV&3mn)IqRUGy$?>#YS-;(Is@Q0I(0_DDJC31M-@JJ`?g+D*vtXKF=UC7kHxk6kWt#F1HRn?#4Nr)C!c+D#5vc(=WeRjGOaQL@! z^}15k&~d=g+UdNW(xdS)}JWJJNTHApl@WuKG#bJ@UH^0Q3%L z{Bu~bCgbq%`q$u#ou{eQAO?uS_r5C`pBl%rT{T1u$$04& zMY)mvO0LI9K!=!5+^)_I88q)U{HRf&wxJMEUWj>ua09)K7}?t9r#fif=r^&$2=dXf zY?XFw+O#Cz<9Yqcq&_`(%HumDPXJ_V!Ql=3+4I{t82}7o6QDxqU|oUw+bg=Dh9FBg zi0?5zrvJ82W8M`_oTp#rp=RQEeZ+7B&-d`H= z9x6bbkCHz5_XAoM-a6iN3i=x$lPx4V9akhCtOP_6!Qc~T&aTDTQTaG8`d=yJ^M#?j z_90Q3HUg8)8DZug$?MA8vx@GI33^U?@my5z+;eVmhRW9R|E;=zK}QCb=bCv;57qTG;`fO1u`PPXOPy6V)t}H z=?0jmK@f`&<^N)K1CN`8?wlxAM8K?_TTFM%$2}w6ec6_X#N0|sRL9*R^L`H~;>JAY zt)Y2!dPnBj&;@E!eIzO6MA;{5C$ulo(!2qN?4QZ^{S3;jo0DUq>)xkF zpD_7uC#v)yqVV2~f+g;_J7l~cvFHW@)<)uH!P}>;^rk4UCHHxw32-PXFs8H-910U5 z^JyE~C?Vf8?(VANzxo&3ziAFVu;F+K(nog!B0OaYjl6Ci(fC{``#BGCz@QcD(+VT* zj0RF>Jhi^aN@NBSx_${uhphr%(d<`@zlzx~h4Xr|MKGMkk2)B;KWi-pDr)o6HSq2~ zTcpCaV6FEE5g6$AD=w%VA{v)Ha8F~vy6nE(hq1cuG%ViEBx=@FQ-)Zz0>ZlKe#Qd2 zEIM4&S=G7UkSH%Y;ZW;TRVPuvuXQ?Hws@zAN3zrAd(sNPL)@1|`>hqo)8J(u{xUG; zK2*CzO2=^@1br@#!3L?UQ}2}k5#K5{n(Q*oGVtMmnp=Kc|6S&>H=53O_^zC(SJPD_?4A8Uf_y!FVL^ZucB_(>1;tdYbMFLW~Hm$QPz{rLJ2jbItiAC$ywfVKS1> z@!I+49Y|7_$zl+XJKSbuc6Qy+nwV4n%<V*j7>e?(FlBf-(}>WFfpd~2 z5QX`LH}4R(HH4v@dzJq>a#_#2M90&`tUuW*W+e`y1IK?nvb(A5!L;hmr=Ka#dmVZD zXH&&{pgVy?q8bRj8k8i0&N8OL##6?OpQk1R?)Cbr^V(s~bC4|4nXR?#_!maGkfEi>fb77N^V0l;_ zhdk`$!Z^b0<7TCG*hbv^9*;pvEc{gKRnZ3`BFI;ce+>h5fEg;MJX{GX2`dik>(+!+y&kx7w_5(vtqdz>9nTVX7~oaR@2b8f zBp+aF-N?BJHx=)(Q^C%NNPo^!vP%_k z5%RJhlrt|#Y+!zXX!)|UEC)-4(pO5x&a+P254q^oE^!kR-`uN8o7(x918zqvwhPm= zs?tajfQCY3`MzeIxS-FxXOq)sa+SfF#{Z9SZ?R&`#ws^D-V&MIBUZgq=*7u z-6Z=kpPkr>n`u!!-T3!?5^jW5K*{#cb1jn>cJJN#*k@rbo4lQ~VD3@!_bQ^RRv7e? zY~Qubc*UdaQ9 zHWFeOo<&OG0MUU?@oa66eD7CB7cDUXxAGXta^jmwAH#@LA;B93Q-GARV=ydjc5NCX5s#@sc&<36e_H?%nisa|}AwvTg^H5as1nE`=;n$P`6ROp!*y>Bqrj_W`U* zoL^Q&x%L^sbrk76&5bhf=oV_Ei0qoYekT*MV8hJSWeaq7hjU56V$~`M@xYnGrK>|@ z<;2-+s5Tt-!y3lXT}7Oo4nSiew`zum{NA@!AaC8lI>{G8-#%?EWw~z>LO#snMpm;_ zk_fN3AB(G7N|r`e3jr$IKYxOxg+V?T#fC*t6_W3@z^Y;(XLw$42dyHNTI-Jha+|f1 zk|G&k;|*FzBeY*)xjcvEH?s-1ORvA_e3Y)OF%mhT|;$mi9Aa!&>Q%>JOkbD8XC|;{+wQ zttDMqXKg`vjhs}B0b$HTUff1oiL<8{oXI(-ixISlfCE~+LzHksO8j&mis0-}ki#aT zee0XbnxtTaupTeZ8==Hpd2(%CaEyfMDZ}eQKs^0KkbP!TEW!eS4OKlK_XebhN!GxT zLOID_jglhSH-x|PMerHl%!`}TNo=S-(Bz#C6~A$orXs$QaKB#&X$}ZT*W=rh+`uZ; zf-~uEh#@PXxW3T)4Zyz+XvwQ_Bf)}HnmUu%up=)51#x8DdP!2P56)xjC`y6W>r(oX zqEqh@gU_ks5wWJ*ERqj^4;@b!u%iCKV>P^AQHsQHNo!MSzQUM>9nLZAT5~x-VnW~D zj6Xj+7y6A@BnOER6jCRF#xlfHoXs@|qOX#I=hRbVk`Qu^rhJ|u1>^G?`d~4a;}b+P z&WQ|&Yv!LxGZO=0bogtmp&uI-aojJvAx4}t_^>NN4hK6DN+OiPZ74CAuuu*YYbk7$ zkl=$5Ip>9J@-{JT4)aLPc;EFZV!RXx76G>Hu6W*2a=b=41Nl(qm=uGsrEsDv5=_55 zDc{qC)oijs+GESQY_=TSft+ux5r{zb*N7H#_%i-?zjdYy=@!ba4@1dGSybu75z0lbPm zceWl$!62I*028L%y+N}>fs%7B)H8&(&%nqr(3RbW$D(#(qN9X3_14@|8ksKxfZ;`7 zHsjk*7$3G8p^yp(`V-1AKnZ}%$yi7{vw&8$u*DK9-Y)5yT7bN~4=b}kZ8kB4OM@VvzjsbW1(MEzKJaF_qmry|VI_M7&jeiliyXu~e&;Qr8~-Cqh(9X9 z^$56@PW(G6lkG0s*q81d8!sQrXLb~0XI!NTr7}>Ld$EWM-eV!!vLFfPRF{}EbOW4k z>0&8|3v7@jVn~b4(ZbzDhrrxLNC5Zhae!zgB|*t>G?SFXf%4LKZe^1I!oHLqlA_xj zC+@v!I%Ed)RxUWgw4puSW23mB?^Yvpy zS47?tLHh8AoXnc7v>Abv#mh*-$+!hX>suPH$GH)}#Ps;Z`(MY}g#ZzSwxIxVRY1qx z3E7a+%1?y;;}pai*%*C2`_J$W3DJBF61>FFRnmG$^iK~LRGEWl3S$ao6Ep(~C|gp* zf@hZ?$p;YG*U?TH=*|M{9>OVuhjYduMd?3Gd!XdN^p0KA&HxFKPbXh6@|92XI7AK1 zR2clq5Fma59kz>$ewP7wT(oU+S&-aFPvl#1fP!M#d$AhAFT&}XOVW3U6Za*LOS)x) z3#{lrN4}tC%cuJuka!WA=7VqEC=qqM?lf@>X;k;!0-+rM?4f2(S-1Uw30y?YA)aSZpC}@lcVmdpi^Pb z$rlLd_RXm4`!@auFJjoI%I(*k{_t3T1n7qk|B{c60e4PNmPvjvEpJqFzqT;LqE&v! z4~5nM$inS>!FDaB!qOf5$OxCR|Jp4S2KgX+>KhsezIsU0p(Zt-qiP7}@OCwRU>SkH zcRt~(vAun}#)<9D?LkO$q0cEn({|rW=Y4}_^Y>R%((qg>_d%|0I6HD3W1Y+(?^zyW zhih(GiL^36x8~Vj(#&V&6x?rb@>xl%#sJ-UK6DjWl8cjO;_x#1KyhhQ&l@PJZM*#H=Z&IWO$?fP%}5hWRkwY#;SNn@(oFpT%S);sQ}t4c z?*g6%=~llCZ3vI~?eX)jQCZbd* z-#kFn04Dl~i1BoR-F7Y$(+;t5xsF0+783vqsgeQ&p`pW;BdwI{~JRS2g101PWPK_akGgx8~}piq`>*f^3U<97FSb7h$pS?i6gQ zX1_XvZ){+i$%?9PVGuQTq$TuMMKvFLQI}y%9E8HZv|Zg>KjXoo$~8QPptawk5d}DW z&s7ae7BC0MAxWy@?;|j6u}ndn;ZU=P&GgWo)a?de9nqOX_o=Fpa@dsqtoDCj}iG9z3{w z&AZP1#E`kuVTuk@fX|LbJa~lUY~ck(LhL4M&x9Tb)1HN6L&w`O?9N{ z**@F7&LOyII)%3FJ#2l$tuvm0gj|+Jt?65?z>ZtgLUY`Odq$?)IP)KU{u1H~F2A$$ zLGvo}tU_9<=_$g@Fu-?5@n#09twoZ!!$l-^jMEMQpl?c%@ ze1zxHvsr|z)|ldRpQ(AMBq&mQ*;Q8fG=q3={OH`R7v{Bw_#y@+UbkF6hP4#|D}SF_ z>=mTX@uYf*`kh}qC{~K|P{{Q6ATZIhe_WZF=nRV(#4S{M%b?_pJu>tZ6Vw53S(b7} zi7tfl4sV*^1&j?CW^;KYeE>40HqzY8;W^cKNW{fJZxMi4Fnk@e5c**{4424&l`|6M zL{2yAYfS&|UL@UKXIL@X+rQE}>6d9Vv+IMmCdstp z>f_=id|JNRT%veR=~!=MHjy3l5LHujh{;078G{AGBDk`bTf;p_Uv)>Y;^u9BiCs+4 zMmw2|;yv2^d=8t99m}}O{_gb-+z6hJu=BE)L+~=ufE|SAagh5mMTC-3JH#8SAy+M;S z%Ny$yTSJOA-oAG1Rda(U@)+)5Gi%xL$>JVLG{5j+S1>^_U52HmeN zpUfNAfllYefrXk3r!mV_;nNj(rXaGaghX}OK01&<#hR(tD(vTE`j#Bk97u13H5{sA z?e0fyu4+D{Kn=L;K6kKUolba(^*UtV@QWft2X*<-T3rU4(3KPYN}~ASr$YfL ze!AendK`1-H>)23r1xJoQ%Aj(JOm)9n!w?FQm}Z2M_U=y9#M!nQBF`(yTOEpEt2GY zpsSOV1uFUrd9<}5uA>?;gCi31f&|>Z*zhU@zpc&hEmI-;a|>ceJVtz!xRQ^T5vwgs z32Hp7EnsJTml-W!i?&WiLG6V18)!P*)Y zoKJiZZf)Gxkz!Nk^@wz=4bbqrE*y$J^~_;_8iDGs(m9yv*+(z;On^RB)8qxS^a$p% zZB*+FAstA`AQjSRbp1uPn)=ehll4W?!k-NmD4~WK)@PP&B2b|1c1)S}BrXo66KetL zu_}CJu}XoyO$5Zgsn(>*D9G=%8ysd>=J&XLv?XsKGZE`PaRc!16$qO*qPFcd`_|n& z#SE5vtMrJwm1qT|gDn8;a%EnB8iS%DGh!iVfkOiZa;q9`s%4mC$K2o#j^H6X`rcsm z34YE;2JBe@lCv1fWqhHUkH~cUZy7Q3s;By43x#=73DImD+b~#8GOtzkj6mbfo47^B zo@oe8dx)edKDj(z1k$I;3YSrWiqpv9u;#jlD>y6m;%XkBdQ=(?IMYP&Z z2840}0^D7e(ISLw-VM;g225=*X^|S@7tD|n^rFxbGHRo|d`3W}a=ol6QA%w?jl~O95@&O#SzzY`W55tb(^F2pku~7VH5_{|%C}@hCLd>Ia$0 zmPVFixslas8h;l*y^U1vqXAbp`vNuXcI6m4@3(&i=s;uar1t>S>ap7gOl5$Z_L1gK zz%d-wkiPzvCuQ-7w77@`JaK~Q+R{D$bjXNnYt#H9czn`SNbp?KanwcJ+uQ5ZT+c4> zIx?p~yOjq$vstAfpkLeHEFgr;;!LrBTaIZaH$p`K&;B!12u+>AW%XXVZeoIg7y?3m zE`43e1;jD)OoH$aEGoB1Q`>}dbv+S1Xz)!C1&7_mQwLq~bl zhX<$dE#IJ>hl56M>nU}dNBVq09|cRPQz{8uk}`c4o@5FP8YD?7wYw2@I&6mcvsm2s zXriXVlPl<^yd3bb1`La78CpU z`2D6Uzb<*p`1EaN0d!>RG=V{x6OORN96pK5d3nmuwY>|E3DjlRLStR(+)mvLVGQlnP&ATxCe zJ7r?kvaSAVE93`1?5Q(Tf4ONM3OTX+qrpm0y`{v*`Vn|Zc>8za$v(duS~19m@Kzi&Kqr*)M7}AA`%gj`V6K&R$z#LfXFe7 z$upS&l@Y)c0SssGr2x=&mJ%je^u?L)6txvU=u#?YGn7B&tea27sZXb!)0zRo_DH_( z7D4I*u*-SHR+O;I-kia*QxVSb^rvo-%=g#ca&B*7oVKq67g&!4=3>t4*kna4JYt%1 z?rvdj7r{-$&(OBvmSk&j-SW8v8VAaip*dpcaYd0xT$CA=?d(D-JYtz#EI7OYk{{hX z=7k_4@RzfBCP2WnOL`nj&@0PTC6=Wnf_QmpM;8v%gtoGETl(Vmc2mrcWUS*rZAZp;@|MYnvdhS7Elz16m(adfW7GGt3aP{O`Ng-+ z>R5=DU#Z>6@`aVT@%TU{3IAA<%(A3gC=?7b(JZcSgUwlGk7jmN-Ul!FAbOZ{3I;we zq1Z@*9?n?HXLi>&k#(`9Ht5!HI{0G?#gJQ4_|lBcHIwzH0Tf|_-_A>o?!UtkvOMr` zt+0kpa+B>cc4maLQw{jg0yC%1JL+ndW&7>zHZF%9M5vcMW&b7{TRHI7907#HdeE^3 zzb?voKhPyPSF!^zzAJuAZzd)(0Yh=T4~C0KfG?BLw$Xh2!+(_&(3O)WmlK!C6Xr7E zMFXyhU3^+e@jbzBCOk)8&J#wGWr0)GHJ^^cIbwnTcc1sU9gWetD(g{p!z!VgBlnl z!l6+hB;517nz#lqPnoyvAqqXOguJ_w{Zs9pR}v+bsqEKy(X}d~=Saly z0tzBv39fw>LzG7Wr(RYFMTGS;nc?`BNPixJ2TKquOMizGt|NN=<4}<}Giu-FCW|uM zOdcPO<3a#8Y~$dkNKqIG9{`4l!tfpJCQe1p2((gia<_;;{Fbp(t=X#(Ts*I}T?NuY z0XPM5ViG%@Prq&UD7`ozT`IGH0GBL}3*U3#r$N zK?#1W7M={qm7W=VWQoIZinxR%k;{x993dq5&jXU#%j)kyv1qtM8G?sKnvc_h8{6~6 zL^JxqXPe*vA*}tHY|jQ>ZRrrEYWsJBisCX>Hr%$af-gP0{yaYigY3_DUP>h`ewC3T zT~a6~nz3dN_udD$vVmM{WG=g#dm;m0Ir!ky6!zKOdrCBP&*5=QRxZvG=-?@|&fd*n za3In3axr!FJ=Jq49W0MMccp*1t2Yuz&lY|w#j|XC7*de;)NKH_aeGQ+dv0WdKP{1T zAuO9gBbli##iurG0HcAWAwtM__4)eHQV5jyQj6)!2cXU0QHGFg zDFqe_3Bf`_{e9Pr7o-4TD^EtOK5zNS?NT8dgwaDgmy?hH9CXxrr;}tYo+a)b#P8ss zB3Cz}65joi93q83K9YP|jf*mf4#0N85in3bkdG@!*uV=d16ww5+y#6;CAm`8Ux@9x z(F}jLLUbjP!UX6+DJ~z;*f58%7XR<_8N3ooy7`8@-bOD#3S^hyFEKFltKN-R#Fc%% zy@FhFeTdHilFPSS%A$Cjrp6!VTXhd8vkr%g;p{nxk+788YWcTzP)6W%EU`vAN&xUw zimsk|m^Q|4xC7BMuJSN~oxCE>QRO z3eqWkf6#O{1|&RUo=ay5bmk2Iw!;zk}cOScp=M$SBK)_ zE?OYJ{DYtAQvg|8(#KNKJS1_>RlISM}K<>m~q+>qzsgT?}P>wss$V% zKzi#*D6H_DtVXZow}O>86HmD3>6xmYv>VpqDsiu*Via@`x8Vq~fBm&9JZ@FUiEBg` zy902NRI~aYy^0D6KyPF|yHUXcUv0ssk9YG~_x{ZRKa4B0n1C~vkmi_i!FSEy#ftEL z%*p$B3V|mTv1^GM-1$KqA_u)65kDGxVHA9^OWN^#diZ1-K?O|v8yw~moOZPx&bz5HZ@#~4ox>c)?pyQZuuBQ7S)#c@%_ z5SP-NdN}@3I;pHVZWuSzI--UEbyLUJ9CZO2aZIq@pmoh(f?|n=5X7#3s z$E)O^gT!L-VOhS2aBJt-(~SY!SiM@@%N6wU6He_Jv1gR;;KVp(8{yEGqdtZ&Np9kn zT8!i%54#e2-F@4vtUZLJWk8_?&ruFWRM&OX#NrQzTP zJ_RFw58&&fq)Z_`s+qIRpKcd($swosff~nK~7y{Zqw$Rj7qa>`R|4o z=NvVkEM(3;X$~Qs)~Eck(W~*te^GBtu9&e|Z~$-)`gm{U%~8B*sV2T}!Y0lf`0(}C zD85ng5kYD3_t}c0zCu7Bl@rhh_*ClWtq;GINXsn4G%$cJ2Ms>KejKKLGl4( z;e)lG=T`y5iG^cJ_k)ke$ydpnKlOV>|Eq7=Z#;a|%qGz|J@L8C_RmoV^4yH_ynS$MoA*ka6yYtt?A>|(jSA*P<>y{Y4G3oF7u8U=&EEiZ@mXLk<@9C9UPM?T zCz=3D?e^zxmD~}Eh>YQvT8j7-Vu*+fI5# zjP5Z40y(3atfrMl)KK}$oI@Iw(g#{=pl*LbX$L`9dw%n}5{?IQ{5^qbmDx z9wWf@_--{)yzLuXMU59fe}Vx+=Tmz0v6pFH0`;$F8|%cU@xY00!mDq3d9ZC^{08kA ztbj*YY;MO5?KZ&%t2$l0rJZ)(IuvjHZ4~*7q`eL%M-+eX+K=LDgYV$Al#%d~g#n|j z537!r_iV~5(npqjtyEE?#O}Fv#+-Q9fg$>KED4Ufk8f#?7gi?(8nzWKP>+vn#)><) z{9%d6&g=eJtfgx)9M~Z!IXkR7bfyx(|a7r^V>x{GO_+qGvN%@baEt9N6)(Dj$6 zPNi}2t{00{(u5(u*Ow?j>KvnGh98LYRqK46pBF8ugo4rP?>d|UfoX7KPirFY%8MTW zX{L%wMk+k@!_yi?yzn@bPj`w!Ma!(1VhP2ox=o~MTdm@!rOZZ$1g3is72F2hSU`ts z@m9S;3C7=G_U#KFvOa4u2q|p?;&!q~mQ%w21Wx{DIRXF2ZX^%54sQ6*Zsh-tGrl#N zt|}H*GK&7bC>Gq{6!<{%v|Y8wud1jNgT8x+GXOv`G}Lusa+?xhDg!3^*9M$vzF7xR zMO*NW6LG#rT5liIb7&`sF0gw3oxram!9fi!Ri3^z3Y+ZWu2vmhqGM&-!J4AM~rxh|M zlTR9(6k_%YhST%<{_!p#skpF#trbUP0Eyl)LL%Tc2yeo#{o(=o=hYsgDUob@Z-Tm* z^R)Oj%8^jsB#eR@Yy43Ojxh$v`dcKp!2m=7o-dy(RA+DpJ#^H~nT-fpqlHb>wO>4k zHLH~~l?ks4H)Y_@GhSMXJ%Z;>rDpFLgJku0Q31QF&sBJ;Rhn`@qEd2&rw%*o`&gDj zIrsT~iKzrAJ=ND-OiE1T`5DxNwDIQXm&-6pKnG`^p6y}I5NXTuy6Ys*IO)qIw>V#z zn$Cp=;2MtvPqRco%2ExF`WCOHJCpjVRlEi5*U23p?b9{N`cyzW>!Hb`7^YR?jY3*u zVZ%QGqCQ%OZ~YR;NnJ=RXj`4&+0wug4^#=`+X%!4Q@KZbJyqc;XzKswlm*@N&2(m7 zhZr-*)s5?tBYGzS;4pQjchBxZt_LBc_k`TcF-`1sLR=d05C&u?=FX-V&^&N5mmD8| z{qh{`r;t#w{92WeD^WrRMm=;Ck%D*dT-h(%%erMv-bBl^glHtU0ri9|v~FHS6yvaW zDQsFBkE~v;vW^>ZyABCF;u49`Y0|b>O5C%SrY*DaBiUjhLfV$G6ny zh^@x@SD-AF(u2P}3Au>jo&^-L9;L)j5D&XwHYmvGbpe6XNt8S@{^%~K_-D9=X zGV(9qF*uAj-%T2%+qwg$d4!(G#nx-_lS=L043V^OzBZ4L8a0sYDY>w0Dlo6|;Xx}i zq0o?{0ST^Yl7wM8&&Y69r0y1w zWafHJ%R;2kH(U$W3JZ+X!CA6suus*OKFr?5A+`MWAT2KiLNCf{=GKD1I$s@XK0x-F zF}2C0EQ$JgXRpf`fbSWGb%HQs4SyC?%PF9R%k~v>ybL*EMQUj%O=Q!r93}PhZ05s; zEFMUc382FwYPeXa_n^5Z`|KIFNJ&}vaj*Rb;YSv1s^|;q;qv~iMPaWJ!Ztf89FvR7 z(Heu@idp-yp3jY1LJzGOud4ii$)2wE9*Vkbg0VMBM&V=hd*9D2l71#CMqwJBx!Bxo z5}1=#qLhUe5{bk0V}J)PkE}B25QW5y-C9trTW@VaqIPBuq*flkHzklAJMfAYdVWBA z@5p7BYqKHg8Do?Cj(T>&;m8Ti7;!^)wTE(=&ztATRpDJ7=*hGv1BRijs>N?*M9(Nt zPsYM2Ss)n1TH--sV$`UQCW56IiR%|2sKvLo(!5xPp>?!}%W`pbSgYM)&Mnh&xZGRu zWSU>#qdRrqDppmLha6)UxiG(+`txI%l0FA>E(gFw)MoKLNf?sSNBxl_A>d&FqVv7m zGYZhT$|)p7;Q%R@Q&|-X@e_+h-S^_xWid>NixZ-vzuzV=v_*W=;^nf z?-%5S<>`;_G>G>wn*P-p2%hTbYeZ^rD`Nc(l-T*?%u zMsACDb+$e^zpv|cg7UzlwmqE{fJuu4@dXA*C@UlJJf<)dJije)7H{%ztsDbuc@Qee z;2bRghHqu7OR#qtYW2XAB8llTF9jG_Gddl-xhGoxQP{E{L|1JO=geV>3ljZg=2q9m zxnY{`{as;iRO^D5eM{bl=xaf~-F{oNX#ofJ1; zYlcNr=V-SVouKBO*kXS5OQ!Zp4=AZN>EPa}k9#h?fV8y-m6f=>&UR(8)k~uC`?a4n zkD^wu3Ny)a<9$t6il_(iP?5@gb|nslJ}c##7Ujq6bTe>_`1A8(CmS24_9JlZgX=Y- zuR;3QbmHz`Dd`u3dk&o^M6fYNFOD%T5xuMB@b66pZ<)O@c0r!kLU>Qb{q3w)nzz_d zW&rvuGWqvY?e|-h>~~~1Jnzoe`roF9xxB-x{{14DOVFiNfKv;7l|9ujm?om&-MOwO zVtg*V_X#Z2)$~*hX;7mk&n*Rz&TEw)gW^=vYX(^pGw0X;IHvJ=B5Rk=gxcdM#@j#I zYXg*Z^d%(^ep(un$;@l~;va$b3`haP^W*6pD87w6kFp>hT zLJ7m3a~b$$)em`*s%Ilu(9Ho?(nHmw3S4n3w2+`8RkId>`1N+Z*ykr5 zrpZCGD#Q=~!&s!)_T@9qYcebF?&7SL)&HXGy}z1R1Ge2~W-`fy8cOJ5KgQ6gy1_VTOYgFtl*!zyX?#-F~uJx_;p0m#R;rIvW zQbT5*`?>Dx<|HBlNJ&aQP8t1Pq~%N_TqBSPjUug5B3-JM_K}n3uZl{L z-$IJso!D22upf>nU%<3*pv$v7A6WFVmjVmPE10FkX~dfkMe`}AzN>{YN%BlvAHKaR zD|U#BB=Smk&p`zH`U|%%?psGVK>TRA4IIefrQN0AXj;-J!p`4t(*WhR| zNnJu3K~PzdIbkw1V3ltsYow$;l`7R15D-^=WS}Y)=6Xtbx*^v`!8*VPm6QNEF*o})l{=8;rfFj-8P{GQ} zGK?TXO8N#2p)pg1yS6hnkm47B(M8e*{+=JVpmzsSh&kidUPO+m6Typ(zYeEz;6Ps8 zKS-r-H?-p*ro!0v_j9sg7Us<$QC_)`ZvaUc=EVSKo43ZDj*BxWEFwN*ywz_>C}jz^ zn2Fr$GRb422Y26$$LF~ndxX}^)MxJZboes4PvjIL*6zYkK{J(uNRt)afv`4QnE@&PBDH_h&=JAGATpXqs))%o6X*T<2|_Bnhy2O%Y-}-Jd$be9^q_!o zi%Xw02%mh!G<+>bix@NxHzr2B`@}i0kV-On-5V_ckc6}b2dlFQ9VJ{>q2(!9S&T!9 zq~4TgWD8;2;qd*n*Fh4F0IQnyY{WvaueO8Y$HumBQ;OdpWyV;gS42E5qX>XpzEcW! zZ&Xf~Jd^Z69X7xleuabM#)q19dS{RhlOVF8u&o%tFYDse>frht3$>B7BYQe1!j=Ql z-0m+Hu1`H}HXp^262(n3BkYZAGFOQx}8EqPu%`u%KXr zA@->b!X#ExwLF>KT+2g3X8Tthh8*8o1d9*e!NY1Z!gmKlZa5f>96c2|iU>*C{4gKp zs2eYm_ce@&R)>C%R%9U4_8=B)7J7%Bi5#s`akyp%&V7k5K7CWw@MII`-mqKZGHQOWNBxdJE6DD z%jbV1zM*F`Pa>=|Vj*^@q;tWxUI^>sYnB;fOC+a{>6pLwl2w;1zw%M#N@@^cp4*_4 zR5c&$uNTa2UF2mtRcNL1MQ>`Io|UF1Rg1`ZNfX3yMxg)>@j|Ptspp*RBAhWhUY_{_ zN>8CfRFZl;Z9h*4b^jg)xkFifW}kk^(D+A7+%w&JsB{`6uNu+gjg{v$DN9kqmacae zj_lwLssE}P3&c{42S1NyaEZHQ7KW>9R_>A2ZuRq!32qx#3l#@%s-4`^lOK~Buyb!+D$h zebiM2m+SC}CVxt{aN0~p&z>=I7Z0Pe#!lrSnmlh!F5*__ZDZy$Gh8V)pQSY$h z$)thNLUXgON0{FUw<~L?~I_bq|auK zBArm^rhn*LJ2+IglJeS+&1R|*%S#gVZfBg~aBmuNJ8q`^bxZZut+b9?*+=Nfi%L`( z3zThdt|{1EYY;T--UcyQm>2VoN@mCn9n!s#!@Gr$DKpTE3Asv&{6IiH(Y{F>NJ~3g zq)ep}?aQu-Vj(GxQ=qH{{TlFhVMf`Ck$iDOa>VeV#F|Ifnw5#BMU{;uJJ^tjCqQ{)GQZ}5I!yRpo zKG+%_lFVCYn!fJ`KBGN{>I#MkxhbJ(e0|b7cj)Cw!G30ydS^58N0zjy6M<-#<9Wo} z&f(v9@ih(DQ-D&~q~%1vyn`q@BGIf0r%`xI%IiKR2xt0oLLHnW(WnSb=241=dyN1vq|ede>mY!er+WAE`-VagLiV!uJL(|-O?DVtfGL+BgTFN z@HTVW$2xdxYT5E-Q1|esv&20T2YwZ&!%0vXs%}TcfX4VltSCYp;tdt?WT+$zs1H>) zTCf2{+`oE3+J^=d1ic}(aMn!_`r?%)an9kO{*H7D;gvu0z(#yFdW{T&57StRME@!w zMUPIT{R%*jJ#(A(zvh8Yf&pS27$Oo;67G;i6aapbP!iY)Oj`gC>>>VaKv}^5e?_Gw zT67h4r4=W=m#IM>qg&R?ofD2v$gK}lQ(Ja%nT?HREbHjC>;FvySJLfPY?ZTJo$n%x z>KiD&uvg9Sw0IXBA0#2x1A^k?Re$`n2#!WK_^k)!G=G<}hUEV#Y_P9k3bL6|ckA7E zpUE=g7vE8P;kZeAyyhNom0o4`ZK7e9pI6=344`8zvnaJ{{2Xnr;@q zFydke`yM758dYBv5amd(m>m(6(Sbg&TJd~FBu(ScgMoM9(E4Q&V4Y+ zb{D#9F_;sY8kmkyBOa$HDrXRe63Saw!;WZrIk!-oZFqA+U5Z5vlKt81G9}d*#1|s= zGT)lZja(#?#mwnDBC@QocaCn)5>jPUTf0lBdvL+Hs%$s^Lgl=_PQd~>_~NT)^+$Zf zlhh!?>;Q^qxrmB7527X1pD`9fMcZ_CX`+5X!W4;p>UDylPdUd)QQd+*LB-NPxzV?j zaNVe_V!?#mk+v@lB;DelFFLEe>V(SrGV>aNW*9NpV!{8ZPy?k;jFUCeuff?mQA{#_ zSI;41T~}=RWLE5UmlC9%#i&iQ$Olr8p;k`3w4xSo{Lo}iB~D%xbWmA2ubX=v-I^WX zqUteHn`}sB@|>g%OZXS4xZXDGo%27j$V=9UalrVPUn8hChDLFI> zf9C$kiofJE$z5#us5 z2ZHbH>GANthN4A$TAI4k+*F0sxAsAbI-^06u)=hvqoZ51%RgNUNtF};%jLn-ROENe zB=u;t_`HEl;sc5@$_uGP<@yzldlEtFguF-ZUpGk7QEcVFAgtgUL#>;?QJcbP+ty;x znkYp^4fEo)74sbn@0G77x~Yjv)o!6qGTvBEQl{WMdA|?qYoq-uUM0F^;kSaaswF@0 zN#E#k7o@KArsE=$Y+nq?PsuH$?A^LLWN~{hOhOn-ZcfO7?vy`9rkDBSa)rxoj%&!x ztj1_2#g|Wh*_X6><6<3*1DOzT4QU~Oy>#B?>V@9N6%;8gHt6Uy2sY9+Z4ZQ`WJFc}|XO#w(xx)z=G&lN-g&$Wz@CR36-Ntk5m8 zMBx;V(w)vH=w3%SbOw`FgMi}1Zr z$Gb*v`ekbF3-j00k$V?qUj?zcS-}j4=iZH8xLAd`T4|nuG|thK-xEU1F4c zyeK2?d+k(Mz`GwplnJ`XMhC`ax*5pEQYVe;JPi>`r;Ug5u=k{l!D{fXypX~d*EPF1 zlF|r9l*w8kIbeG75`chwal>N1P&FG+G(=+AUKt^&P*4{CdYsgh&yvd)&suXh@Q;hS zT?BmKO8?wE|OnMpmc9Thulw)L=O$z zwWCx|;WFpLfJnL35y;LYA;khfu_=HU@qlb4K`air%Q}c&!TmJ3T_ax(421)NGR2WF z9oVeoptnD(w7o6{^<&R%3?sA|GeG~*Yba4G($6cxzDxs%U7(oGgH*5^#>)Kj_I>QyGjBd0`)MiT z6Cn!C23GqeB4=%t$Wogm>dF_n7p#tW-JOm2cD(to&0#NoDpo~_nTy!5TXe-hjV+d4 z0ualrx+3k3P5aCY!KO!yQ@8JZJ2m{*k(B2iD%PxdZB;dR4T*md5s8syVG|JjNwIzT zv(uaQ9!k#}Z8{RN(jlVcP|$B~foW?3V)5!p$_4L7pIWSI+~!RkaFe}s8$vP9V#$PP z>glA9YyD^h?%ysw-u2+N*4!Hpd!^6osqZ+#ykXdVkO9nF^(l(>#}njk%!}-50_uo%eYBEM+t~eC6#L;5DjEy42?+dcXch)-FWExX9=>(w z&h(b!zpJ}$D8Li*p3sZFBNa%L{ry*d*12DKZyhglC_BeBPp@!1^Q`apqaW*LkKg2! zW3L=T{@M3kPf>*Hy!1Oe=;r493 zAV$GO^Oh6l1ruGn&J>wHxNms#p>d#&_DV;t5ITC(NmgdO0N``Lmew>QTTrr+Z4?jwGYuu5>x6r$(;;a^KMIroCOx^UZbVfzEYgBke{s~} zK`)k%&Y`j>B`_}Mb%5O$KH|60-hl~-vLawtKWBlP$ojk(kWp_UM z)W(3-z^Jaiq?DZQh{vSfRXx=?Dy@dov~3)xi8s9p`8M&lTU(9?@QPZTV?mf9i!Pq| zt-HqyDDzeTcEN8U`aZAsMchfX@=hrQxQ7Arnj|iaWal$7$%(h!GnL$M zvOt#;(8Pmm`4rL*ydi$U*1TCUg-4NRrtD=B)&QqWSYH3*#^wCnUAPf#DoO8x@7wFZ zpN}nbLC*3JTQSIHA@Nxll}obVrrLOfhjPg%PmvHyQ}99`04zbk76}A#w6gTDwG2|? zgm0(_rFRuquvRJDQVz0q9Q+0*+%f0tz<`ZX8Z*DMDTEP4mWletVt}@8Lv#2xWj`aD zSs4c+NXx2h_FJYVv7IR`CaJM22w$}y4<;u%Mc@#zQ%hH^dIOi_@z5u+*b0VnN}!U0 zjeFV<;>Rf{;e(jwieVIu#?dk+^x=fr4h*LI4uQssjPWDA-jhA@7~2K1PSHCSkE$k|LYj@-m2@3I@y2GggjkI%}T3qNs4z zocg+?-w-pEEPqj68q9}|T4C-y%4p(lzp0c`F^mc*;$h`b9>tJjQA{Hx3u?C$4^f}g5g2H}OytHMN;oG#nT;lH41V$>$DUam+p$2|AgU_jpgYx7a(PImE!I%qs6}dU z66J>KCQ(+BQUaY9iEE3Qz=_jTW~8#du$rJB^;z}vb785%MceLDQu&a`5(>W6+?2C- zhXDH8TVWy2Iq`I_!pZ_!4p3%6mc&eO06DL8w+9;o?Bv5nDyl4m2yA(49 zcvOl(9N08TF@pw)NDC=4Z!B|GU>3Gr-vfz|W@6g(7j(G(3o{5$`cdB?K(>MD*p%>$$28YGd-mO=y#5u(2am+ zJALMfwA^W_9dgDqJe~%*Im0XVD&}$Pc>*YnsxAwB`+NI5QlYhnc#ChFDN1oZdcuG<}%8|lBWl&p;}CbGSYUPm4oAK@?- z$d4<58youTP0K-o7-jaSrvDe1U_4mL2{mLCRday&blHO>B91QE zX&wxe_o3E8DVvs}V75aM4IuU^$^2!teE$6nX5;#p8u!G%BS!X|)2J2?X9%3$M{^&7=+O{hSUK;| zWi6$|I2N1*_XCmwZXOplSS)D^Gu~gv+xtcr@g-6uTEX#=%_TxgyRAus5Rztgt@g%@ zMQFkcgStahbrJfnM@Zr&h1)h`lcVcnG{>UeD#l8WO-qj@9_DDKj&*Nv>=%*@X_mgv z4Q{<0G1ej2V5%$E&#DhRM&@etg>i5rb8TdDktXw6`YCygBNm&b%1619!beje$ zO>TNpZ+u!7&lq_zns)c#*&^XtLx1uEPwJ$VN-&|=G0I;ul_PaI$cHXDvN4Fn(wb!T z!`C;FF53ZZ(UIBQNd%A((RmK%s7L=~K=mYtab&p_g0DdY$ z(Z9rl^+Xr0v^m>t%NzTFpeIEpMQ8hL+dc|Cv?m|csQq~)fVok-!-WknsPwgPWx z=?JF6Cu!n{D^5HVQ4I-LWU?@B#DQbgfw!xbzF@SR#f=W4WV3P&Bae&!w#Z#-u;u4MzuLTf6XIe{&H>8@?;lQ5H_@Ghf^x%do z?#23tNG3^cOz3h1iAJ02BSEVkUN@$pt~B8F55#PnTg=vSYMtArHmSzE>GS@^sx3Et zj=+T4F4An$-{hp9Gzv5RssWH&mLl!8QIQZ1G%M3&%@{qTYMI$}*->>x0=j;3o+OBj3dh}O#17DZ>tP`9saUwGfZb9fgM)NL^|;veqo0-k)vxgO}- zCQOOfXLyY$c-|ddB^$;@^@j@VsxT!9xY(xX$q`H=q_?w0jj<1i`w6$%dk*Y}SS^re z+=)}#nZ61Wut^lr?hr*H(m#4ZsUuHCRaaC}u4c^D=>75f5jGEx)GG>xTBRhAp8)** z)Fh#U!Z@;;Nb=)zV?4RAlNd;GWe7s}9}j%jVKFrZwC2SOj4$GirNv7YK-qah=UbSj z3`g*?e-Mf`<{nj+iE$0>)L%y*Omf;I+*&p~d;bf}t$KEYc9x>;E`%R!vw{_w&j(`l z4JFUmDGz%>!wK8jh*`WV@40O@;i~LkZ!YUE0;$}69tt!1lFm{q;y(0Ds*9bp`1uw; zS z)+9VZ?^^@Kf(viP`HfeU)B%tu9ApA@)q$AxJ%F@2eXRr?NLjkCLp_2)mzF@AAH9)+ zh?{1JB9WCBBML%b%n1!XNL~&gKT*iO1_Usn06yvV-v6B#0^-;IyCR{M2n^T%e+Py| zj98@>CATH6(Bh8HPJS27b*-@*!fN_obEBu|*jk%qYJuzJ6r5zIiBxcQHagnKLUc`J zWPjblK00w$e|lQp)-pzOg{HU9>Hj0IFJgJ=D#+^FJFBY3#|H&P0LY2z$P+gfEqY)W z^@^->z6k9g&0R07Y*$uXYO`VhcdS+0BqlB!j}w6*FAQXhTz9vtu#cv{awgcyeKy

}N65OMcw4C{9pte$-k^!&1%W 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 literal 0 HcmV?d00001 diff --git a/src/index.html b/src/index.html index 30968b2d..ac4b67cf 100644 --- a/src/index.html +++ b/src/index.html @@ -54,6 +54,7 @@ +

From c3d5fa4b3e252329e292b7b7e0bcf98ce6b46558 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 8 Aug 2016 12:00:27 +0530 Subject: [PATCH 42/91] Enhancement #155 | Add without any information Allow to add service without any Customer information and/or Vehicle Information --- .../services/services-add.controller.js | 16 +++------------- .../services/services-edit.controller.js | 16 +++++++--------- src/app/components/services/services.factory.js | 2 ++ 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index e74a9174..fdeac6de 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -2038,28 +2038,18 @@ function validate() { if (vm.user.name == '') { - changeUserInfoState(true); - setTimeout(doFocus, 300); - utils.showSimpleToast('Please Enter Name'); - - function doFocus() { - $('#ami-user-name').focus(); - } - return false + vm.user.name = 'Anonymous'; } var isVehicleBlank = (vm.vehicle.manuf == undefined || vm.vehicle.manuf == '') && (vm.vehicle.model == undefined || vm.vehicle.model == '') && (vm.vehicle.reg == undefined || vm.vehicle.reg == ''); if (isVehicleBlank) { - changeVehicleInfoState(true); - utils.showSimpleToast('Please Enter At Least One Vehicle Detail'); - return false; + vm.vehicle.reg = 'Any'; } - return true; } // save to database function save(redirect) { - if (!validate()) return; + validate(); switch (vm.serviceType) { case vm.serviceTypeList[0]: if (checkBasic() == false) { diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 172b79e6..1227d6b8 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -489,6 +489,7 @@ var totalCost = 0; treatmentTotal = 0, inventoryTotal = 0; vm.service.problems.forEach(iterateProblem); + console.log(vm.problem); if (vm.problem.details != '') totalCost += (vm.problem.rate) ? parseFloat(vm.problem.rate) : 0; vm.selectedInventories.forEach(iterateInventories); @@ -810,13 +811,13 @@ }); vm.selectedInventories.push(vm.inventories[vm.inventories.length - 1]); } - calculate(); vm.inventory.name = ''; vm.inventory.amount = ''; vm.inventory.rate = ''; vm.inventory.tax = {}; vm.inventory.qty = 1; vm.inventory.total = ''; + calculate(); if (isFromAutocomplete || foundExisting.length != 0) vm.inventoryFocusIndex = (foundExisting.length == 0) ? vm.selectedInventories.length - 1 : vm.selectedInventories.indexOf(foundExisting[0]); else @@ -1981,11 +1982,11 @@ }); vm.selectedProblems.push(vm.service.problems[vm.service.problems.length - 1]); } - calculate(); vm.problem.details = ''; - vm.problem.amount = ''; - vm.problem.rate = ''; + vm.problem.amount = 0; + vm.problem.rate = 0; vm.problem.tax = {}; + calculate(); if (isFromAutocomplete || foundExisting.length != 0) vm.problemFocusIndex = (foundExisting.length == 0) ? vm.selectedProblems.length - 1 : vm.selectedProblems.indexOf(foundExisting[0]); else @@ -2042,16 +2043,13 @@ var isVehicleBlank = (vm.vehicle.manuf == undefined || vm.vehicle.manuf == '') && (vm.vehicle.model == undefined || vm.vehicle.model == '') && (vm.vehicle.reg == undefined || vm.vehicle.reg == ''); if (isVehicleBlank) { - changeVehicleInfoState(true); - utils.showSimpleToast('Please Enter At Least One Vehicle Detail'); - return false; + vm.vehicle.reg = 'Any'; } - return true; } // save to database function save(redirect) { - if (!validate()) return; + validate(); switch (vm.serviceType) { case vm.serviceTypeList[0]: if (checkBasic() == false) { diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 383347dc..59b43596 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -559,6 +559,8 @@ name += ' ' + splitname[i]; } name = utils.convertToTitleCase(name); + if (name == 'Anonymous') + return; customers.push({ id: row.id, name: name From 146bb108e70b29418ec6b4c95fca0e690fdabdea Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 8 Aug 2016 13:40:11 +0530 Subject: [PATCH 43/91] Enhancement #155 | Remove entries on lists 1. Remove name and vehicle information while displaying service list as well as customer list --- .../components/customers/customers.factory.js | 10 ++++++---- .../services/services-viewall.controller.js | 5 +++++ .../components/services/services_viewAll.html | 4 ++-- src/assets/img/loader.gif | Bin 98814 -> 94182 bytes 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/components/customers/customers.factory.js b/src/app/components/customers/customers.factory.js index c79209c6..abdd7660 100644 --- a/src/app/components/customers/customers.factory.js +++ b/src/app/components/customers/customers.factory.js @@ -272,9 +272,10 @@ } function iterateValues(value) { - var customer = {}, - vehicles = []; + var customer = {}, vehicles = []; if (value) { + if (value.name == 'Anonymous') + return; customer.id = value._id; customer.name = value.name; customer.mobile = value.mobile; @@ -305,8 +306,9 @@ res.rows.forEach(iterateRow); function iterateRow(row) { - var customer = {}, - vehicles = []; + if (row.doc.user.name == 'Anonymous') + return; + var customer = {}, vehicles = []; if (row.doc && row.doc.user) { customer.id = row.doc._id; customer.name = row.doc.user.name; diff --git a/src/app/components/services/services-viewall.controller.js b/src/app/components/services/services-viewall.controller.js index 8fc24548..764da66f 100644 --- a/src/app/components/services/services-viewall.controller.js +++ b/src/app/components/services/services-viewall.controller.js @@ -48,6 +48,7 @@ vm.IsServiceStateEs = IsServiceStateEs; vm.IsServiceStateIv = IsServiceStateIv; vm.openTimeFilter = openTimeFilter; + vm.IsCustomerAnonymous = IsCustomerAnonymous; // default execution steps $scope.$watch('vm.serviceQuery', watchServiceQuery); @@ -57,6 +58,10 @@ // function definitions + function IsCustomerAnonymous(name) { + return (name == 'Anonymous'); + } + function getCurrencySymbol() { amServices.getCurrencySymbol().then(success).catch(failure); diff --git a/src/app/components/services/services_viewAll.html b/src/app/components/services/services_viewAll.html index e7926439..58f35e1c 100644 --- a/src/app/components/services/services_viewAll.html +++ b/src/app/components/services/services_viewAll.html @@ -41,8 +41,8 @@ - {{service.cstmr_name}} - {{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}) + {{service.cstmr_name}} + {{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}) {{vm.getServiceDate(service.srvc_date)}} {{service.srvc_cost}} {{service.srvc_status}} diff --git a/src/assets/img/loader.gif b/src/assets/img/loader.gif index 6b19313b925140ed50772fd3bac4873a75a9839f..0adc29640c6ba932fd960b663a712adcb3a8de67 100644 GIT binary patch literal 94182 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 D`|Q#Q literal 98814 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

}N65OMcw4C{9pte$-k^!&1%W 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 From f83f95d4a41db73be33bf6bbf5a9477a16fdcc96 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 8 Aug 2016 15:34:03 +0530 Subject: [PATCH 44/91] Enhancement | Editable Customer Name --- src/app/app.states.js | 1 + .../customers/customers-edit.controller.js | 54 ++++++++++++++++++- .../components/customers/customers_edit.html | 34 ++++++++++-- .../services/services-add.controller.js | 2 - .../services/services-edit.controller.js | 23 +++++++- .../components/services/services.factory.js | 19 ++++++- src/app/components/services/services_add.html | 7 +-- 7 files changed, 127 insertions(+), 13 deletions(-) diff --git a/src/app/app.states.js b/src/app/app.states.js index d65d0d0e..bc0c4e0f 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -453,6 +453,7 @@ function loadCuUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'material-datatable', + 'assets/js/angular-elastic-input.min.js', 'app/components/customers/customers-edit.controller.js', 'app/components/customers/tmpl/vehicle-crud.controller.js' ]) diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index b95aea4f..3b5df880 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -21,7 +21,7 @@ // temporary named assignments var autofillVehicle = false; - var userDbInstance; + var userDbInstance, userMobile; var nextDueDate = new Date(); nextDueDate.setMonth(nextDueDate.getMonth() + 3); @@ -76,6 +76,8 @@ vm.getDate = getDate; vm.IsVehicleSelected = IsVehicleSelected; vm.editVehicle = editVehicle; + vm.convertNameToTitleCase = convertNameToTitleCase; + vm.checkExistingCustomerMobile = checkExistingCustomerMobile; // default execution steps // $state.params.id = ($state.params.id == undefined) ? "usr-anand-kacha-772d071e-852c-4a45-aaaf-089d80f73449" : $state.params.id; @@ -90,6 +92,42 @@ // function definitions + function checkExistingCustomerMobile(ev) { + vm.loadingBasedOnMobile = true; + amCustomers.getCustomerByMobile(vm.user.mobile).then(success).catch(failure); + + function success(res) { + vm.loadingBasedOnMobile = false; + var confirm = $mdDialog.confirm() + .title('Do you want to edit customer details ?') + .textContent('Customer record for ' + res.name + ' with ' + vm.user.mobile + ' already exists') + .ariaLabel('Edit Customer') + .targetEvent(ev) + .ok('Yes') + .cancel('No'); + + $mdDialog.show(confirm).then(doEdit, ignore); + + function doEdit() { + $state.go('restricted.customers.edit', { + id: res.id + }); + } + + function ignore() { + vm.user.mobile = userMobile; + } + } + + function failure(err) { + vm.loadingBasedOnMobile = false; + } + } + + function convertNameToTitleCase() { + vm.user.name = utils.convertToTitleCase(vm.user.name); + } + function getCurrencySymbol() { amCustomers.getCurrencySymbol().then(success).catch(failure); @@ -509,6 +547,7 @@ var pvl = []; vm.user.id = res._id; vm.user.mobile = res.user.mobile; + userMobile = res.user.mobile; vm.user.email = res.user.email; vm.user.name = res.user.name; vm.user.address = res.user.address; @@ -643,6 +682,15 @@ userDbInstance.user.email = vm.user.email; userDbInstance.user.address = vm.user.address; userDbInstance.user.type = vm.user.type; + + if (userDbInstance.user.name != vm.user.name) { + userDbInstance._deleted = true; + amCustomers.saveCustomer(userDbInstance).then(logResponse).catch(logResponse); + var prefixUser = 'usr-' + angular.lowercase(vm.user.name).replace(' ', '-'); + userDbInstance._id = utils.generateUUID(prefixUser); + delete userDbInstance._deleted; + delete userDbInstance._rev; + } if (vm.membershipChips != undefined) { var smArray = $.extend([], vm.membershipChips); @@ -685,6 +733,10 @@ delete membership.treatments[treatment.name]['$$hashKey']; } } + + function logResponse(res) { + console.info(res); + } } function successfullSave(res) { diff --git a/src/app/components/customers/customers_edit.html b/src/app/components/customers/customers_edit.html index 235805c0..e3875d5b 100644 --- a/src/app/components/customers/customers_edit.html +++ b/src/app/components/customers/customers_edit.html @@ -1,4 +1,29 @@ -

- +
@@ -193,6 +193,9 @@ Continue Continue to Add Service + + View Customer Profile + View {{vm.user.name}}'s Profile diff --git a/src/app/components/services/services_viewAll.html b/src/app/components/services/services_viewAll.html index 58f35e1c..b09bbb05 100644 --- a/src/app/components/services/services_viewAll.html +++ b/src/app/components/services/services_viewAll.html @@ -48,7 +48,7 @@ {{service.srvc_status}} {{service.srvc_state}} - + delete From c38d5effe8082c95d61cd53e3afd4cec4efe5efb Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 9 Aug 2016 18:01:17 +0530 Subject: [PATCH 56/91] UI Changes for Edit Customer --- .../customers/customers-add.controller.js | 2 ++ .../customers/customers-edit.controller.js | 2 ++ src/app/components/customers/customers_add.html | 10 +++++----- src/app/components/customers/customers_edit.html | 16 ++++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index b454f8fa..738c67e2 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -217,6 +217,8 @@ } function checkExistingCustomerMobile(ev) { + if (vm.user.mobile == '') + return; vm.loadingBasedOnMobile = true; amCustomers.getCustomerByMobile(vm.user.mobile).then(success).catch(failure); diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 8a1a7128..af16312e 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -92,6 +92,8 @@ // function definitions function checkExistingCustomerMobile(ev) { + if (vm.user.mobile == '') + return; vm.loadingBasedOnMobile = true; amCustomers.getCustomerByMobile(vm.user.mobile).then(success).catch(failure); diff --git a/src/app/components/customers/customers_add.html b/src/app/components/customers/customers_add.html index d2a11cdb..32db4bb9 100644 --- a/src/app/components/customers/customers_add.html +++ b/src/app/components/customers/customers_add.html @@ -138,11 +138,11 @@
-
+
- +
-
+
@@ -151,9 +151,9 @@
-
+
- + {{type}}
diff --git a/src/app/components/customers/customers_edit.html b/src/app/components/customers/customers_edit.html index bcd6fd28..d0bc6e85 100644 --- a/src/app/components/customers/customers_edit.html +++ b/src/app/components/customers/customers_edit.html @@ -160,11 +160,11 @@
-
+
- +
-
+
@@ -174,13 +174,13 @@
-
+
- + {{type}}
-
+
{{vm.services.length}}
@@ -202,11 +202,11 @@
* Click on Membership for details
-
+
{{vm.currencySymbol}} {{vm.paymentDone}}
-
+
{{vm.currencySymbol}} {{vm.paymentDue}}
From b3aea1330695d184a51023ff9c028fb3cb33cdd8 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Wed, 10 Aug 2016 10:11:03 +0530 Subject: [PATCH 57/91] Bug #202 #195 #201 202. It should not show (Any) in vehicle column in Service List page when vehicle is not specified in service. 195. It shows invoice on delete service button. 201. Total amount comes incorrectly in floating point, when tax is inclusive --- src/app/components/invoices/invoices-view.controller.js | 5 +++++ src/app/components/invoices/invoices_view.html | 4 ++-- src/app/components/services/services-add.controller.js | 3 ++- src/app/components/services/services-edit.controller.js | 2 +- src/app/components/services/services_viewAll.html | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 8eba4894..8dbeee26 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -62,6 +62,7 @@ vm.IsLastPage = IsLastPage; vm.currentPage = currentPage; vm.IsNotSinglePage = IsNotSinglePage; + vm.IsCustomerNotAnonymus = IsCustomerNotAnonymus; // default execution steps if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { @@ -81,6 +82,10 @@ // function definitions + function IsCustomerNotAnonymus() { + return (vm.user.name != 'Anonymous'); + } + function loadInvoiceFLogo() { var source = localStorage.getItem('invoice-f-pic'); vm.invoiceFLogo = source; diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index ac306cd0..eaf1da99 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -166,8 +166,8 @@

{{vm.workshop.name}}

diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 3371c574..21871bb1 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -1968,6 +1968,7 @@ } totalCost = (totalCost % 1 != 0) ? parseFloat(totalCost.toFixed(2)) : totalCost; totalCost = (totalCost % 1).toFixed(2) == 0.00 ? Math.round(totalCost) : totalCost; + totalCost = (totalCost % 1).toFixed(2) == 0.99 ? Math.round(totalCost) : totalCost; vm.service.cost = parseFloat(totalCost); function iterateTaxes(tax) { @@ -2070,7 +2071,7 @@ var isVehicleBlank = (vm.vehicle.manuf == undefined || vm.vehicle.manuf == '') && (vm.vehicle.model == undefined || vm.vehicle.model == '') && (vm.vehicle.reg == undefined || vm.vehicle.reg == ''); if (isVehicleBlank) { - vm.vehicle.reg = 'Any'; + vm.vehicle.reg = 'Vehicle'; } } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index b513e31f..42770e47 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -2095,7 +2095,7 @@ var isVehicleBlank = (vm.vehicle.manuf == undefined || vm.vehicle.manuf == '') && (vm.vehicle.model == undefined || vm.vehicle.model == '') && (vm.vehicle.reg == undefined || vm.vehicle.reg == ''); if (isVehicleBlank) { - vm.vehicle.reg = 'Any'; + vm.vehicle.reg = 'Vehicle'; } } diff --git a/src/app/components/services/services_viewAll.html b/src/app/components/services/services_viewAll.html index b09bbb05..58f35e1c 100644 --- a/src/app/components/services/services_viewAll.html +++ b/src/app/components/services/services_viewAll.html @@ -48,7 +48,7 @@ From 16ff7b7d8eb8e4d6bfa5a9134d152a2836a299a1 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Wed, 10 Aug 2016 10:19:37 +0530 Subject: [PATCH 58/91] Bug Fix #201 in Edit Services --- src/app/components/services/services-edit.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 42770e47..7e24c396 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -1996,6 +1996,7 @@ } totalCost = (totalCost % 1 != 0) ? parseFloat(totalCost.toFixed(2)) : totalCost; totalCost = (totalCost % 1).toFixed(2) == 0.00 ? Math.round(totalCost) : totalCost; + totalCost = (totalCost % 1).toFixed(2) == 0.99 ? Math.round(totalCost) : totalCost; vm.service.cost = parseFloat(totalCost); function iterateTaxes(tax) { From 704c916c0c879725664bd0a2beb3ae2b2c0e811e Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 11 Aug 2016 14:29:02 +0530 Subject: [PATCH 59/91] Architectural Change #204 & Feature #191 [init] 204. Merge pdbCustomers and pdbConfig into pdbMain 191. Cloud Sync Implementation [in progress] --- src/app/app.controller.js | 10 +- src/app/app.db.js | 170 +---- src/app/app.directive.js | 2 +- src/app/app.factory.js | 16 +- src/app/app.js | 10 +- src/app/app.service.js | 670 ++++++------------ src/app/app.states.js | 15 + .../customers/customers-add.controller.js | 2 +- .../customers/customers-viewall.controller.js | 4 +- .../components/customers/customers.factory.js | 64 +- .../dashboard/dashboard.controller.js | 5 +- .../components/dashboard/dashboard.factory.js | 37 +- .../components/inventory/inventory.factory.js | 85 +-- .../components/invoices/invoices.factory.js | 40 +- .../components/services/services.factory.js | 173 ++--- .../settings/settings-backup.factory.js | 37 +- .../settings/settings-importdata.service.js | 15 +- .../settings/settings-invoices.factory.js | 156 ++-- .../settings/settings-login.factory.js | 126 +--- .../settings/settings-tax.factory.js | 33 +- .../settings/settings.controller.js | 43 +- .../components/settings/settings.factory.js | 46 +- src/app/components/settings/settings.html | 27 - .../treatments/treatments.factory.js | 171 ++--- src/app/login/login.controller.js | 122 ++++ src/app/login/login.factory.js | 204 ++++++ src/app/login/login.html | 70 ++ src/app/temp/app.service.js | 577 +++++++++++++++ src/index.html | 7 +- 29 files changed, 1580 insertions(+), 1357 deletions(-) create mode 100644 src/app/login/login.controller.js create mode 100644 src/app/login/login.factory.js create mode 100644 src/app/login/login.html create mode 100644 src/app/temp/app.service.js diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 9dbfcc56..a4662c9a 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -78,10 +78,18 @@ vm.openLockScreen = openLockScreen; vm.openHelpWindow = openHelpWindow; vm.addService = addService; - $rootScope.setCoverPic(); + // default execution steps + setCoverPic(); amRootFactory.getPasscode().then(gps).catch(failure); + // function definitions + + function setCoverPic() { + var source = localStorage.getItem('cover-pic'); + $('#am-cover-pic').attr('src', (source) ? source : 'assets/img/logo-250x125px.png').width(250).height(125); + } + function addService() { $state.go('restricted.services.add'); } diff --git a/src/app/app.db.js b/src/app/app.db.js index d0ce95c3..ca543f6c 100644 --- a/src/app/app.db.js +++ b/src/app/app.db.js @@ -1,4 +1,4 @@ -/* +/** * Closure to create factories for handling databases * @author ndkcha * @since 0.4.1 @@ -8,105 +8,18 @@ /// (function() { - angular.module('automintApp') - .factory('pdbCustomers', PouchDbCustomers) - .factory('pdbConfig', PouchDbConfig) - .factory('pdbCommon', PouchDbCommon) - .factory('pdbCache', PouchDbCache); - - PouchDbCustomers.$inject = ['$q']; - PouchDbConfig.$inject = ['$q']; - PouchDbCommon.$inject = ['$q']; - PouchDbCache.$inject = ['$q']; - - function PouchDbCache($q) { - // named assignments - var database; - - // initialized factory and function mappings - var factory = { - setDatabase: setDatabase, - save: save, - get: get, - query: query, - getAll: getAll, - saveAll: saveAll - }; - - return factory; - - // function definitions - - // setup database - function setDatabase(name) { - database = new PouchDB(name, { - auto_compaction: true - }); - } - - // save a document in pouchDb database - function save(document) { - var tracker = $q.defer(); - if (document._id) - database.put(document).then(saveSuccessfull).catch(saveFailed); - else { - noIdError = { - ok: false, - message: 'No id provided' - }; - tracker.reject(noIdError); - } - - return tracker.promise; - - // respond to success response on saving database - function saveSuccessfull(response) { - tracker.resolve(response); - } - - // respond to failure response on saving database - function saveFailed(error) { - tracker.reject(error); - } - } - - // get a document from pouchdb database - function get(documentId, options) { - if (options) - return database.get(documentId, options); - else - return database.get(documentId); - } - - // query a document - // refs { mrf: map reduce function } - function query(mrf, options) { - if (options) - return database.query(mrf, options); - else - return database.query(mrf); - } + angular.module('automintApp').factory('pdbMain', PouchDBMain).factory('pdbCache', PouchDbCache).factory('pdbLocal', PouchDbLocal); - // fetch bulk documents from database - function getAll(options) { - if (options) - return database.allDocs(options); - else { - dgaOptions = { - include_docs: true - }; - return database.allDocs(dgaOptions); - } - } + /* + === NOTE === + Use PouchDB() as constructor of PouchDb databases as mentioned in pouchdb api + */ - // put bulk documents into database - function saveAll(documents) { - return database.bulkDocs(documents); - } - } + PouchDbLocal.$inject = ['$q']; + PouchDBMain.$inject = ['$q']; + PouchDbCache.$inject = ['$q']; - // factory function for customers' database - function PouchDbCustomers($q) { + function PouchDbLocal($q) { // named assignments // refs { dga: defaultGetAll } var database, syncOptions, noIdError, dgaOptions; @@ -129,7 +42,9 @@ // setup database function setDatabase(name) { - database = new PouchDB(name); + database = new PouchDB(name, { + auto_compaction: true + }); } // return change listeners of database @@ -215,8 +130,7 @@ } } - // factory function for workshop's config database - function PouchDbConfig($q) { + function PouchDBMain($q) { // named assignments // refs { dga: defaultGetAll } var database, syncOptions, noIdError, dgaOptions; @@ -229,7 +143,8 @@ save: save, get: get, query: query, - getAll: getAll + getAll: getAll, + saveAll: saveAll }; return factory; @@ -239,7 +154,7 @@ // setup database function setDatabase(name) { database = new PouchDB(name, { - auto_compaction: true + revs_limit: 10 }); } @@ -319,23 +234,25 @@ return database.allDocs(dgaOptions); } } - } - // factory function for common dataset - function PouchDbCommon($q) { + // put bulk documents into database + function saveAll(documents) { + return database.bulkDocs(documents); + } + } + + function PouchDbCache($q) { // named assignments - // refs { dga: defaultGetAll } - var database, replicaOptions, noIdError, dgaOptions; - + var database; + // initialized factory and function mappings var factory = { setDatabase: setDatabase, - OnDbChanged: OnDbChanged, - replicate : replicate, save: save, get: get, query: query, - getAll: getAll + getAll: getAll, + saveAll: saveAll }; return factory; @@ -344,29 +261,9 @@ // setup database function setDatabase(name) { - database = new PouchDB(name); - } - - // return change listeners of database - function OnDbChanged(options) { - if (!options) { - options = { - since: 'now', - live: true, - } - } - return database.changes(options); - } - - // setup replication from server - function replicate(remoteDb) { - if (!replicaOptions) { - replicaOptions = { - live: true, - retry: true - }; - } - return database.replicate.from(remoteDb, replicaOptions); + database = new PouchDB(name, { + auto_compaction: true + }); } // save a document in pouchDb database @@ -423,5 +320,10 @@ return database.allDocs(dgaOptions); } } + + // put bulk documents into database + function saveAll(documents) { + return database.bulkDocs(documents); + } } })(); \ No newline at end of file diff --git a/src/app/app.directive.js b/src/app/app.directive.js index b5d4c81d..0a2d88af 100644 --- a/src/app/app.directive.js +++ b/src/app/app.directive.js @@ -55,7 +55,7 @@ var checkTitle = (toState.data && toState.data.pageTitle); $rootScope.sidebarItemIndex = (toState.data && (toState.data.sidebarItemIndex != undefined)) ? toState.data.sidebarItemIndex : -1; $rootScope.module_title = checkTitle ? toState.data.pageTitle : ''; - $rootScope.page_title = checkTitle ? toState.data.pageTitle + ' - ' + default_title : default_title; + $rootScope.page_title = checkTitle ? (default_title + ' | ' + toState.data.pageTitle) : default_title; } } } diff --git a/src/app/app.factory.js b/src/app/app.factory.js index 6760344d..83aafd59 100644 --- a/src/app/app.factory.js +++ b/src/app/app.factory.js @@ -2,20 +2,18 @@ * Closure for root level factories * @author ndkcha * @since 0.4.1 - * @version 0.6.1 + * @version 0.7.0 */ /// (function() { - angular.module('automintApp') - .factory('amRootFactory', RootFactory) - .factory('utils', UtilsFactory); + angular.module('automintApp').factory('amRootFactory', RootFactory).factory('utils', UtilsFactory); - RootFactory.$inject = ['$q', '$amRoot', 'pdbConfig']; + RootFactory.$inject = ['$q', '$rootScope', 'pdbMain']; UtilsFactory.$inject = ['$mdToast']; - function RootFactory($q, $amRoot, pdbConfig) { + function RootFactory($q, $rootScope, pdbMain) { // initialize factory object and map functions var factory = { getPasscode: getPasscode @@ -27,13 +25,9 @@ function getPasscode() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - function getSettingsObj(res) { tracker.resolve(res.passcode); } diff --git a/src/app/app.js b/src/app/app.js index 4c539348..743868ed 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -2,7 +2,7 @@ * Automint Application * @author ndkcha * @since 0.4.1 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -50,16 +50,8 @@ $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; - $rootScope.setCoverPic = setCoverPic; - // initialize database and default syncing mechanism with automint server $amRoot.initDb(); - - // set cover photo - function setCoverPic() { - var source = localStorage.getItem('cover-pic'); - $('#am-cover-pic').attr('src', (source) ? source : 'assets/img/logo-250x125px.png').width(250).height(125); - } } // configure debug mode diff --git a/src/app/app.service.js b/src/app/app.service.js index 7a819cb2..51b7fa1e 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -1,5 +1,5 @@ -/* - * Closure for root level service +/** + * Root level service * @author ndkcha * @since 0.4.1 * @version 0.7.0 @@ -8,162 +8,264 @@ /// (function() { - angular.module('automintApp') - .service('$amRoot', AutomintService); - - AutomintService.$inject = ['$rootScope', '$state', '$q', '$log', 'utils', 'constants', 'pdbCustomers', 'pdbConfig', 'pdbCommon', 'amFactory', 'pdbCache']; - - function AutomintService($rootScope, $state, $q, $log, utils, constants, pdbCustomers, pdbConfig, pdbCommon, amFactory, pdbCache) { - // set up service object - var sVm = this; - var blockViews = true; - - // keep track of current configuration of application - sVm.docIds = {}; - sVm.username = ''; + angular.module('automintApp').service('$amRoot', AutomintService); + + AutomintService.$inject = ['$rootScope', '$state', '$q', 'utils', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory', 'amLogin']; + + function AutomintService($rootScope, $state, $q, utils, constants, pdbMain, pdbCache, pdbLocal, amFactory, amLogin) { + // set up service view model + var vm = this; + + // named assignments to rootScope + if (!$rootScope.amGlobals) + $rootScope.amGlobals = {}; + if ($rootScope.amGlobals.configDocIds == undefined) { + $rootScope.amGlobals.configDocIds = { + settings: 'settings', + treatment: 'treatments', + inventory: 'inventory', + workshop: 'workshop' + } + } + if ($rootScope.amGlobals.creator == undefined) + $rootScope.amGlobals.creator = ''; + if ($rootScope.amGlobals.channel == undefined) + $rootScope.amGlobals.channel = ''; + + // named assignments to keep track of current configuration of application service // map functions - sVm.initDb = initDb; - sVm.syncDb = syncDb; - sVm.ccViews = ccViews; - sVm.isWorkshopId = isWorkshopId; - sVm.isTreatmentId = isTreatmentId; - sVm.isSettingsId = isSettingsId; - sVm.isInventoryId = isInventoryId; - sVm.updateConfigReferences = updateConfigReferences; - - // named assignments - var successResponse = { - success: true - } - var failureResponse = { - success: false + $rootScope.amGlobals.IsConfigDoc = IsConfigDoc; + vm.initDb = initDb; + vm.dbAfterLogin = dbAfterLogin; + + // function definitions + + function IsConfigDoc(id) { + if (id.match(/\bsettings/i)) + return true; + if (id.match(/\btreatments/i)) + return true; + if (id.match(/\binventory/i)) + return true; + if (id.match(/\bworkshop/i)) + return true; + return false; } // initialize databases function initDb() { // setup local databases - pdbConfig.setDatabase(constants.pdb_w_config); - pdbCustomers.setDatabase(constants.pdb_w_customers); - pdbCommon.setDatabase(constants.pdb_common); + pdbMain.setDatabase(constants.pdb_main); pdbCache.setDatabase(constants.pdb_cache); + pdbLocal.setDatabase(constants.pdb_local); - // check and create views - manageDbVersions().then(ccViews).catch(ccViews); + // check for login + // pdbLocal.get(constants.pdb_local_docs.login).then(checkLoginState).catch(doLogin); + dbAfterLogin(); + } - // listen to changes in local db - OnCustomerDbChanged(); - OnConfigDbChanged(); + function dbAfterLogin() { + // handle database migration if any and generate cache docs after the process + // no such migrations right now + generateCacheDocs(); - // setup server iteraction - // oneWayReplication(); - // syncDb(); + // setup database change listeners + pdbMain.OnDbChanged({ + since: 'now', + live: true + }).on('change', OnChangeMainDb); } - function manageDbVersions() { - var tracker = $q.defer(); - isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get(sVm.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); + function checkLoginState(res) { + if (res && res.username && res.password) { + amLogin.loadCredentials(res.username, res.password); + amLogin.login(success, failure); + } else + doLogin(); + + function success(response) { + if (response.data && (response.ok == true)) { + if (response.data.userCtx && (response.data.userCtx.name != null) && response.data.userCtx.channels && (Object.keys(response.data.userCtx.channels).length > 0)) { + if (response.data.userCtx.channels.length > 1) { + amLogin.saveLoginCredentials(true, res.username, res.password, response.data.userCtx.channels[1]).then(proceed).catch(doLogin); + return; + } + } + } + utils.showSimpleToast('Please Login Again!'); + doLogin(); } - function getSettingsObject(res) { - if (!res.dbversion) - res.dbversion = 0; - switch (res.dbversion) { - case 0: - applyPatch1().then(proceed).catch(proceed); - break; + function failure(err) { + if (err.status == -1) { + if (res.isLoggedIn == true) { + proceed(); + return; + } } + utils.showSimpleToast('Please Login Again!'); + doLogin(); + } - function proceed(res) { - res.dbversion = constants.db_version; - pdbConfig.save(res).then(success).catch(failure); - } + function proceed() { + if (res.channel) + $rootScope.amGlobals.channel = res.channel; + if (res.username) + $rootScope.amGlobals.creator = res.username; + dbAfterLogin(); } + } - function writeSettingsObject(err) { - var doc = { - _id: utils.generateUUID('sttngs'), - creator: sVm.username, - dbversion: constants.db_version - } - applyPatch1().then(proceed).catch(proceed); + function doLogin(err) { + $state.go('login'); + } - function proceed(res) { - pdbConfig.save(doc).then(success).catch(failure); - } + function OnChangeMainDb(change) { + if (IsConfigDoc(change.id)) + return; + if (change.deleted == true) { + generateCacheDocs(force); + return; } - - function success(res) { - tracker.resolve(res); + var curdoc; + + pdbMain.get(change.id, { + revs_info: true + }).then(getCurrentVersion); + + function getCurrentVersion(cdoc) { + curdoc = cdoc; + if (cdoc._revs_info.length > 1) { + pdbMain.get(change.id, { + rev: cdoc._revs_info[1].rev + }).then(getLastVersion); + } else + getLastVersion(); } - function failure(err) { - tracker.reject(err); - } - } + function getLastVersion(ldoc) { + pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); + pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(vndvs).catch(vndvs); - function applyPatch1() { - var tracker = $q.defer(); - pdbCustomers.getAll().then(success).catch(failure); - return tracker.promise; + function vndvs(cachedoc) { + if (cachedoc.error == true) { + cachedoc = { + _id: constants.pdb_cache_views.view_services + } + } + if (curdoc.user.vehicles) + Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); + pdbCache.save(cachedoc); - function success(res) { - var customers = []; - res.rows.forEach(iterateRows); - pdbCustomers.saveAll(customers).then(saveSuccess).catch(failure); - - function iterateRows(row) { - if (row.doc && row.doc.user && row.doc.user.vehicles) { - Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); - customers.push(row.doc); + function iterateVehicles(vId) { + var vehicle = curdoc.user.vehicles[vId]; + var lastVehicle = undefined, lvcd = undefined; + if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId]) + lastVehicle = ldoc.user.vehicles[vId]; + if (lastVehicle && lastVehicle.nextdue) + lvcd = moment(lastVehicle.nextdue).format('MMM YYYY'); + if (!vehicle.nextdue) { + if (lastVehicle) { + if (lastVehicle.nextdue) { + if (lvcd && cachedoc[lvcd] && cachedoc[lvcd][vId]) + delete cachedoc[lvcd][vId]; + } + } + return; + } + var cd = moment(vehicle.nextdue).format('MMM YYYY'); + if (lastVehicle && lvcd) { + if ((cachedoc[lvcd] != undefined) && (cachedoc[lvcd][vId] != undefined)) + delete cachedoc[lvcd][vId]; + } + if (cachedoc[cd] == undefined) + cachedoc[cd] = {}; + cachedoc[cd][vId] = { + cstmr_id: change.id, + cstmr_name: curdoc.user.name, + cstmr_mobile: curdoc.user.mobile, + vhcl_reg: vehicle.reg, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_nextdue: vehicle.nextdue, + } } + } + + function vsuv(cachedoc) { + if (cachedoc.error == true) { + cachedoc = { + _id: constants.pdb_cache_views.view_services + } + } + + if (curdoc.user.vehicles) + Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); + pdbCache.save(cachedoc); function iterateVehicles(vId) { - if (row.doc.user.vehicles[vId].services) - Object.keys(row.doc.user.vehicles[vId].services).forEach(iterateServices); + var vehicle = curdoc.user.vehicles[vId]; + if (vehicle.services) + Object.keys(vehicle.services).forEach(iterateServices); function iterateServices(sId) { - if (row.doc.user.vehicles[vId].services[sId].status == 'billed') - row.doc.user.vehicles[vId].services[sId].status = 'due'; - if (row.doc.user.vehicles[vId].services[sId].state == undefined || row.doc.user.vehicles[vId].services[sId].state == '') - row.doc.user.vehicles[vId].services[sId].state = 'Bill'; + var service = vehicle.services[sId]; + var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); + var cd = moment(service.date).format('MMM YYYY'); + cd = angular.lowercase(cd).replace(' ', '-'); + if (service._deleted == true) { + if (cachedoc[cd] && cachedoc[cd][sId]) + delete cachedoc[cd][sId]; + return; + } + if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId] && ldoc.user.vehicles[vId].services && ldoc.user.vehicles[vId].services[sId]) { + var lvd = moment(ldoc.user.vehicles[vId].services[sId].date).format('MMM YYYY'); + lvd = angular.lowercase(lvd).replace(' ', '-'); + if (cachedoc[lvd][sId] != undefined) + delete cachedoc[lvd][sId]; + } + if (cachedoc[cd] == undefined) + cachedoc[cd] = {}; + cachedoc[cd][sId] = { + cstmr_id: change.id, + cstmr_name: curdoc.user.name, + cstmr_mobile: curdoc.user.mobile, + vhcl_id: vId, + vhcl_reg: vehicle.reg, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_nextdue: vehicle.nextdue, + srvc_date: service.date, + srvc_cost: service.cost, + srvc_status: service.status, + srvc_state: service.state, + srvc_payreceived: payreceived + }; } } } } - - function saveSuccess(res) { - tracker.resolve(res); - } - - function failure(err) { - tracker.reject(err); - } } - function ccViews(force) { - // service module + function generateCacheDocs(force) { + pdbMain.getAll().then(success).catch(failure); - pdbCustomers.getAll().then(success).catch(failure); - - // view service function success(res) { - pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); - pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(viewvehiclessuccess).catch(viewvehiclessuccess); + var serviceCacheCallback = (force == true) ? generateServicesCache : ignore; + var nextDueCacheCallback = (force == true) ? generateNextDueCache : ignore; + pdbCache.get(constants.pdb_cache_views.view_services).then(serviceCacheCallback).catch(generateServicesCache); + pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(nextDueCacheCallback).catch(generateNextDueCache); - function viewvehiclessuccess(vvcdoc) { + function generateNextDueCache(vvcdoc) { var vdocToSave = {}; res.rows.forEach(iterateRows); vdocToSave._id = constants.pdb_cache_views.view_next_due_vehicles; - if (vvcdoc._rev) - vdocToSave._rev = vvcdoc._rev; pdbCache.save(vdocToSave); function iterateRows(row) { + if (IsConfigDoc(row.id)) + return; if (row.doc.user.vehicles) Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); @@ -187,15 +289,15 @@ } } - function vsuv(cachedoc) { + function generateServicesCache(cachedoc) { var docsToSave = {}, isChanged = false; res.rows.forEach(iterateRows); docsToSave._id = constants.pdb_cache_views.view_services; - if (cachedoc._rev) - docsToSave._rev = cachedoc._rev; pdbCache.save(docsToSave); function iterateRows(row) { + if (row.id == $rootScope.amGlobals.configDocIds.settings) + return; if (row.doc.user.vehicles) Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); @@ -235,343 +337,15 @@ } } } - } - - function failure(err) { - console.warn(err); - } - } - - function OnCustomerDbChanged() { - pdbCustomers.OnDbChanged({ - since: 'now', - live: true - }).on('change', onChange).on('complete', onComplete).on('error', onError); - function onChange(change) { - if (change.deleted == true) { - ccViews(); - return; + function ignore(res) { + // do nothing } - var curdoc; - - pdbCustomers.get(change.id, { - revs_info: true - }).then(getCurrentVersion); - - function getCurrentVersion(cdoc) { - curdoc = cdoc; - if (cdoc._revs_info.length > 1) { - pdbCustomers.get(change.id, { - rev: cdoc._revs_info[1].rev - }).then(getLastVersion); - } else - getLastVersion(); - } - - function getLastVersion(ldoc) { - pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); - pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(vndvs).catch(vndvs); - - function vndvs(cachedoc) { - if (cachedoc.error == true) { - cachedoc = { - _id: constants.pdb_cache_views.view_services - } - } - if (curdoc.user.vehicles) - Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); - pdbCache.save(cachedoc); - - function iterateVehicles(vId) { - var vehicle = curdoc.user.vehicles[vId]; - var lastVehicle = undefined, lvcd = undefined; - if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId]) - lastVehicle = ldoc.user.vehicles[vId]; - if (lastVehicle && lastVehicle.nextdue) - lvcd = moment(lastVehicle.nextdue).format('MMM YYYY'); - if (!vehicle.nextdue) { - if (lastVehicle) { - if (lastVehicle.nextdue) { - if (lvcd && cachedoc[lvcd] && cachedoc[lvcd][vId]) - delete cachedoc[lvcd][vId]; - } - } - return; - } - var cd = moment(vehicle.nextdue).format('MMM YYYY'); - if (lastVehicle && lvcd) { - if ((cachedoc[lvcd] != undefined) && (cachedoc[lvcd][vId] != undefined)) - delete cachedoc[lvcd][vId]; - } - if (cachedoc[cd] == undefined) - cachedoc[cd] = {}; - cachedoc[cd][vId] = { - cstmr_id: change.id, - cstmr_name: curdoc.user.name, - cstmr_mobile: curdoc.user.mobile, - vhcl_reg: vehicle.reg, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_nextdue: vehicle.nextdue, - } - } - } - - function vsuv(cachedoc) { - if (cachedoc.error == true) { - cachedoc = { - _id: constants.pdb_cache_views.view_services - } - } - - if (curdoc.user.vehicles) - Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); - pdbCache.save(cachedoc); - - function iterateVehicles(vId) { - var vehicle = curdoc.user.vehicles[vId]; - if (vehicle.services) - Object.keys(vehicle.services).forEach(iterateServices); - - function iterateServices(sId) { - var service = vehicle.services[sId]; - var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); - var cd = moment(service.date).format('MMM YYYY'); - cd = angular.lowercase(cd).replace(' ', '-'); - if (service._deleted == true) { - if (cachedoc[cd] && cachedoc[cd][sId]) - delete cachedoc[cd][sId]; - return; - } - if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId] && ldoc.user.vehicles[vId].services && ldoc.user.vehicles[vId].services[sId]) { - var lvd = moment(ldoc.user.vehicles[vId].services[sId].date).format('MMM YYYY'); - lvd = angular.lowercase(lvd).replace(' ', '-'); - if (cachedoc[lvd][sId] != undefined) - delete cachedoc[lvd][sId]; - } - if (cachedoc[cd] == undefined) - cachedoc[cd] = {}; - cachedoc[cd][sId] = { - cstmr_id: change.id, - cstmr_name: curdoc.user.name, - cstmr_mobile: curdoc.user.mobile, - vhcl_id: vId, - vhcl_reg: vehicle.reg, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_nextdue: vehicle.nextdue, - srvc_date: service.date, - srvc_cost: service.cost, - srvc_status: service.status, - srvc_state: service.state, - srvc_payreceived: payreceived - }; - } - } - } - } - } - - function onComplete(info) { - // console.log(info); } - function onError(error) { - // console.log(error); - } - } - - function OnConfigDbChanged() { - pdbConfig.OnDbChanged({ - since: 'now', - live: true, - include_docs: true - }).on('change', onChange).on('complete', onComplete).on('error', onError); - - function onChange(change) { - // console.log(change); - } - - function onComplete(info) { - // console.log(info); - } - - function onError(error) { - // console.log(error); - } - } - - function updateConfigReferences() { - var tracker = $q.defer(); - pdbConfig.getAll().then(successQuery).catch(failedQuery); - return tracker.promise; - - // if promise returns with all documents, update configurations - function successQuery(res) { - res.rows.forEach(iterateDocuments); - tracker.resolve(successResponse); - - // iterate through documents to match id(s) of documents - function iterateDocuments(element) { - if (element.id.match(/\btrtmnt-/i)) - sVm.docIds.treatment = element.id; - if (element.id.match(/\bwrkshp-/i)) - sVm.docIds.workshop = element.id; - if (element.id.match(/\bsttngs-/i)) - sVm.docIds.settings = element.id; - if (element.id.match(/\binvntry-/i)) - sVm.docIds.inventory = element.id; - } - } - - // if promise returns with error - function failedQuery(err) { - tracker.reject(failureResponse); - } - } - - // check if database is syncable to remote - function isSyncable() { - var tracker = $q.defer(); - var workshopUser = $.extend({}, successResponse); - - isWorkshopId().then(setCredentials).catch(noWorkshopUser); - return tracker.promise; - - // if workshop document is tracker, look for username and password inside document - function setCredentials(res) { - pdbConfig.get(sVm.docIds.workshop).then(setWorkshopUser).catch(noWorkshopUser); - - // if workshop users existing, return with username and password - function setWorkshopUser(res) { - if (res.user) { - workshopUser.username = res.user.username; - workshopUser.password = res.user.password; - tracker.resolve(workshopUser); - } else - noWorkshopUser(); - } - } - - // if no workshop, return with failed response bool - function noWorkshopUser(error) { - tracker.reject(failureResponse); - } - } - - // setup sync (bidirectional replication) - function syncDb() { - isSyncable().then().catch(); - - // if sync details found - function runSync(res) { - if (res.success) { - sVm.username = res.username; - // construct database url for workshop configuration and customers' db - dbConfigUrl = amFactory.generateDbUrl(res.username, res.password, constants.sgw_w_config); - dbCustomersUrl = amFactory.generateDbUrl(res.username, res.password, constants.sgw_w_customers); - - // sync database - pdbConfig.sync(dbConfigUrl) - .on('change', onChangedDb) - .on('paused', onPausedDb) - .on('active', onActiveDb) - .on('denied', onDeniedDb) - .on('complete', onCompleteDb) - .on('error', onErrorDb); - - pdbCustomers.sync(dbCustomersUrl) - .on('change', onChangedDb) - .on('paused', onPausedDb) - .on('active', onActiveDb) - .on('denied', onDeniedDb) - .on('complete', onCompleteDb) - .on('error', onErrorDb); - } - } - - // no sync details found - function noSync(error) { - $log.debug('cannot sync at moment! no sync details found'); - } - } - - // setup one-way replication - function oneWayReplication() { - // setup common database replication - pdbCommon.replicate(constants.db_url + constants.sgw_common) - .on('change', onChangedDb) - .on('paused', onPausedDb) - .on('active', onActiveDb) - .on('denied', onDeniedDb) - .on('complete', onCompleteDb) - .on('error', onErrorDb); - } - - // check if treatment's document id is loaded to current docId object - function isTreatmentId() { - return isDocId('trtmnt'); - } - - // check if workshop's document id is loaded to current docId object - function isWorkshopId() { - return isDocId('wrkshp'); - } - - // check if settings' document id is loaded to current docId object - function isSettingsId() { - return isDocId('sttngs'); - } - - // check if inventory's document id is loaded to current docId object - function isInventoryId() { - return isDocId('invntry'); - } - - // the check function - function isDocId(query) { - var tracker = $q.defer(); - if (sVm.docIds[query] == '' || sVm.docIds[query] == undefined) - sVm.updateConfigReferences().then(configUpdated).catch(updateFailed); - else - updateFailed(); - return tracker.promise; - - function configUpdated(res) { - tracker.resolve(successResponse); - } - - function updateFailed(err) { - tracker.resolve(failureResponse); + function failure(err) { + console.warn(err); } } - - // database listeners - - function onChangedDb(info) { - // listen to on change event - } - - function onPausedDb(err) { - // listen to on pause event\ - } - - function onActiveDb() { - // listen to on active event - } - - function onDeniedDb(err) { - // listen to on denied event - } - - function onCompleteDb(info) { - // listen to on complete event - updateConfigReferences(); - } - - function onErrorDb(err) { - // listen to on error event - } } })(); \ No newline at end of file diff --git a/src/app/app.states.js b/src/app/app.states.js index b743a3f8..58fd98a0 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -30,6 +30,15 @@ fromState: undefined } }) + .state('login', { + url: '/login', + templateUrl: 'app/login/login.html', + controller: 'amLoginCtrl', + controllerAs: 'vm', + resolve: { + deps: ['$ocLazyLoad', loadLoginDeps] + } + }) .state('restricted', { abstract: true, url: '', @@ -400,6 +409,12 @@ } }); + function loadLoginDeps($ocLazyLoad) { + return $ocLazyLoad.load([ + 'app/login/login.controller.js' + ]) + } + function loadInUIDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'app/components/inventory/inventory-edit.controller.js' diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index 738c67e2..c0ab150a 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -204,7 +204,7 @@ $mdDialog.show(confirm).then(performDelete, ignoreDelete); function performDelete() { - console.info('deleted'); + console.info('membership deleted'); } function ignoreDelete() { diff --git a/src/app/components/customers/customers-viewall.controller.js b/src/app/components/customers/customers-viewall.controller.js index 5ea2947d..b33235e4 100644 --- a/src/app/components/customers/customers-viewall.controller.js +++ b/src/app/components/customers/customers-viewall.controller.js @@ -40,9 +40,6 @@ // default watchers $scope.$watch('vm.customerQuery', watchCustomerQuery); - // default execution steps - getCustomers(); - // function definitions function watchCustomerQuery(newValue, oldValue) { @@ -78,6 +75,7 @@ } function failure(error) { + console.log(error); vm.customers = []; vm.query.total = 0; } diff --git a/src/app/components/customers/customers.factory.js b/src/app/components/customers/customers.factory.js index abdd7660..7eda2b11 100644 --- a/src/app/components/customers/customers.factory.js +++ b/src/app/components/customers/customers.factory.js @@ -11,9 +11,9 @@ angular.module('automintApp') .factory('amCustomers', CustomersFactory); - CustomersFactory.$inject = ['$q', '$http', 'utils', 'pdbCommon', 'pdbCustomers', 'pdbConfig', '$amRoot']; + CustomersFactory.$inject = ['$rootScope', '$q', '$http', 'utils', 'pdbMain']; - function CustomersFactory($q, $http, utils, pdbCommon, pdbCustomers, pdbConfig, $amRoot) { + function CustomersFactory($rootScope, $q, $http, utils, pdbMain) { // initialize factory variable and map functions var factory = { getCustomers: getCustomers, @@ -36,13 +36,9 @@ function getCurrencySymbol() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.currency) tracker.resolve(res.currency); @@ -57,7 +53,7 @@ function getCustomerByMobile(mobile) { var tracker = $q.defer(); - pdbCustomers.query(mapView, { + pdbMain.query(mapView, { include_docs: true, key: mobile }).then(success).catch(failure); @@ -104,13 +100,9 @@ function getVehicleTypes() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.vehicletypes) tracker.resolve(res.vehicletypes); @@ -126,13 +118,9 @@ function getRegularTreatments() { var tracker = $q.defer(); var treatments = []; - $amRoot.isTreatmentId().then(getConfigDocument).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getConfigObject).catch(failure); return tracker.promise; - function getConfigDocument(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getConfigObject).catch(failure); - } - function getConfigObject(res) { if (res.regular) Object.keys(res.regular).forEach(iterateTreatments); @@ -159,13 +147,9 @@ memberships: [], total: 0 } - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.memberships) Object.keys(res.memberships).forEach(iterateMemberships); @@ -204,13 +188,13 @@ var tracker = $q.defer(); var customers = []; if (query == '' || query == undefined) { - pdbCustomers.getAll({ + pdbMain.getAll({ include_docs: true, limit: limit, skip: --page * limit }).then(successBulk).catch(failure); } else { - pdbCustomers.query({ + pdbMain.query({ map: mapQuery, reduce: reduceQuery }, { @@ -274,7 +258,7 @@ function iterateValues(value) { var customer = {}, vehicles = []; if (value) { - if (value.name == 'Anonymous') + if ($rootScope.amGlobals.IsConfigDoc(value._id) || (value.name == 'Anonymous')) return; customer.id = value._id; customer.name = value.name; @@ -306,7 +290,7 @@ res.rows.forEach(iterateRow); function iterateRow(row) { - if (row.doc.user.name == 'Anonymous') + if ($rootScope.amGlobals.IsConfigDoc(row.id) || (row.doc.user.name == 'Anonymous')) return; var customer = {}, vehicles = []; if (row.doc && row.doc.user) { @@ -340,6 +324,7 @@ } // when database rejects function failure(error) { + console.log(error); tracker.reject({ total: 0, customers: customers @@ -351,7 +336,7 @@ function getManufacturers() { var tracker = $q.defer(); var manufacturers = []; - pdbCommon.get('manuf-models').then(success).catch(missDb); + $http.get('data/manuf_model.json').success(success).catch(failure); return tracker.promise; // success ? add manufacturers to an array and return it via promise @@ -360,11 +345,6 @@ tracker.resolve(manufacturers); } - // !success ? get manufacturer list from raw json file, then execute success sequence again - function missDb(error) { - $http.get('data/manuf_model.json').success(success).catch(failure); - } - // iterate through manufacturer list and get individual manufacturer function manufIterator(manuf) { if (!manuf.match(/\b_id|\b_rev/i)) @@ -381,7 +361,7 @@ function getModels(manufacturer) { var tracker = $q.defer(); var models = []; - pdbCommon.get('manuf-models').then(success).catch(missDb); + $http.get('data/manuf_model.json').success(success).catch(failure); return tracker.promise; // success ? add models to an array and return it via promise @@ -391,11 +371,6 @@ tracker.resolve(models); } - // !success ? get manufacturer list from raw json file, then execute success sequence again - function missDb(error) { - $http.get('data/manuf_model.json').success(success).catch(failure); - } - // throw an error via promise function failure(error) { tracker.reject(error); @@ -405,12 +380,12 @@ // delete customer from database function deleteCustomer(cId) { var tracker = $q.defer(); - pdbCustomers.get(cId).then(userFound).catch(failure); + pdbMain.get(cId).then(userFound).catch(failure); return tracker.promise; function userFound(res) { res._deleted = true; - pdbCustomers.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function success(res) { @@ -425,7 +400,7 @@ // save customer to database function saveCustomer(document) { var tracker = $q.defer(); - pdbCustomers.save(document).then(success).catch(failure); + pdbMain.save(document).then(success).catch(failure); return tracker.promise; function success(response) { @@ -452,7 +427,8 @@ var doc = { _id: utils.generateUUID(prefixUser), - creator: $amRoot.username, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, user: customer } @@ -481,7 +457,7 @@ // get customer from database function getCustomer(id) { - return pdbCustomers.get(id); + return pdbMain.get(id); } } })(); \ No newline at end of file diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index cf6337e4..f7cb9461 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -12,9 +12,9 @@ angular.module('automintApp').controller('dashboardCtrl', DashboardController); - DashboardController.$inject = ['$state', '$filter', '$log', '$mdDialog', '$amRoot', 'utils', 'amDashboard']; + DashboardController.$inject = ['$state', '$filter', '$log', '$mdDialog', 'utils', 'amDashboard']; - function DashboardController($state, $filter, $log, $mdDialog, $amRoot, utils, amDashboard) { + function DashboardController($state, $filter, $log, $mdDialog, utils, amDashboard) { // initialize view model var vm = this; var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false; @@ -102,7 +102,6 @@ // default execution steps initCurrentTimeSet(); getFilterMonths(); - $amRoot.ccViews(); processPreferences(); getCurrencySymbol(); diff --git a/src/app/components/dashboard/dashboard.factory.js b/src/app/components/dashboard/dashboard.factory.js index 8a9b0301..b8b375e5 100644 --- a/src/app/components/dashboard/dashboard.factory.js +++ b/src/app/components/dashboard/dashboard.factory.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').factory('amDashboard', DashboardFactory); - DashboardFactory.$inject = ['$q', '$filter', '$amRoot', 'utils', 'constants', 'pdbConfig', 'pdbCache', 'pdbCustomers']; + DashboardFactory.$inject = ['$q', '$filter', '$rootScope', 'utils', 'constants', 'pdbMain', 'pdbCache']; - function DashboardFactory($q, $filter, $amRoot, utils, constants, pdbConfig, pdbCache, pdbCustomers) { + function DashboardFactory($q, $filter, $rootScope, utils, constants, pdbMain, pdbCache) { // initialize dashboard factory and funtion mappings var factory = { getTotalCustomerServed: getTotalCustomerServed, @@ -32,25 +32,22 @@ function saveCurrencySymbol(currency) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds).then(getSettingsObject).catch(writeSettingsObject); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { res.currency = currency; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsObject(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, currency: currency } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -64,13 +61,9 @@ function getCurrencySymbol() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.currency) tracker.resolve(res.currency); @@ -85,12 +78,12 @@ function changeServiceReminderDate(cId, vId, nextdue) { var tracker = $q.defer(); - pdbCustomers.get(cId).then(getCustomerDoc).catch(failure); + pdbMain.get(cId).then(getCustomerDoc).catch(failure); return tracker.promise; function getCustomerDoc(res) { res.user.vehicles[vId].nextdue = nextdue; - pdbCustomers.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function success(res) { @@ -104,12 +97,12 @@ function deleteServiceReminder(cId, vId) { var tracker = $q.defer(); - pdbCustomers.get(cId).then(getCustomerDoc).catch(failure); + pdbMain.get(cId).then(getCustomerDoc).catch(failure); return tracker.promise; function getCustomerDoc(res) { delete res.user.vehicles[vId].nextdue; - pdbCustomers.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function success(res) { @@ -236,7 +229,7 @@ function getProblemsAndVehicleTypes(dateRange) { var problems = {}, vehicleTypes = {}; var tracker = $q.defer(); - pdbCustomers.getAll().then(success).catch(failure); + pdbMain.getAll().then(success).catch(failure); return tracker.promise; function success(res) { @@ -247,6 +240,8 @@ }); function iterateRows(row) { + if ($rootScope.amGlobals.IsConfigDoc(row.id)) + return; if (row.doc.user.vehicles) Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); diff --git a/src/app/components/inventory/inventory.factory.js b/src/app/components/inventory/inventory.factory.js index 732dd6f6..a74f7b9d 100644 --- a/src/app/components/inventory/inventory.factory.js +++ b/src/app/components/inventory/inventory.factory.js @@ -2,7 +2,7 @@ * Factory that handles database interactions between inventory database and controller * @author ndkcha * @since 0.6.1 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').factory('amInventory', InventoryFactory); - InventoryFactory.$inject = ['$q', '$amRoot', '$filter', 'utils', 'pdbConfig']; + InventoryFactory.$inject = ['$q', '$rootScope', '$filter', 'pdbMain']; - function InventoryFactory($q, $amRoot, $filter, utils, pdbConfig) { + function InventoryFactory($q, $rootScope, $filter, pdbMain) { // Initialize factory variable and function mappings var factory = { getInventory: getInventory, @@ -29,59 +29,51 @@ function getDisplayAsList() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(configFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(configDocFound).catch(failure); return tracker.promise; - function configFound(res) { - pdbConfig.get($amRoot.docIds.settings).then(configDocFound).catch(failure); - } - function configDocFound(res) { if (res.settings && res.settings.inventory) tracker.resolve(res.settings.inventory.displayAsList); else - failure(); + failure('Settings not found!'); } function failure(err) { - if (!err) { - err = { - success: false, - message: 'Setting not found!' - } - } tracker.reject(err); } } function changeDisplayAsList(displayAsList) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(configFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(configDocFound).catch(writeNewConfig); return tracker.promise; - function configFound(res) { - pdbConfig.get($amRoot.docIds.settings).then(configDocFound).catch(writeNewConfig); - } function configDocFound(res) { if (!res.settings) res.settings = {}; if (!res.settings.inventory) res.settings.inventory = {}; res.settings.inventory['displayAsList'] = displayAsList; - pdbConfig.save(res).then(saveSuccess).catch(failure); + pdbMain.save(res).then(saveSuccess).catch(failure); } + function writeNewConfig(err) { - var doc = {}; - doc['_id'] = utils.generateUUID('sttngs'); - doc['creator'] = $amRoot.username; + var doc = { + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, + }; doc.settings = {}; doc.settings['inventory'] = {} doc.settings.inventory['displayAsList'] = displayAsList; - pdbConfig.save(doc).then(saveSuccess).catch(failure); + pdbMain.save(doc).then(saveSuccess).catch(failure); } + function saveSuccess(response) { tracker.resolve(response); } + function failure(error) { tracker.reject(error); } @@ -89,19 +81,15 @@ function deleteInventory(name) { var tracker = $q.defer(); - $amRoot.isInventoryId().then(getInventoryDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.inventory).then(getInventoryObj).catch(failure); return tracker.promise; - function getInventoryDoc(res) { - pdbConfig.get($amRoot.docIds.inventory).then(getInventoryObj).catch(failure); - } - function getInventoryObj(res) { if (res[name]) { res[name]._deleted = true; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } else - failure(); + failure('Object not found!'); } function success(res) { @@ -109,25 +97,15 @@ } function failure(err) { - if (!err) { - err = { - success: false, - message: 'Object Not Found!' - } - } tracker.reject(err); } } function getInventory(name) { var tracker = $q.defer(); - $amRoot.isInventoryId().then(getInventoryDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.inventory).then(getInventoryObj).catch(failure); return tracker.promise; - function getInventoryDoc(res) { - pdbConfig.get($amRoot.docIds.inventory).then(getInventoryObj).catch(failure); - } - function getInventoryObj(res) { if (res[name]) { var temp = res[name]; @@ -151,19 +129,15 @@ function getInventories() { var tracker = $q.defer(); var response = []; - $amRoot.isInventoryId().then(getInventoryDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.inventory).then(getInventoryObj).catch(failure); return tracker.promise; - function getInventoryDoc(res) { - pdbConfig.get($amRoot.docIds.inventory).then(getInventoryObj).catch(failure); - } - function getInventoryObj(res) { Object.keys(res).forEach(iterateInventories); tracker.resolve(response); function iterateInventories(name) { - if (name.match(/\b_id|\b_rev|\bcreator/i) || res[name]._deleted == true) + if (name.match(/\b_id|\b_rev|\bcreator|\bchannel/i) || res[name]._deleted == true) return; var temp = res[name]; temp.name = name; @@ -179,18 +153,14 @@ function saveInventory(inventory, operationMode) { var tracker = $q.defer(); - $amRoot.isInventoryId().then(getInventoryDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.inventory).then(getInventoryObj).catch(writeInventoryDoc); return tracker.promise; - function getInventoryDoc(res) { - pdbConfig.get($amRoot.docIds.inventory).then(getInventoryObj).catch(writeInventoryDoc); - } - function getInventoryObj(res) { if (!(res[inventory.name] && operationMode == 'add')) { res[inventory.name] = inventory; delete res[inventory.name].name; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } else { tracker.resolve({ ok: true, @@ -201,12 +171,13 @@ function writeInventoryDoc(err) { var doc = { - _id: utils.generateUUID('invntry'), - creator: $amRoot.username + _id: $rootScope.amGlobals.configDocIds.inventory, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel }; doc[inventory.name] = inventory; delete inventory.name; - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { diff --git a/src/app/components/invoices/invoices.factory.js b/src/app/components/invoices/invoices.factory.js index c56b4e82..53e33cd3 100644 --- a/src/app/components/invoices/invoices.factory.js +++ b/src/app/components/invoices/invoices.factory.js @@ -11,9 +11,9 @@ angular.module('automintApp') .factory('amInvoices', InvoicesFactory); - InvoicesFactory.$inject = ['$q', '$amRoot', 'pdbCustomers', 'pdbConfig']; + InvoicesFactory.$inject = ['$q', '$rootScope', 'pdbMain']; - function InvoicesFactory($q, $amRoot, pdbCustomers, pdbConfig) { + function InvoicesFactory($q, $rootScope, pdbMain) { // initialize factory variable and function mappings var factory = { getServiceDetails: getServiceDetails, @@ -31,13 +31,9 @@ function getInvoicePageSize() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.pageSize) tracker.resolve(res.settings.invoices.pageSize); @@ -52,13 +48,9 @@ function getCurrencySymbol() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.currency) tracker.resolve(res.currency); @@ -73,12 +65,12 @@ function saveCustomerEmail(userId, email) { var tracker = $q.defer(); - pdbCustomers.get(userId).then(getUserObject).catch(failure); + pdbMain.get(userId).then(getUserObject).catch(failure); return tracker.promise; function getUserObject(res) { res.user.email = email; - pdbCustomers.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function success(res) { @@ -92,13 +84,9 @@ function getIvAlignMargins() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.margin) tracker.resolve(res.settings.invoices.margin); @@ -120,7 +108,7 @@ // get service details from database function getServiceDetails(userId, vehicleId, serviceId) { var tracker = $q.defer(); - pdbCustomers.get(userId).then(getUserObject).catch(failure); + pdbMain.get(userId).then(getUserObject).catch(failure); return tracker.promise; function getUserObject(res) { @@ -214,13 +202,9 @@ // get workshop details from database function getWorkshopDetails() { var tracker = $q.defer(); - $amRoot.isWorkshopId().then(getWorkshopDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.workshop).then(getWorkshopObject).catch(failure); return tracker.promise; - function getWorkshopDoc(res) { - pdbConfig.get($amRoot.docIds.workshop).then(getWorkshopObject).catch(failure); - } - function getWorkshopObject(res) { if (res.workshop) tracker.resolve(res.workshop); @@ -241,13 +225,9 @@ // get display configurations function getIvSettings() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices) tracker.resolve(res.settings.invoices); diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 4a214ab7..cadf2fb1 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -11,9 +11,9 @@ angular.module('automintApp') .factory('amServices', ServicesFactory); - ServicesFactory.$inject = ['$http', '$timeout', '$q', '$log', '$filter', 'utils', '$amRoot', 'constants', 'pdbCustomers', 'pdbCommon', 'pdbConfig', 'pdbCache']; + ServicesFactory.$inject = ['$http', '$timeout', '$q', '$log', '$filter', 'utils', '$rootScope', 'constants', 'pdbMain', 'pdbCache']; - function ServicesFactory($http, $timeout, $q, $log, $filter, utils, $amRoot, constants, pdbCustomers, pdbCommon, pdbConfig, pdbCache) { + function ServicesFactory($http, $timeout, $q, $log, $filter, utils, $rootScope, constants, pdbMain, pdbCache) { // initialize factory variable and function maps var factory = { getManufacturers: getManufacturers, @@ -48,13 +48,9 @@ function getCurrencySymbol() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.currency) tracker.resolve(res.currency); @@ -69,13 +65,9 @@ function getTreatmentsTax() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - function getSettingsObj(res) { var result = []; if (res.settings && res.settings.tax) @@ -104,13 +96,9 @@ function getInventoryTax() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - function getSettingsObj(res) { var result = []; if (res.settings && res.settings.tax) @@ -165,13 +153,9 @@ function getDefaultServiceType() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.servicestate) tracker.resolve(res.settings.servicestate); @@ -192,13 +176,9 @@ function getInventoriesSettings() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(configFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(configDocFound).catch(failure); return tracker.promise; - function configFound(res) { - pdbConfig.get($amRoot.docIds.settings).then(configDocFound).catch(failure); - } - function configDocFound(res) { if (res.settings && res.settings.inventory) tracker.resolve(res.settings.inventory.displayAsList); @@ -220,13 +200,9 @@ function getInventories() { var tracker = $q.defer(); var response = []; - $amRoot.isInventoryId().then(getInventoryDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.inventory).then(getInventoryObj).catch(failure); return tracker.promise; - function getInventoryDoc(res) { - pdbConfig.get($amRoot.docIds.inventory).then(getInventoryObj).catch(failure); - } - function getInventoryObj(res) { Object.keys(res).forEach(iterateInventories); tracker.resolve(response); @@ -253,14 +229,9 @@ // retrieve current treatment settings function getTreatmentSettings() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - // if settings configuration is tracked, fetch corrosponding document - function getSettingsDoc(response) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - // if document call is successfull, return treatment settings if available function getSettingsObject(response) { if (response.settings.treatments) @@ -281,13 +252,9 @@ // retrieve vehicle types from config database function getVehicleTypes() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.vehicletypes) tracker.resolve(res.vehicletypes); @@ -303,7 +270,7 @@ // return service tree function serviceTree(userId, vehicleId, serviceId) { var tracker = $q.defer(); - pdbCustomers.get(userId).then(getUserObject).catch(failure); + pdbMain.get(userId).then(getUserObject).catch(failure); return tracker.promise; function getUserObject(response) { @@ -364,12 +331,12 @@ // delete service from UI function deleteService(userId, vehicleId, serviceId) { var tracker = $q.defer(); - pdbCustomers.get(userId).then(getUserObject).catch(failure); + pdbMain.get(userId).then(getUserObject).catch(failure); return tracker.promise; function getUserObject(res) { res.user.vehicles[vehicleId].services[serviceId]._deleted = true; - pdbCustomers.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function success(res) { @@ -436,13 +403,9 @@ memberships: [], total: 0 } - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.memberships) Object.keys(res.memberships).forEach(iterateMemberships); @@ -480,13 +443,9 @@ function getPackages() { var tracker = $q.defer(); var packages = []; - $amRoot.isTreatmentId().then(getConfigDocument).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getConfigObject).catch(failure); return tracker.promise; - function getConfigDocument(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getConfigObject).catch(failure); - } - function getConfigObject(res) { if (res.packages) Object.keys(res.packages).forEach(iteratePackages); @@ -511,13 +470,9 @@ function getRegularTreatments() { var tracker = $q.defer(); var treatments = []; - $amRoot.isTreatmentId().then(getConfigDocument).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getConfigObject).catch(failure); return tracker.promise; - function getConfigDocument(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getConfigObject).catch(failure); - } - function getConfigObject(res) { if (res.regular) Object.keys(res.regular).forEach(iterateTreatments); @@ -545,7 +500,7 @@ include_docs: false } var customers = []; - pdbCustomers.getAll(dbOptions).then(success).catch(failure); + pdbMain.getAll(dbOptions).then(success).catch(failure); return tracker.promise; function success(res) { @@ -553,6 +508,8 @@ tracker.resolve(customers); function iterateRows(row) { + if ($rootScope.amGlobals.IsConfigDoc(row.id)) + return; var splitname = row.id.split('-'); var name = splitname[1]; for (var i = 2; i < (splitname.length - 5); i++) { @@ -576,7 +533,7 @@ // return customer information and their vehicles function getCustomerChain(uId) { var tracker = $q.defer(); - pdbCustomers.get(uId).then(success).catch(failure); + pdbMain.get(uId).then(success).catch(failure); return tracker.promise; function success(res) { @@ -615,7 +572,7 @@ // return customer information based on their mobile number function getCustomerByMobile(mobile) { var tracker = $q.defer(); - pdbCustomers.query(mapView, { + pdbMain.query(mapView, { include_docs: true, key: mobile }).then(success).catch(failure); @@ -713,7 +670,7 @@ saveLastJobCardNo(newService.jobcardno); } - pdbCustomers.get(newUserId).then(foundExistingUser).catch(noUserFound); + pdbMain.get(newUserId).then(foundExistingUser).catch(noUserFound); return tracker.promise; function foundExistingUser(res) { @@ -725,7 +682,7 @@ name = utils.convertToTitleCase(name); if (name != newUser.name) { res._deleted = true; - pdbCustomers.save(res).then(logResponse).catch(logResponse); + pdbMain.save(res).then(logResponse).catch(logResponse); res._id = utils.generateUUID(prefixUser); delete res._deleted; delete res._rev; @@ -743,7 +700,7 @@ res.user.vehicles[newVehicleId].services = {}; res.user.vehicles[newVehicleId].services[newServiceId] = newService; } - pdbCustomers.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); function iterateUserFields(ufn) { res.user[ufn] = newUser[ufn]; @@ -769,10 +726,11 @@ } var doc = { _id: newUserId, - creator: $amRoot.username, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, user: newUser } - pdbCustomers.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -878,7 +836,7 @@ function getManufacturers() { var tracker = $q.defer(); var manufacturers = []; - pdbCommon.get('manuf-models').then(success).catch(missDb); + $http.get('data/manuf_model.json').success(success).catch(failure); return tracker.promise; // success ? add manufacturers to an array and return it via promise @@ -887,11 +845,6 @@ tracker.resolve(manufacturers); } - // !success ? get manufacturer list from raw json file, then execute success sequence again - function missDb(error) { - $http.get('data/manuf_model.json').success(success).catch(failure); - } - // iterate through manufacturer list and get individual manufacturer function manufIterator(manuf) { if (!manuf.match(/\b_id|\b_rev/i)) @@ -908,7 +861,7 @@ function getModels(manufacturer) { var tracker = $q.defer(); var models = []; - pdbCommon.get('manuf-models').then(success).catch(missDb); + $http.get('data/manuf_model.json').success(success).catch(failure); return tracker.promise; // success ? add models to an array and return it via promise @@ -918,11 +871,6 @@ tracker.resolve(models); } - // !success ? get manufacturer list from raw json file, then execute success sequence again - function missDb(error) { - $http.get('data/manuf_model.json').success(success).catch(failure); - } - // throw an error via promise function failure(error) { tracker.reject(error); @@ -931,13 +879,9 @@ function getLastJobCardNo() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.lastJobCardNo) tracker.resolve(res.settings.invoices.lastJobCardNo); @@ -958,13 +902,9 @@ function getLastEstimateNo() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.lastEstimateNo) tracker.resolve(res.settings.invoices.lastEstimateNo); @@ -986,13 +926,9 @@ // get last invoice number function getLastInvoiceNo() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.lastInvoiceNumber) tracker.resolve(res.settings.invoices.lastInvoiceNumber); @@ -1013,11 +949,7 @@ // save last invoice number function saveLastInvoiceNo(lino) { - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsObject); function getSettingsObject(res) { if (!res.settings) @@ -1025,20 +957,21 @@ if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.lastInvoiceNumber = lino; - pdbConfig.save(res); + pdbMain.save(res); } function writeSettingsObject(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { lastInvoiceNumber: 1 } } } - pdbConfig.save(doc); + pdbMain.save(doc); } function failure(err) { @@ -1047,11 +980,7 @@ } function saveLastEstimateNo(leno) { - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsObject); function getSettingsObject(res) { if (!res.settings) @@ -1059,20 +988,21 @@ if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.lastEstimateNo = leno; - pdbConfig.save(res); + pdbMain.save(res); } function writeSettingsObject(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { lastEstimateNo: 1 } } } - pdbConfig.save(doc); + pdbMain.save(doc); } function failure(err) { @@ -1081,11 +1011,7 @@ } function saveLastJobCardNo(ljbno) { - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); - - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsObject); function getSettingsObject(res) { if (!res.settings) @@ -1093,20 +1019,21 @@ if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.lastJobCardNo = ljbno; - pdbConfig.save(res); + pdbMain.save(res); } function writeSettingsObject(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { lastJobCardNo: 1 } } } - pdbConfig.save(doc); + pdbMain.save(doc); } function failure(err) { diff --git a/src/app/components/settings/settings-backup.factory.js b/src/app/components/settings/settings-backup.factory.js index 5ca2ac6a..e0307118 100644 --- a/src/app/components/settings/settings-backup.factory.js +++ b/src/app/components/settings/settings-backup.factory.js @@ -2,18 +2,17 @@ * Factory to backup pouchDb dump to local storage * @author ndkcha * @since 0.4.1 - * @version 0.4.1 + * @version 0.7.0 */ /// (function() { - angular.module('automintApp') - .factory('amBackup', BackupFactory); + angular.module('automintApp').factory('amBackup', BackupFactory); - BackupFactory.$inject = ['$q', 'pdbCustomers', 'pdbConfig', 'constants']; + BackupFactory.$inject = ['$q', 'pdbMain', 'pdbLocal']; - function BackupFactory($q, pdbCustomers, pdbConfig, constants) { + function BackupFactory($q, pdbMain, pdbLocal) { // temporary named assignments var backupDocument = {}; var tracker; @@ -32,20 +31,20 @@ tracker = $q.defer(); $q.all([ - pdbCustomers.getAll(), - pdbConfig.getAll() + pdbMain.getAll(), + pdbLocal.getAll() ]).then(foundDocsToBackup).catch(noDocsToBackup); return tracker.promise; } function foundDocsToBackup(data) { backupDocument.backupTime = Date.now(); - backupDocument.customers = {} - backupDocument.customers.doc = []; - backupDocument.config = {}; - backupDocument.config.doc = []; - data[0].rows.forEach(iterateCustomers); - data[1].rows.forEach(iterateConfigs); + backupDocument.main = {} + backupDocument.main.doc = []; + backupDocument.local = {}; + backupDocument.local.doc = []; + data[0].rows.forEach(iterateMain); + data[1].rows.forEach(iterateLocal); // content dispostion var docText = JSON.stringify(backupDocument); var date = new Date(); @@ -63,14 +62,14 @@ }); } - // iterate through customers to add into backup document - function iterateCustomers(intr_customer) { - backupDocument.customers.doc.push(intr_customer.doc); + // iterate through main docs to add into backup document + function iterateMain(intr_customer) { + backupDocument.main.doc.push(intr_customer.doc); } - // iterate through configs to add into backup document - function iterateConfigs(intr_config) { - backupDocument.config.doc.push(intr_config.doc); + // iterate through local docs to add into backup document + function iterateLocal(intr_config) { + backupDocument.local.doc.push(intr_config.doc); } // error fetching from database diff --git a/src/app/components/settings/settings-importdata.service.js b/src/app/components/settings/settings-importdata.service.js index 9b289bb9..44fd40c8 100644 --- a/src/app/components/settings/settings-importdata.service.js +++ b/src/app/components/settings/settings-importdata.service.js @@ -2,7 +2,7 @@ * Service for Importing data to Automint * @author ndkcha * @since 0.4.1 - * @version 0.4.1 + * @version 0.7.0 */ /// @@ -11,9 +11,9 @@ angular.module('automintApp') .service('amImportdata', ImportDataService); - ImportDataService.$inject = ['pdbCustomers', '$filter', '$q', 'utils', '$amRoot', '$log']; + ImportDataService.$inject = ['pdbMain', '$filter', '$q', 'utils', '$rootScope', '$log']; - function ImportDataService(pdbCustomers, $filter, $q, utils, $amRoot, $log) { + function ImportDataService(pdbMain, $filter, $q, utils, $rootScope, $log) { var sVm = this; // temporary named mappings @@ -133,7 +133,7 @@ function getDocuments() { var tracker = $q.defer(); var documents = []; - pdbCustomers.getAll().then(docFound).catch(failure); + pdbMain.getAll().then(docFound).catch(failure); return tracker.promise; function docFound(res) { @@ -146,6 +146,8 @@ } function iterateDocuments(element) { + if ($rootScope.amGlobals.IsConfigDoc(element.id)) + return; documents.push(element.doc); } } @@ -196,7 +198,8 @@ var prefixUser = 'usr-' + angular.lowercase(customer.name).replace(' ', '-'); targetUser = { _id: utils.generateUUID(prefixUser), - creator: $amRoot.username + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel }; targetUser.user = $.extend({}, customer); if (customer.vehicles && customer.vehicles.length > 0) { @@ -217,7 +220,7 @@ }); customersToSave.push(targetUser); } - pdbCustomers.saveAll(customersToSave).then(saveSuccess).catch(failure); + pdbMain.saveAll(customersToSave).then(saveSuccess).catch(failure); tracker.resolve({ success: true, message: addedCustomerCount + " customer(s) added!" diff --git a/src/app/components/settings/settings-invoices.factory.js b/src/app/components/settings/settings-invoices.factory.js index 5c1b13bc..4658fc73 100644 --- a/src/app/components/settings/settings-invoices.factory.js +++ b/src/app/components/settings/settings-invoices.factory.js @@ -11,9 +11,9 @@ angular.module('automintApp') .factory('amIvSettings', IvSettingsFactory); - IvSettingsFactory.$inject = ['$q', '$amRoot', 'utils', 'pdbConfig']; + IvSettingsFactory.$inject = ['$q', '$rootScope', 'pdbMain']; - function IvSettingsFactory($q, $amRoot, utils, pdbConfig) { + function IvSettingsFactory($q, $rootScope, pdbMain) { // initialize factory and function mappings var factory = { getWorkshopDetails: getWorkshopDetails, @@ -36,33 +36,30 @@ function saveInvoicePageSize(pageSize) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsDoc); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.pageSize = pageSize; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { pageSize: pageSize } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -76,13 +73,9 @@ function getInvoicePageSize() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.pageSize) tracker.resolve(res.settings.invoices.pageSize); @@ -98,13 +91,9 @@ // get workshop details from config database function getWorkshopDetails() { var tracker = $q.defer(); - $amRoot.isWorkshopId().then(getWorkshopDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.workshop).then(getWorkshopObject).catch(failure); return tracker.promise; - function getWorkshopDoc(res) { - pdbConfig.get($amRoot.docIds.workshop).then(getWorkshopObject).catch(failure); - } - function getWorkshopObject(res) { if (res.workshop) tracker.resolve(res.workshop); @@ -126,25 +115,22 @@ // save workshop details to config database function saveWorkshopDetails(workshop) { var tracker = $q.defer(); - $amRoot.isWorkshopId().then(getWorkshopDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.workshop).then(updateWorkshopDoc).catch(createWorkshopDoc); return tracker.promise; - function getWorkshopDoc(res) { - pdbConfig.get($amRoot.docIds.workshop).then(updateWorkshopDoc).catch(createWorkshopDoc); - } - function updateWorkshopDoc(res) { res.workshop = workshop; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function createWorkshopDoc(err) { var doc = { - _id: utils.generateUUID('wrkshp'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.workshop, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, workshop: workshop } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -159,13 +145,9 @@ // get invoice settings function getInvoiceSettings() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices) tracker.resolve(res.settings.invoices); @@ -187,33 +169,30 @@ // save invoice display settings function saveIvDisplaySettings(display) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsDoc); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.display = display; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { display: display } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -226,33 +205,30 @@ function changeLastJobCardNo(jobcardno) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.lastJobCardNo = jobcardno; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } - function writeSettingsObject(err) { + function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { lastJobCardNo: jobcardno } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -266,33 +242,30 @@ function changeLastEstimateNo(estimateno) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.lastEstimateNo = estimateno; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } - function writeSettingsObject(err) { + function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { lastEstimateNo: estimateno } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -307,33 +280,30 @@ // change last invoice number in database function changeLastInvoiceNo(invoiceno) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.lastInvoiceNumber = invoiceno; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } - function writeSettingsObject(err) { + function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { lastInvoiceNumber: invoiceno } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -347,33 +317,30 @@ function saveIvEmailSubject(emailsubject) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.emailsubject = emailsubject; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } - function writeSettingsObject(err) { + function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { emailsubject: emailsubject } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -387,33 +354,30 @@ function saveIvAlignMargins(margin) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsDoc); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; if (!res.settings.invoices) res.settings.invoices = {}; res.settings.invoices.margin = margin; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { invoices: { margin: margin } } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -427,13 +391,9 @@ function getIvAlignMargins() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.invoices && res.settings.invoices.margin) tracker.resolve(res.settings.invoices.margin); diff --git a/src/app/components/settings/settings-login.factory.js b/src/app/components/settings/settings-login.factory.js index f9bf978e..43de6712 100644 --- a/src/app/components/settings/settings-login.factory.js +++ b/src/app/components/settings/settings-login.factory.js @@ -2,22 +2,20 @@ * Factory to handle login events * @author ndkcha * @since 0.4.1 - * @version 0.6.1 + * @version 0.7.0 */ /// (function() { angular.module('automintApp') - .factory('amLogin', LoginFactory); + .factory('amLoginSettings', LoginFactory); - LoginFactory.$inject = ['$q', '$amRoot', 'utils', 'pdbConfig', 'pdbCustomers']; + LoginFactory.$inject = ['$q', '$rootScope', 'pdbMain']; - function LoginFactory($q, $amRoot, utils, pdbConfig, pdbCustomers) { + function LoginFactory($q, $rootScope, pdbMain) { // initialize factory variable and funtion maps var factory = { - login: login, - loginDetails: loginDetails, getPasscode: getPasscode, savePasscode: savePasscode } @@ -28,13 +26,9 @@ function getPasscode() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - function getSettingsObj(res) { tracker.resolve(res.passcode); } @@ -46,31 +40,28 @@ function savePasscode(passcode, enabled) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(writeSettingsObj); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(writeSettingsObj); - } - function getSettingsObj(res) { res.passcode = { enabled: enabled, code: passcode }; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsObj(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, passcode: { enabled: enabled, code: passcode } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -81,100 +72,5 @@ tracker.reject(err); } } - - // get login details from database - function loginDetails() { - var tracker = $q.defer(); - $amRoot.isWorkshopId().then(workshopConfigFound).catch(failure); - return tracker.promise; - - function workshopConfigFound(res) { - pdbConfig.get($amRoot.docIds.workshop).then(configExists).catch(failure); - } - - function configExists(res) { - if (res.user && res.user.username && res.user.password) { - tracker.resolve({ - username: res.user.username - }); - } else - failure(); - } - - function failure(err) { - tracker.reject({ - success: false - }) - } - } - - // store login information into database - function login(username, password) { - var tracker = $q.defer(); - $amRoot.isWorkshopId().then(workshopConfigFound).catch(failure); - return tracker.promise; - - function workshopConfigFound(res) { - pdbConfig.get($amRoot.docIds.workshop).then(configExists).catch(noDocFound); - - function configExists(res) { - if (!res.user) - res.user = {}; - res.creator = $amRoot.username; - res.user['username'] = username; - res.user['password'] = password; - updateCreatorToDocs(username); - pdbConfig.save(res).then(success).catch(failure); - } - - function noDocFound(err) { - var doc = {}; - doc['_id'] = utils.generateUUID('wrkshp'); - doc.creator = $amRoot.username; - doc.user = { - username: username, - password: password - }; - updateCreatorToDocs(username); - pdbConfig.save(doc).then(success).catch(failure); - } - - function success(res) { - tracker.resolve(res); - } - } - - function failure(err) { - tracker.reject(err); - } - } - - // manually update creator to all (unsynced) documents - function updateCreatorToDocs(creator) { - pdbConfig.getAll().then(configFound).catch(failure); - pdbCustomers.getAll().then(customersFound).catch(failure); - - function configFound(res) { - res.rows.forEach(iterateConfigDoc); - - function iterateConfigDoc(element) { - element.doc['creator'] = creator; - pdbConfig.save(element.doc); - } - } - - function customersFound(res) { - res.rows.forEach(iterateCustomerDoc); - - function iterateCustomerDoc(element) { - element.doc['creator'] = creator; - pdbCustomers.save(element.doc); - } - } - - function failure(err) { - $log.warn('Cannot sync data'); - } - } } })(); \ No newline at end of file diff --git a/src/app/components/settings/settings-tax.factory.js b/src/app/components/settings/settings-tax.factory.js index 1ecd5e02..1387e52c 100644 --- a/src/app/components/settings/settings-tax.factory.js +++ b/src/app/components/settings/settings-tax.factory.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').factory('amTaxSettings', TaxSettings); - TaxSettings.$inject = ['$q', '$amRoot', 'utils', 'pdbConfig']; + TaxSettings.$inject = ['$q', '$rootScope', 'pdbMain']; - function TaxSettings($q, $amRoot, utils, pdbConfig) { + function TaxSettings($q, $rootScope, pdbMain) { // intialize factory variable and function maps var factory = { getAllTaxSettings: getAllTaxSettings, @@ -26,17 +26,13 @@ function deleteTaxSettings(tax) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - function getSettingsObj(res) { if (res.settings && res.settings.tax && res.settings.tax[tax.name]) { delete res.settings.tax[tax.name]; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } } @@ -51,13 +47,9 @@ function saveTaxSettings(tax) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(writeSettingsDoc); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(writeSettingsDoc); - } - function getSettingsObj(res) { if (!res.settings) res.settings = {}; @@ -70,13 +62,14 @@ isForInventory: tax.isForInventory, percent: tax.percent }; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsDoc(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { tax: {} } @@ -88,7 +81,7 @@ isForInventory: tax.isForInventory, percent: tax.percent } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -102,13 +95,9 @@ function getAllTaxSettings() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObj).catch(failure); - } - function getSettingsObj(res) { var result = []; if (res.settings && res.settings.tax) { diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 2cd8f518..32a4b494 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -17,9 +17,9 @@ angular.module('automintApp').controller('amCtrlSettings', SettingsController); - SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', 'utils', 'amBackup', 'amLogin', 'amImportdata', 'amIvSettings', 'amTaxSettings', 'amSettings']; + SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', 'utils', 'amBackup', 'amLoginSettings', 'amImportdata', 'amIvSettings', 'amTaxSettings', 'amSettings']; - function SettingsController($rootScope, $scope, $state, $log, utils, amBackup, amLogin, amImportdata, amIvSettings, amTaxSettings, amSettings) { + function SettingsController($rootScope, $scope, $state, $log, utils, amBackup, amLoginSettings, amImportdata, amIvSettings, amTaxSettings, amSettings) { // initialize view model var vm = this; @@ -75,7 +75,6 @@ vm.changeUsernameLabel = changeUsernameLabel; vm.changePasswordLabel = changePasswordLabel; vm.doBackup = doBackup; - vm.doLogin = doLogin; vm.uploadCSV = uploadCSV; vm.uploadCover = uploadCover; vm.handleUploadedFile = handleUploadedFile; @@ -135,7 +134,6 @@ default: break; } - checkLogin(); getPasscode(); getDefaultServiceType(); getAmAppDataPath(); @@ -502,7 +500,7 @@ } function getPasscode() { - amLogin.getPasscode().then(success).catch(failure); + amLoginSettings.getPasscode().then(success).catch(failure); function success(res) { if (!res) { @@ -531,7 +529,7 @@ } if (vm.passcode == oPasscode && vm.isPasscodeEnabled == oPasscodeEnabled) return; - amLogin.savePasscode(vm.passcode, vm.isPasscodeEnabled).then(success).catch(failure); + amLoginSettings.savePasscode(vm.passcode, vm.isPasscodeEnabled).then(success).catch(failure); function success(res) { if (res.ok) { @@ -685,39 +683,6 @@ $log.info(res); } } - - // check if user is signed in or not - function checkLogin() { - amLogin.loginDetails().then(success).catch(failure); - - function success(res) { - if (res.username) { - vm.isLoggedIn = true; - vm.label_login = 'You are signed in'; - vm.user.username = res.username; - changeUsernameLabel(); - } else - failure(); - } - - function failure() { - vm.isLoggedIn = false; - vm.label_login = 'Sign In'; - } - } - - // store user's login information in database - function doLogin() { - amLogin.login(vm.user.username, vm.user.password).then(success); - - function success(res) { - if (res.ok) { - vm.isLoggedIn = true; - vm.label_login = 'You are signed in'; - utils.showSimpleToast('You have successfully logged in!'); - } - } - } // functions defined for invoice settings // get workshop details from database diff --git a/src/app/components/settings/settings.factory.js b/src/app/components/settings/settings.factory.js index c236b172..4ef4c175 100644 --- a/src/app/components/settings/settings.factory.js +++ b/src/app/components/settings/settings.factory.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').factory('amSettings', SettingsFactory); - SettingsFactory.$inject = ['$q', '$amRoot', 'utils', 'pdbConfig']; + SettingsFactory.$inject = ['$q', '$rootScope', 'pdbMain']; - function SettingsFactory($q, $amRoot, utils, pdbConfig) { + function SettingsFactory($q, $rootScope, pdbMain) { var factory = { getDefaultServiceType: getDefaultServiceType, saveDefaultServiceType: saveDefaultServiceType, @@ -26,13 +26,9 @@ function getCurrencySymbol() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.currency) tracker.resolve(res.currency); @@ -47,25 +43,22 @@ function saveCurrencySymbol(currency) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsObject); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { res.currency = currency; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsObject(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, currency: currency } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -79,13 +72,9 @@ function getDefaultServiceType() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.settings && res.settings.servicestate) tracker.resolve(res.settings.servicestate); @@ -106,29 +95,26 @@ function saveDefaultServiceType(servicestate) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(writeSettingsObject); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - function getSettingsObject(res) { if (!res.settings) res.settings = {}; res.settings.servicestate = servicestate; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeSettingsObject(err) { var doc = { - _id: utils.generateUUID('sttngs'), - creator: $amRoot.username, + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel, settings: { servicestate: servicestate } } - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 07164dbf..7736d37e 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -149,33 +149,6 @@ - - - - {{vm.label_login}} as {{vm.user.username}} - - - -
- - person_pin - - - - - vpn_key - - - -
-
- - send - Login - -
-
-
diff --git a/src/app/components/treatments/treatments.factory.js b/src/app/components/treatments/treatments.factory.js index e130d0ce..094ff8f6 100644 --- a/src/app/components/treatments/treatments.factory.js +++ b/src/app/components/treatments/treatments.factory.js @@ -11,9 +11,9 @@ angular.module('automintApp') .factory('amTreatments', TreatmentsFactory); - TreatmentsFactory.$inject = ['$q', '$filter', '$amRoot', 'utils', 'pdbConfig']; + TreatmentsFactory.$inject = ['$q', '$filter', '$rootScope', 'pdbMain']; - function TreatmentsFactory($q, $filter, $amRoot, utils, pdbConfig) { + function TreatmentsFactory($q, $filter, $rootScope, pdbMain) { // initialized factory and function maps var factory = { saveTreatment: saveTreatment, @@ -39,13 +39,9 @@ function getCurrencySymbol() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(getSettingsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObject).catch(failure); return tracker.promise; - function getSettingsDoc(res) { - pdbConfig.get($amRoot.docIds.settings).then(getSettingsObject).catch(failure); - } - function getSettingsObject(res) { if (res.currency) tracker.resolve(res.currency); @@ -61,13 +57,9 @@ // retrieve vehicle types from config database function getVehicleTypes() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(configFound).catch(noConfigFound); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(settingsDocFound).catch(noConfigFound); return tracker.promise; - function configFound(res) { - pdbConfig.get($amRoot.docIds.settings).then(settingsDocFound).catch(noConfigFound); - } - function settingsDocFound(res) { if (res.vehicletypes) tracker.resolve(res.vehicletypes); @@ -83,13 +75,9 @@ // store vehicle types to config database function saveVehicleTypes(vehicleTypes) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(configFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(settingsDocFound).catch(writeNewConfig); return tracker.promise; - function configFound(res) { - pdbConfig.get($amRoot.docIds.settings).then(settingsDocFound).catch(writeNewConfig); - } - function settingsDocFound(res) { var totalMatch = 0; if (!res.vehicletypes) @@ -106,15 +94,17 @@ if (totalMatch == vehicleTypes.length) tracker.resolve('Duplicate Entries'); else - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeNewConfig(err) { - var doc = {}; - doc['_id'] = utils.generateUUID('sttngs'); - doc['creator'] = $amRoot.username; + var doc = { + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel + }; doc.vehicletypes = vehicleTypes; - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -129,12 +119,9 @@ // return particular treatment function treatmentDetails(treatmentName) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(treatmentConfigFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(failure); return tracker.promise; - - function treatmentConfigFound(response) { - pdbConfig.get($amRoot.docIds.treatment).then(treatmentDocFound).catch(failure); - } + function treatmentDocFound(res) { var treatment = { name: treatmentName, @@ -142,6 +129,7 @@ } tracker.resolve(treatment); } + function failure(error) { tracker.reject(error); } @@ -154,13 +142,9 @@ treatments: [], total: 0 }; - $amRoot.isTreatmentId().then(treatmentConfigFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(failure); return tracker.promise; - function treatmentConfigFound(res) { - pdbConfig.get($amRoot.docIds.treatment).then(treatmentDocFound).catch(failure); - } - function treatmentDocFound(res) { if (res.regular) Object.keys(res.regular).forEach(iterateRegularTreatments); @@ -184,16 +168,12 @@ // delete treatment from database function deleteTreatment(name) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(treatmentConfigFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(failure); return tracker.promise; - function treatmentConfigFound(res) { - pdbConfig.get($amRoot.docIds.treatment).then(treatmentDocFound).catch(failure); - } - function treatmentDocFound(res) { res.regular[name]._deleted = true; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function success(res) { @@ -211,29 +191,27 @@ var i = { rate: treatment.rate }; - $amRoot.isTreatmentId().then(treatmentConfigFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(noTreatmentDoc); return tracker.promise; - function treatmentConfigFound(response) { - pdbConfig.get($amRoot.docIds.treatment).then(treatmentDocFound).catch(noTreatmentDoc); - } - function treatmentDocFound(res) { if (!res.regular) res.regular = {}; if (res.regular[treatment.name]) delete res.regular[treatment.name]['._deleted']; res.regular[treatment.name] = i; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function noTreatmentDoc(err) { - var doc = {}; - doc['_id'] = utils.generateUUID('trtmnt'); - doc['creator'] = $amRoot.username; + var doc = { + _id: $rootScope.amGlobals.configDocIds.treatment, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel + }; doc.regular = {}; doc.regular[treatment.name] = i; - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -248,12 +226,9 @@ // current treatment settings function getTreatmentSettings() { var tracker = $q.defer(); - $amRoot.isSettingsId().then(treatmentConfigFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(treatmentDocFound).catch(failure); return tracker.promise; - function treatmentConfigFound(response) { - pdbConfig.get($amRoot.docIds.settings).then(treatmentDocFound).catch(failure); - } function treatmentDocFound(res) { if (res.settings.treatments) tracker.resolve(res.settings.treatments); @@ -263,6 +238,7 @@ }); } } + function failure(error) { tracker.reject(error); } @@ -271,33 +247,34 @@ // change display format setting of treatments function changeDisplayAsList(displayAsList) { var tracker = $q.defer(); - $amRoot.isSettingsId().then(configFound).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(configDocFound).catch(writeNewConfig); return tracker.promise; - function configFound(response) { - pdbConfig.get($amRoot.docIds.settings).then(configDocFound).catch(writeNewConfig); - } function configDocFound(res) { - if (!res.settings) res.settings = {}; if (!res.settings.treatments) res.settings.treatments = {}; res.settings.treatments['displayAsList'] = displayAsList; - pdbConfig.save(res).then(saveSuccess).catch(failure); + pdbMain.save(res).then(saveSuccess).catch(failure); } + function writeNewConfig(err) { - var doc = {}; - doc['_id'] = utils.generateUUID('sttngs'); - doc['creator'] = $amRoot.username; + var doc = { + _id: $rootScope.amGlobals.configDocIds.settings, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel + }; doc.settings = {}; doc.settings['treatments'] = {} doc.settings.treatments['displayAsList'] = displayAsList; - pdbConfig.save(doc).then(saveSuccess).catch(failure); + pdbMain.save(doc).then(saveSuccess).catch(failure); } + function saveSuccess(response) { tracker.resolve(response); } + function failure(error) { tracker.reject(error); } @@ -305,13 +282,9 @@ function getPackageInfo(name) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.packages && res.packages[name]) { tracker.resolve({ @@ -339,13 +312,9 @@ packages: [], total: 0 } - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.packages) Object.keys(res.packages).forEach(iteratePackages); @@ -369,17 +338,13 @@ function deletePackage(name) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.packages && res.packages[name]) { res.packages[name]._deleted = true; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } else failure(); } @@ -400,13 +365,9 @@ function savePackage(package, oldName) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(writeTreatmentDoc); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(writeTreatmentDoc); - } - function getTreatmentObject(res) { if (!res.packages) res.packages = {}; @@ -414,18 +375,19 @@ delete res.packages[oldName]; res.packages[package.name] = package; delete res.packages[package.name].name; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeTreatmentDoc(err) { var doc = { - _id: utils.generateUUID('trtmnt'), - creator: $amRoot.username + _id: $rootScope.amGlobals.configDocIds.treatment, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel } doc.packages = {}; doc.packages[package.name] = package; delete doc.packages[package.name].name; - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { @@ -439,13 +401,9 @@ function getMembershipInfo(name) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.memberships && res.memberships[name]) { var m = $.extend({}, res.memberships[name]); @@ -483,13 +441,9 @@ memberships: [], total: 0 } - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.memberships) Object.keys(res.memberships).forEach(iterateMemberships); @@ -519,17 +473,13 @@ function deleteMembership(name) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(failure); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(failure); - } - function getTreatmentObject(res) { if (res.memberships && res.memberships[name]) { res.memberships[name]._deleted = true; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } else failure(); } @@ -551,13 +501,9 @@ function saveMembership(membership, oldName) { var tracker = $q.defer(); - $amRoot.isTreatmentId().then(getTreatmentsDoc).catch(failure); + pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(getTreatmentObject).catch(writeTreatmentDoc); return tracker.promise; - function getTreatmentsDoc(res) { - pdbConfig.get($amRoot.docIds.treatment).then(getTreatmentObject).catch(writeTreatmentDoc); - } - function getTreatmentObject(res) { if (!res.memberships) res.memberships = {}; @@ -565,18 +511,19 @@ delete res.memberships[oldName]; res.memberships[membership.name] = membership; delete res.memberships[membership.name].name; - pdbConfig.save(res).then(success).catch(failure); + pdbMain.save(res).then(success).catch(failure); } function writeTreatmentDoc(err) { var doc = { - _id: utils.generateUUID('trtmnt'), - creator: $amRoot.username + _id: $rootScope.amGlobals.configDocIds.treatment, + creator: $rootScope.amGlobals.creator, + channel: $rootScope.amGlobals.channel } doc.memberships = {}; doc.memberships[membership.name] = membership; delete doc.memberships[membership.name].name; - pdbConfig.save(doc).then(success).catch(failure); + pdbMain.save(doc).then(success).catch(failure); } function success(res) { diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js new file mode 100644 index 00000000..fd2536d0 --- /dev/null +++ b/src/app/login/login.controller.js @@ -0,0 +1,122 @@ +/** + * Controller for Login Compoenent + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + var base64url = require('base64url'); + + angular.module('automintApp').controller('amLoginCtrl', LoginController); + + LoginController.$inject = ['$rootScope', '$amRoot', '$location', 'amLogin']; + + function LoginController($rootScope, $amRoot, $location, amLogin) { + // initialize view model + var vm = this; + + // named assignments for view model + vm.isLogingIn = false; + vm.username = ''; + vm.password = ''; + vm.code = ''; + vm.message = undefined; + + // named assignments for other scopes + $rootScope.hidePreloader = true; + + // function mappings + vm.changeCode = changeCode; + vm.changeUserDetails = changeUserDetails; + vm.submit = submit; + + // default execution steps + setTimeout(focusUsername, 300); + + // function definitions + + function focusUsername() { + $('#ami-username').focus(); + } + + function focusPassword() { + $('#ami-password').focus(); + } + + function focusCode() { + $('#ami-code').focus(); + } + + function changeCode() { + vm.password = ''; + vm.message = undefined; + } + + function changeUserDetails() { + vm.code = ''; + vm.message = undefined; + } + + function submit() { + vm.message = undefined; + if ((vm.username == '') && (vm.password == '') && (vm.code == '')) { + vm.message = 'Please Enter Username and Password, or Code!'; + return; + } + if (((vm.username != '') && (vm.password == '')) && (vm.code == '')) { + vm.message = 'Please Enter Password!'; + return; + } + if (((vm.password != '') && (vm.username == '')) && (vm.code == '')) { + vm.message = 'Please Enter Username!'; + return; + } + vm.isLogingIn = true; + amLogin.loadCredentials(vm.username, vm.password); + amLogin.login(success, failure); + + function success(res) { + if (res.data && (res.ok == true)) { + if (res.data.userCtx && (res.data.userCtx.name != null) && res.data.userCtx.channels && (Object.keys(res.data.userCtx.channels).length > 0)) { + if (res.data.userCtx.channels.length <= 1) { + failure(); + return; + } + amLogin.saveLoginCredentials(true, vm.username, vm.password, res.data.userCtx.channels[1]).then(proceed).catch(docNotSaved); + } + } + + function proceed(res) { + $amRoot.dbAfterLogin(); + $location.path('/'); + } + + function docNotSaved(err) { + console.error(err); + failure(); + } + } + + function failure(err) { + if (!err) { + vm.message = "Something went wrong! Please contact Automint Care!"; + vm.isLogingIn = false; + return; + } + switch (err.status) { + case 401: + vm.message = "Invalid Username/Password!"; + break; + default: + vm.message = "Please check your internet connectivity!"; + break; + } + vm.isLogingIn = false; + console.error(err); + } + } + } +})(); \ No newline at end of file diff --git a/src/app/login/login.factory.js b/src/app/login/login.factory.js new file mode 100644 index 00000000..51dfbc3d --- /dev/null +++ b/src/app/login/login.factory.js @@ -0,0 +1,204 @@ +/** + * Factories for Login Mechanism + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').factory('amLogin', LoginFactory).factory('amLoginBase64', LoginBase64Factory); + + LoginBase64Factory.$inject = []; + LoginFactory.$inject = ['$rootScope', '$http', '$q', 'amLoginBase64', 'amFactory', 'constants', 'pdbLocal', 'pdbMain']; + + /* + === NOTE === + Do not include $amRoot as dependency as this module is used in it. + */ + + function LoginFactory($rootScope, $http, $q, amLoginBase64, amFactory, constants, pdbLocal, pdbMain) { + // initialize factory and functino mappings + var factory = { + loadCredentials: loadCredentials, + login: login, + saveLoginCredentials: saveLoginCredentials + } + + return factory; + + // function definitions + + function loadCredentials(username, password) { + if (!$rootScope.amGlobals) + $rootScope.amGlobals = {}; + $rootScope.amGlobals.authHeaderData = amLoginBase64.encode(username + ':' + password); + } + + function login(success, failure) { + $http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.amGlobals.authHeaderData; + $http.get(amFactory.generateAuthUrl(constants.sgw_w_main)).then(success, failure); + } + + function saveLoginCredentials(isLoggedIn, username, password, channel) { + var tracker = $q.defer(); + if ($rootScope.amGlobals == undefined) + $rootScope.amGlobals = {}; + $rootScope.amGlobals.creator = username; + $rootScope.amGlobals.channel = channel; + pdbLocal.get('login').then(getLoginDoc).catch(writeLoginDoc); + return tracker.promise; + + function getLoginDoc(res) { + res.isLoggedIn = isLoggedIn; + res.username = username; + res.password = password; + res.channel = channel; + pdbLocal.save(res).then(success).catch(failure); + } + + function writeLoginDoc(err) { + var doc = { + _id: 'login', + isLoggedIn: isLoggedIn, + username : username, + password: password, + channel: channel + } + pdbLocal.save(doc).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function changeExistingDocs() { + var tracker = $q.defer() + if (!$rootScope.amGlobals || ($rootScope.amGlobals && !$rootScope.amGlobals.creator) || ($rootScope.amGlobals && $rootScope.amGlobals.channel)) + return 404; + pdbMain.getAll().then(getAllDocs).catch(failure); + return tracker.promise; + + function getAllDocs(res) { + var docsToSave = []; + res.rows.forEach(iterateRows); + + function iterateRows(row) { + switch (row.id) { + case 'settings': + convertSettingsDoc(row); + break; + default: + convertUserDoc(row); + break; + } + + function convertUserDoc(row) { + console.log(row); + } + } + } + + function failure(err) { + console.error(err); + } + } + } + + function LoginBase64Factory() { + // named assignments + var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + // factory object and function mappings + var factory = { + encode: encode, + decode: decode + } + + return factory; + + // function definitions + + function encode(input) { + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + + keyStr.charAt(enc4); + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + } while (i < input.length); + + return output; + } + + function decode(input) { + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + var base64test = /[^A-Za-z0-9\+\/\=]/g; + if (base64test.exec(input)) { + alert("There were invalid base64 characters in the input text.\n" + + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + + "Expect errors in decoding."); + } + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + + } while (i < input.length); + + return output; + } + } +})(); \ No newline at end of file diff --git a/src/app/login/login.html b/src/app/login/login.html new file mode 100644 index 00000000..07175153 --- /dev/null +++ b/src/app/login/login.html @@ -0,0 +1,70 @@ + + + + + \ No newline at end of file diff --git a/src/app/temp/app.service.js b/src/app/temp/app.service.js new file mode 100644 index 00000000..ce9f81ed --- /dev/null +++ b/src/app/temp/app.service.js @@ -0,0 +1,577 @@ +/* + * Closure for root level service + * @author ndkcha + * @since 0.4.1 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp') + .service('$amRoot', AutomintService); + + AutomintService.$inject = ['$rootScope', '$state', '$q', '$log', 'utils', 'constants', 'pdbCustomers', 'pdbConfig', 'pdbCommon', 'amFactory', 'pdbCache']; + + function AutomintService($rootScope, $state, $q, $log, utils, constants, pdbCustomers, pdbConfig, pdbCommon, amFactory, pdbCache) { + // set up service object + var sVm = this; + var blockViews = true; + + // keep track of current configuration of application + sVm.docIds = {}; + sVm.username = ''; + + // map functions + sVm.initDb = initDb; + sVm.syncDb = syncDb; + sVm.ccViews = ccViews; + sVm.isWorkshopId = isWorkshopId; + sVm.isTreatmentId = isTreatmentId; + sVm.isSettingsId = isSettingsId; + sVm.isInventoryId = isInventoryId; + sVm.updateConfigReferences = updateConfigReferences; + + // named assignments + var successResponse = { + success: true + } + var failureResponse = { + success: false + } + + // initialize databases + function initDb() { + // setup local databases + pdbConfig.setDatabase(constants.pdb_w_config); + pdbCustomers.setDatabase(constants.pdb_w_customers); + pdbCommon.setDatabase(constants.pdb_common); + pdbCache.setDatabase(constants.pdb_cache); + + // check and create views + manageDbVersions().then(ccViews).catch(ccViews); + + // listen to changes in local db + OnCustomerDbChanged(); + OnConfigDbChanged(); + + // setup server iteraction + // oneWayReplication(); + // syncDb(); + } + + function manageDbVersions() { + var tracker = $q.defer(); + isSettingsId().then(getSettingsDoc).catch(failure); + return tracker.promise; + + function getSettingsDoc(res) { + pdbConfig.get(sVm.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); + } + + function getSettingsObject(res) { + if (!res.dbversion) + res.dbversion = 0; + switch (res.dbversion) { + case 0: + applyPatch1().then(proceed).catch(proceed); + break; + } + + function proceed(res) { + res.dbversion = constants.db_version; + pdbConfig.save(res).then(success).catch(failure); + } + } + + function writeSettingsObject(err) { + var doc = { + _id: utils.generateUUID('sttngs'), + creator: sVm.username, + dbversion: constants.db_version + } + applyPatch1().then(proceed).catch(proceed); + + function proceed(res) { + pdbConfig.save(doc).then(success).catch(failure); + } + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function applyPatch1() { + var tracker = $q.defer(); + pdbCustomers.getAll().then(success).catch(failure); + return tracker.promise; + + function success(res) { + var customers = []; + res.rows.forEach(iterateRows); + pdbCustomers.saveAll(customers).then(saveSuccess).catch(failure); + + function iterateRows(row) { + if (row.doc && row.doc.user && row.doc.user.vehicles) { + Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); + customers.push(row.doc); + } + + function iterateVehicles(vId) { + if (row.doc.user.vehicles[vId].services) + Object.keys(row.doc.user.vehicles[vId].services).forEach(iterateServices); + + function iterateServices(sId) { + if (row.doc.user.vehicles[vId].services[sId].status == 'billed') + row.doc.user.vehicles[vId].services[sId].status = 'due'; + if (row.doc.user.vehicles[vId].services[sId].state == undefined || row.doc.user.vehicles[vId].services[sId].state == '') + row.doc.user.vehicles[vId].services[sId].state = 'Bill'; + } + } + } + } + + function saveSuccess(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function ccViews(force) { + // service module + + pdbCustomers.getAll().then(success).catch(failure); + + // view service + function success(res) { + pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); + pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(viewvehiclessuccess).catch(viewvehiclessuccess); + + function viewvehiclessuccess(vvcdoc) { + var vdocToSave = {}; + res.rows.forEach(iterateRows); + vdocToSave._id = constants.pdb_cache_views.view_next_due_vehicles; + if (vvcdoc._rev) + vdocToSave._rev = vvcdoc._rev; + pdbCache.save(vdocToSave); + + function iterateRows(row) { + if (row.doc.user.vehicles) + Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); + + function iterateVehicles(vId) { + var vehicle = row.doc.user.vehicles[vId]; + if (!vehicle.nextdue) + return; + var cd = moment(vehicle.nextdue).format('MMM YYYY'); + if (vdocToSave[cd] == undefined) + vdocToSave[cd] = {}; + vdocToSave[cd][vId] = { + cstmr_id: row.id, + cstmr_name: row.doc.user.name, + cstmr_mobile: row.doc.user.mobile, + vhcl_reg: vehicle.reg, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_nextdue: vehicle.nextdue + } + } + } + } + + function vsuv(cachedoc) { + var docsToSave = {}, isChanged = false; + res.rows.forEach(iterateRows); + docsToSave._id = constants.pdb_cache_views.view_services; + if (cachedoc._rev) + docsToSave._rev = cachedoc._rev; + pdbCache.save(docsToSave); + + function iterateRows(row) { + if (row.doc.user.vehicles) + Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); + + function iterateVehicles(vId) { + var vehicle = row.doc.user.vehicles[vId]; + if (vehicle.services) + Object.keys(vehicle.services).forEach(iterateServices); + + function iterateServices(sId) { + var service = vehicle.services[sId]; + var cd = moment(service.date).format('MMM YYYY'); + var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); + cd = angular.lowercase(cd).replace(' ', '-'); + if (service._deleted == true) { + if (cachedoc[cd][sId] != undefined) + isChanged = true; + return; + } + if (docsToSave[cd] == undefined) + docsToSave[cd] = {}; + docsToSave[cd][sId] = { + cstmr_id: row.id, + cstmr_name: row.doc.user.name, + cstmr_mobile: row.doc.user.mobile, + vhcl_id: vId, + vhcl_reg: vehicle.reg, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_nextdue: vehicle.nextdue, + srvc_date: service.date, + srvc_cost: service.cost, + srvc_status: service.status, + srvc_state: service.state, + srvc_payreceived: payreceived + }; + } + } + } + } + } + + function failure(err) { + console.warn(err); + } + } + + function OnCustomerDbChanged() { + pdbCustomers.OnDbChanged({ + since: 'now', + live: true + }).on('change', onChange).on('complete', onComplete).on('error', onError); + + function onChange(change) { + if (change.deleted == true) { + ccViews(); + return; + } + var curdoc; + + pdbCustomers.get(change.id, { + revs_info: true + }).then(getCurrentVersion); + + function getCurrentVersion(cdoc) { + curdoc = cdoc; + if (cdoc._revs_info.length > 1) { + pdbCustomers.get(change.id, { + rev: cdoc._revs_info[1].rev + }).then(getLastVersion); + } else + getLastVersion(); + } + + function getLastVersion(ldoc) { + pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); + pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(vndvs).catch(vndvs); + + function vndvs(cachedoc) { + if (cachedoc.error == true) { + cachedoc = { + _id: constants.pdb_cache_views.view_services + } + } + if (curdoc.user.vehicles) + Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); + pdbCache.save(cachedoc); + + function iterateVehicles(vId) { + var vehicle = curdoc.user.vehicles[vId]; + var lastVehicle = undefined, lvcd = undefined; + if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId]) + lastVehicle = ldoc.user.vehicles[vId]; + if (lastVehicle && lastVehicle.nextdue) + lvcd = moment(lastVehicle.nextdue).format('MMM YYYY'); + if (!vehicle.nextdue) { + if (lastVehicle) { + if (lastVehicle.nextdue) { + if (lvcd && cachedoc[lvcd] && cachedoc[lvcd][vId]) + delete cachedoc[lvcd][vId]; + } + } + return; + } + var cd = moment(vehicle.nextdue).format('MMM YYYY'); + if (lastVehicle && lvcd) { + if ((cachedoc[lvcd] != undefined) && (cachedoc[lvcd][vId] != undefined)) + delete cachedoc[lvcd][vId]; + } + if (cachedoc[cd] == undefined) + cachedoc[cd] = {}; + cachedoc[cd][vId] = { + cstmr_id: change.id, + cstmr_name: curdoc.user.name, + cstmr_mobile: curdoc.user.mobile, + vhcl_reg: vehicle.reg, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_nextdue: vehicle.nextdue, + } + } + } + + function vsuv(cachedoc) { + if (cachedoc.error == true) { + cachedoc = { + _id: constants.pdb_cache_views.view_services + } + } + + if (curdoc.user.vehicles) + Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); + pdbCache.save(cachedoc); + + function iterateVehicles(vId) { + var vehicle = curdoc.user.vehicles[vId]; + if (vehicle.services) + Object.keys(vehicle.services).forEach(iterateServices); + + function iterateServices(sId) { + var service = vehicle.services[sId]; + var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); + var cd = moment(service.date).format('MMM YYYY'); + cd = angular.lowercase(cd).replace(' ', '-'); + if (service._deleted == true) { + if (cachedoc[cd] && cachedoc[cd][sId]) + delete cachedoc[cd][sId]; + return; + } + if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId] && ldoc.user.vehicles[vId].services && ldoc.user.vehicles[vId].services[sId]) { + var lvd = moment(ldoc.user.vehicles[vId].services[sId].date).format('MMM YYYY'); + lvd = angular.lowercase(lvd).replace(' ', '-'); + if (cachedoc[lvd][sId] != undefined) + delete cachedoc[lvd][sId]; + } + if (cachedoc[cd] == undefined) + cachedoc[cd] = {}; + cachedoc[cd][sId] = { + cstmr_id: change.id, + cstmr_name: curdoc.user.name, + cstmr_mobile: curdoc.user.mobile, + vhcl_id: vId, + vhcl_reg: vehicle.reg, + vhcl_manuf: vehicle.manuf, + vhcl_model: vehicle.model, + vhcl_nextdue: vehicle.nextdue, + srvc_date: service.date, + srvc_cost: service.cost, + srvc_status: service.status, + srvc_state: service.state, + srvc_payreceived: payreceived + }; + } + } + } + } + } + + function onComplete(info) { + // console.log(info); + } + + function onError(error) { + // console.log(error); + } + } + + function OnConfigDbChanged() { + pdbConfig.OnDbChanged({ + since: 'now', + live: true, + include_docs: true + }).on('change', onChange).on('complete', onComplete).on('error', onError); + + function onChange(change) { + // console.log(change); + } + + function onComplete(info) { + // console.log(info); + } + + function onError(error) { + // console.log(error); + } + } + + function updateConfigReferences() { + var tracker = $q.defer(); + pdbConfig.getAll().then(successQuery).catch(failedQuery); + return tracker.promise; + + // if promise returns with all documents, update configurations + function successQuery(res) { + res.rows.forEach(iterateDocuments); + tracker.resolve(successResponse); + + // iterate through documents to match id(s) of documents + function iterateDocuments(element) { + if (element.id.match(/\btrtmnt-/i)) + sVm.docIds.treatment = element.id; + if (element.id.match(/\bwrkshp-/i)) + sVm.docIds.workshop = element.id; + if (element.id.match(/\bsttngs-/i)) + sVm.docIds.settings = element.id; + if (element.id.match(/\binvntry-/i)) + sVm.docIds.inventory = element.id; + } + } + + // if promise returns with error + function failedQuery(err) { + tracker.reject(failureResponse); + } + } + + // check if database is syncable to remote + function isSyncable() { + var tracker = $q.defer(); + var workshopUser = $.extend({}, successResponse); + + isWorkshopId().then(setCredentials).catch(noWorkshopUser); + return tracker.promise; + + // if workshop document is tracker, look for username and password inside document + function setCredentials(res) { + pdbConfig.get(sVm.docIds.workshop).then(setWorkshopUser).catch(noWorkshopUser); + + // if workshop users existing, return with username and password + function setWorkshopUser(res) { + if (res.user) { + workshopUser.username = res.user.username; + workshopUser.password = res.user.password; + tracker.resolve(workshopUser); + } else + noWorkshopUser(); + } + } + + // if no workshop, return with failed response bool + function noWorkshopUser(error) { + tracker.reject(failureResponse); + } + } + + // setup sync (bidirectional replication) + function syncDb() { + isSyncable().then().catch(); + + // if sync details found + function runSync(res) { + if (res.success) { + sVm.username = res.username; + // construct database url for workshop configuration and customers' db + dbConfigUrl = amFactory.generateDbUrl(res.username, res.password, constants.sgw_w_config); + dbCustomersUrl = amFactory.generateDbUrl(res.username, res.password, constants.sgw_w_customers); + + // sync database + pdbConfig.sync(dbConfigUrl) + .on('change', onChangedDb) + .on('paused', onPausedDb) + .on('active', onActiveDb) + .on('denied', onDeniedDb) + .on('complete', onCompleteDb) + .on('error', onErrorDb); + + pdbCustomers.sync(dbCustomersUrl) + .on('change', onChangedDb) + .on('paused', onPausedDb) + .on('active', onActiveDb) + .on('denied', onDeniedDb) + .on('complete', onCompleteDb) + .on('error', onErrorDb); + } + } + + // no sync details found + function noSync(error) { + $log.debug('cannot sync at moment! no sync details found'); + } + } + + // setup one-way replication + function oneWayReplication() { + // setup common database replication + pdbCommon.replicate(constants.db_url + constants.sgw_common) + .on('change', onChangedDb) + .on('paused', onPausedDb) + .on('active', onActiveDb) + .on('denied', onDeniedDb) + .on('complete', onCompleteDb) + .on('error', onErrorDb); + } + + // check if treatment's document id is loaded to current docId object + function isTreatmentId() { + return isDocId('trtmnt'); + } + + // check if workshop's document id is loaded to current docId object + function isWorkshopId() { + return isDocId('wrkshp'); + } + + // check if settings' document id is loaded to current docId object + function isSettingsId() { + return isDocId('sttngs'); + } + + // check if inventory's document id is loaded to current docId object + function isInventoryId() { + return isDocId('invntry'); + } + + // the check function + function isDocId(query) { + var tracker = $q.defer(); + if (sVm.docIds[query] == '' || sVm.docIds[query] == undefined) + sVm.updateConfigReferences().then(configUpdated).catch(updateFailed); + else + updateFailed(); + return tracker.promise; + + function configUpdated(res) { + tracker.resolve(successResponse); + } + + function updateFailed(err) { + tracker.resolve(failureResponse); + } + } + + // database listeners + + function onChangedDb(info) { + // listen to on change event + } + + function onPausedDb(err) { + // listen to on pause event\ + } + + function onActiveDb() { + // listen to on active event + } + + function onDeniedDb(err) { + // listen to on denied event + } + + function onCompleteDb(info) { + // listen to on complete event + updateConfigReferences(); + } + + function onErrorDb(err) { + // listen to on error event + } + } +})(); \ No newline at end of file diff --git a/src/index.html b/src/index.html index 97a29724..5129d4c0 100644 --- a/src/index.html +++ b/src/index.html @@ -49,6 +49,7 @@ + @@ -63,12 +64,12 @@
- +
- +
-
+
From 8225c569db6bd5af197f3f82866f71e40539e217 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 11 Aug 2016 15:09:23 +0530 Subject: [PATCH 60/91] Fixes in #202 #158 #143 202. It should not show (Any) in vehicle column in Service List page when vehicle is not specified in service. 158. It's not proper when enabling margins. 143. make it : "Restarting App. Please Wait.." --- src/app/components/invoices/invoices_view.html | 5 +++-- src/app/components/services/services-edit.controller.js | 7 +++++-- src/app/components/settings/settings.html | 4 +++- src/index.html | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index eaf1da99..d8bbb959 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -146,7 +146,8 @@ -
+
+
 
@@ -281,9 +282,9 @@

{{vm.workshop.name}}

Page {{vm.currentPage(page.index)}} of {{vm.pages.length}}
+
 
-
diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 7e24c396..238300f3 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -1498,7 +1498,7 @@ function success(res) { autofillVehicle = true; vm.user.id = $state.params.userId; - vm.user.name = res.name; + vm.user.name = (res.name == 'Anonymous') ? '' : res.name; vm.user.email = res.email; vm.user.mobile = res.mobile; userMobile = res.mobile; @@ -1506,7 +1506,7 @@ if (res.memberships) Object.keys(res.memberships).forEach(iterateMemberships); vm.vehicle.id = $state.params.vehicleId; - vm.vehicle.reg = res.vehicle.reg; + vm.vehicle.reg = (res.vehicle.reg == 'Vehicle') ? '' : res.vehicle.reg; vm.vehicle.manuf = res.vehicle.manuf; vm.vehicle.model = res.vehicle.model; vm.vehicle.type = res.vehicle.type; @@ -2093,6 +2093,9 @@ } function validate() { + if (vm.user.name == '') { + vm.user.name = 'Anonymous'; + } var isVehicleBlank = (vm.vehicle.manuf == undefined || vm.vehicle.manuf == '') && (vm.vehicle.model == undefined || vm.vehicle.model == '') && (vm.vehicle.reg == undefined || vm.vehicle.reg == ''); if (isVehicleBlank) { diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 7736d37e..9faa983d 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -168,7 +168,9 @@
App Data Location - : {{vm.amAppPath}} +
+
+ {{vm.amAppPath}}
diff --git a/src/index.html b/src/index.html index 5129d4c0..f6167670 100644 --- a/src/index.html +++ b/src/index.html @@ -57,7 +57,7 @@
- Restarting App + Restarting App. Please Wait..
From c9a76a7e818e8e9b11f7cae9d9ca5f750cb393dc Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 11 Aug 2016 15:16:23 +0530 Subject: [PATCH 61/91] Bug Fix #196 --- src/app/components/services/services.factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index cadf2fb1..1c5f457d 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -208,7 +208,7 @@ tracker.resolve(response); function iterateInventories(name) { - if (name.match(/\b_id|\b_rev|\bcreator/i) || res[name]._deleted == true) + if (name.match(/\b_id|\b_rev|\bcreator|\bchannel/i) || res[name]._deleted == true) return; var temp = res[name]; temp.name = name; From 73fd4dd77766f23a139d1a15239fb7384121ea27 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 11 Aug 2016 15:22:09 +0530 Subject: [PATCH 62/91] Enhanceent #205 | Lead Customer to Normal Customer 205. When 'Lead' customer has a service entry, then turn the type to 'Customer', in the profile. --- src/app/components/services/services.factory.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 1c5f457d..6589e48e 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -703,6 +703,8 @@ pdbMain.save(res).then(success).catch(failure); function iterateUserFields(ufn) { + if (ufn == 'type') + newUser[ufn] = (res.user[ufn] == 'Lead') ? 'Customer' : newUser[ufn]; res.user[ufn] = newUser[ufn]; } From 59dcb8fe0828561bdf5cf086fb322fde269994d6 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 11 Aug 2016 20:37:08 +0530 Subject: [PATCH 63/91] New Lockscreen Mechanism as per new Architecture --- src/app/app.controller.js | 247 ++++-------------- src/app/app.factory.js | 28 +- src/app/app.service.js | 56 +--- src/app/app.states.js | 44 ++-- src/app/appbar/appbar.factory.js | 39 +++ src/app/appbar/headerbar.controller.js | 76 ++++++ .../{headerView.html => headerbar.html} | 0 src/app/appbar/sidebar.controller.js | 91 +++++++ .../appbar/{sidebarView.html => sidebar.html} | 0 .../services/services-add.controller.js | 8 +- src/app/lock/lockscreen.controller.js | 57 ++++ src/app/lock/lockscreen.html | 67 +++++ src/app/login/login.controller.js | 16 +- src/app/login/login.factory.js | 4 +- src/app/views/initializing.html | 1 + src/app/views/lockscreen.html | 77 ------ src/assets/css/automint.css | 9 +- src/index.html | 4 +- 18 files changed, 441 insertions(+), 383 deletions(-) create mode 100644 src/app/appbar/appbar.factory.js create mode 100644 src/app/appbar/headerbar.controller.js rename src/app/appbar/{headerView.html => headerbar.html} (100%) create mode 100644 src/app/appbar/sidebar.controller.js rename src/app/appbar/{sidebarView.html => sidebar.html} (100%) create mode 100644 src/app/lock/lockscreen.controller.js create mode 100644 src/app/lock/lockscreen.html create mode 100644 src/app/views/initializing.html delete mode 100644 src/app/views/lockscreen.html diff --git a/src/app/app.controller.js b/src/app/app.controller.js index a4662c9a..fdf96aa8 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -8,217 +8,70 @@ /// (function() { - const electron = require('electron').remote; - const ammHelp = require('./automint_modules/am-help.js'); - const ipcRenderer = require("electron").ipcRenderer; - const amApp = electron.app; - const BrowserWindow = electron.BrowserWindow; - angular.module('automintApp') - .controller('lockScreenCtrl', LockScreenController) - .controller('appBarHeaderCtrl', HeaderbarController) - .controller('appSideBarCtrl', SidebarController); + angular.module('automintApp').controller('amCtrl', HomeController); - HeaderbarController.$inject = ['$rootScope', '$scope', '$state', '$timeout', '$mdSidenav', 'amRootFactory']; - SidebarController.$inject = ['$rootScope', '$scope', '$state', '$http', '$mdSidenav', 'amRootFactory']; - LockScreenController.$inject = ['$rootScope', '$state', '$window', 'amRootFactory', 'utils']; + HomeController.$inject = ['$rootScope', '$state', '$amRoot', 'utils', 'amLogin']; - function LockScreenController($rootScope, $state, $window, amRootFactory, utils) { - var vm = this, passcode, skip = true; - - $rootScope.cRootLock = 'active'; - - vm.unlock = unlock; - vm.isMessage = false; - vm.addService = addService; - - var source = localStorage.getItem('cover-pic'); - $('#am-lockscreen-img').attr('src', (source) ? source : 'assets/img/logo-250x125px.png').width(250).height(125); - amRootFactory.getPasscode().then(gps).catch(unlock); - - function addService() { - $rootScope.cRootLock = ''; - $state.go('restricted.services.add', { - fromState: 'locked' - }); - } - - function gps(res) { - if (!res || res.enabled == false) { - unlock(); - skip = true; - return; - } - skip = false; - passcode = res.code; - } - - function unlock() { - var transitState = 'restricted.dashboard'; - if (skip == false) { - if (vm.passcode != passcode) { - vm.message = "Wrong Passcode!!!"; - vm.isMessage = true; - return; - } - } - $rootScope.isAutomintLocked = false; - $rootScope.cRootLock = ''; - if ($state.params.fromState != undefined) - transitState = $state.params.fromState; - $state.go(transitState); - } - } - - function HeaderbarController($rootScope, $scope, $state, $timeout, $mdSidenav, amRootFactory) { - var vm = this; - - // map functions to view model - vm.toggleSideNavbar = buildDelayedToggler('main-nav-left'); - vm.openLockScreen = openLockScreen; - vm.openHelpWindow = openHelpWindow; - vm.addService = addService; - - // default execution steps - setCoverPic(); - amRootFactory.getPasscode().then(gps).catch(failure); - - // function definitions - - function setCoverPic() { - var source = localStorage.getItem('cover-pic'); - $('#am-cover-pic').attr('src', (source) ? source : 'assets/img/logo-250x125px.png').width(250).height(125); - } - - function addService() { - $state.go('restricted.services.add'); - } - - function openHelpWindow() { - ammHelp.openHelpWindow(); - } - - function gps(res) { - if (res == undefined) - return; - $rootScope.isPasscodeEnabled = res.enabled; - } - - function failure(err) { - $rootScope.isPasscodeEnabled = false; - } - - function openLockScreen() { - $rootScope.isAutomintLocked = true; - $state.go('locked', { - fromState: $state.current.name - }); - } - - // Supplies a function that will continue to operate until the time is up. - function debounce(func, wait, context) { - var timer; - return function debounced() { - var context = $scope, - args = Array.prototype.slice.call(arguments); - $timeout.cancel(timer); - timer = $timeout(function() { - timer = undefined; - func.apply(context, args); - }, wait || 10); - }; - } - - // Build handler to open/close a SideNav; when animation finishes report completion in console - function buildDelayedToggler(navID) { - return debounce(function() { - $mdSidenav(navID).toggle(); - }, 200); - } - } - - function SidebarController($rootScope, $scope, $state, $http, $mdSidenav, amRootFactory) { + function HomeController($rootScope, $state, $amRoot, utils, amLogin) { + // initialize view model var vm = this; - // objects passed to view model - vm.items = [{ - name: 'Dashboard', - icon: 'dashboard', - state: 'restricted.dashboard' - }, { - name: 'Customers', - icon: 'group', - state: 'restricted.customers.all' - }, { - name: 'Treatments', - icon: 'local_car_wash', - state: 'restricted.treatments.master' - }, { - name: 'Inventory', - icon: 'build', - state: 'restricted.inventory.all' - }, { - name: 'Settings', - icon: 'settings', - state: 'restricted.settings' - }]; - vm.isAutomintUpdateAvailable = undefined; - - // map functions to view model - vm.openState = openState; - vm.doUpdate = doUpdate; - vm.isSelected = isSelected; - vm.goToDashboard = goToDashboard; - // default execution steps - ipcRenderer.on('automint-updated', listenToAutomintUpdates); - getPackageFile(); - $rootScope.hidePreloader = true; - amRootFactory.getPasscode().then(gps).catch(unlock); + $amRoot.IsAutomintLoggedIn().then(checkLoginState).catch(doLogin); // function definitions - function gps(res) { - if ($rootScope.isAutomintLocked == false) - return; - if (!res || res.enabled == false) { - unlock(); - return; - } - if (res.enabled == true) { - $state.go('locked'); + function checkLoginState(res) { + if (res && res.username && res.password) { + amLogin.loadCredentials(res.username, res.password); + amLogin.login(success, failure); + } else + doLogin(); + + function success(response) { + if (response.data && (response.statusText == "OK")) { + if (response.data.userCtx && (response.data.userCtx.name != null) && response.data.userCtx.channels && (Object.keys(response.data.userCtx.channels).length > 0)) { + if (Object.keys(response.data.userCtx.channels).length > 1) { + amLogin.saveLoginCredentials(true, res.username, res.password, Object.keys(response.data.userCtx.channels)[1]).then(proceed).catch(doLogin); + return; + } + } + } + utils.showSimpleToast('Please Login Again!'); + doLogin(); } - } - function unlock() { - console.info('unlocked'); - } - - function goToDashboard() { - $mdSidenav('main-nav-left').close() - $state.go(vm.items[0].state); - } - - function isSelected(index) { - return ($rootScope.sidebarItemIndex == index); - } - - function listenToAutomintUpdates(event, arg) { - vm.isAutomintUpdateAvailable = arg; - $scope.$apply(); - } - - function doUpdate() { - ipcRenderer.send('am-quit-update', true); - } + function failure(err) { + if (err.status == -1) { + if (res.isLoggedIn == true) { + proceed(); + return; + } + } + utils.showSimpleToast('Please Login Again!'); + doLogin(); + } - function getPackageFile() { - vm.automintVersion = amApp.getVersion(); + function proceed() { + $rootScope.hidePreloader = true; + if (res.channel) + $rootScope.amGlobals.channel = res.channel; + if (res.username) + $rootScope.amGlobals.creator = res.username; + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') + } + $amRoot.dbAfterLogin(); + } } - function openState(state) { - $mdSidenav('main-nav-left').close() - $state.go(state); + function doLogin(err) { + $rootScope.hidePreloader = true; + $state.go('login'); } } })(); \ No newline at end of file diff --git a/src/app/app.factory.js b/src/app/app.factory.js index 83aafd59..a6f80948 100644 --- a/src/app/app.factory.js +++ b/src/app/app.factory.js @@ -8,35 +8,9 @@ /// (function() { - angular.module('automintApp').factory('amRootFactory', RootFactory).factory('utils', UtilsFactory); + angular.module('automintApp').factory('utils', UtilsFactory); - RootFactory.$inject = ['$q', '$rootScope', 'pdbMain']; UtilsFactory.$inject = ['$mdToast']; - - function RootFactory($q, $rootScope, pdbMain) { - // initialize factory object and map functions - var factory = { - getPasscode: getPasscode - } - - return factory; - - // function definitions - - function getPasscode() { - var tracker = $q.defer(); - pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); - return tracker.promise; - - function getSettingsObj(res) { - tracker.resolve(res.passcode); - } - - function failure(err) { - tracker.reject(err); - } - } - } function UtilsFactory($mdToast) { // temporary named assignments diff --git a/src/app/app.service.js b/src/app/app.service.js index 51b7fa1e..ddf81427 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').service('$amRoot', AutomintService); - AutomintService.$inject = ['$rootScope', '$state', '$q', 'utils', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory', 'amLogin']; + AutomintService.$inject = ['$rootScope', '$state', '$location', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal']; - function AutomintService($rootScope, $state, $q, utils, constants, pdbMain, pdbCache, pdbLocal, amFactory, amLogin) { + function AutomintService($rootScope, $state, $location, constants, pdbMain, pdbCache, pdbLocal) { // set up service view model var vm = this; @@ -37,6 +37,7 @@ // map functions $rootScope.amGlobals.IsConfigDoc = IsConfigDoc; vm.initDb = initDb; + vm.IsAutomintLoggedIn = IsAutomintLoggedIn; vm.dbAfterLogin = dbAfterLogin; // function definitions @@ -59,10 +60,11 @@ pdbMain.setDatabase(constants.pdb_main); pdbCache.setDatabase(constants.pdb_cache); pdbLocal.setDatabase(constants.pdb_local); + } - // check for login - // pdbLocal.get(constants.pdb_local_docs.login).then(checkLoginState).catch(doLogin); - dbAfterLogin(); + // check for login + function IsAutomintLoggedIn() { + return pdbLocal.get(constants.pdb_local_docs.login); } function dbAfterLogin() { @@ -75,50 +77,8 @@ since: 'now', live: true }).on('change', OnChangeMainDb); - } - - function checkLoginState(res) { - if (res && res.username && res.password) { - amLogin.loadCredentials(res.username, res.password); - amLogin.login(success, failure); - } else - doLogin(); - - function success(response) { - if (response.data && (response.ok == true)) { - if (response.data.userCtx && (response.data.userCtx.name != null) && response.data.userCtx.channels && (Object.keys(response.data.userCtx.channels).length > 0)) { - if (response.data.userCtx.channels.length > 1) { - amLogin.saveLoginCredentials(true, res.username, res.password, response.data.userCtx.channels[1]).then(proceed).catch(doLogin); - return; - } - } - } - utils.showSimpleToast('Please Login Again!'); - doLogin(); - } - - function failure(err) { - if (err.status == -1) { - if (res.isLoggedIn == true) { - proceed(); - return; - } - } - utils.showSimpleToast('Please Login Again!'); - doLogin(); - } - - function proceed() { - if (res.channel) - $rootScope.amGlobals.channel = res.channel; - if (res.username) - $rootScope.amGlobals.creator = res.username; - dbAfterLogin(); - } - } - function doLogin(err) { - $state.go('login'); + $location.path('/dashboard'); } function OnChangeMainDb(change) { diff --git a/src/app/app.states.js b/src/app/app.states.js index 58fd98a0..68e52c2d 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -14,21 +14,14 @@ StateConfigs.$inject = ['$stateProvider', '$urlRouterProvider']; function StateConfigs($stateProvider, $urlRouterProvider) { - $urlRouterProvider.when('/', '/dashboard').otherwise('/dashboard'); + $urlRouterProvider.when('/', '/home').otherwise('/'); $stateProvider - .state('locked', { - url: '/locked', - views: { - 'lockscreen': { - templateUrl: 'app/views/lockscreen.html', - controller: 'lockScreenCtrl', - controllerAs: 'vm' - } - }, - params: { - fromState: undefined - } + .state('home', { + url: '/home', + templateUrl: 'app/views/initializing.html', + controller: 'amCtrl', + controllerAs: 'vm' }) .state('login', { url: '/login', @@ -43,19 +36,27 @@ abstract: true, url: '', views: { + 'lockscreen': { + templateUrl: 'app/lock/lockscreen.html', + controller: 'amCtrlLock', + controllerAs: 'lockVm' + }, 'header_bar': { - templateUrl: 'app/appbar/headerView.html', - controller: 'appBarHeaderCtrl', + templateUrl: 'app/appbar/headerbar.html', + controller: 'amCtrlHeaderbar', controllerAs: 'headerVm' }, 'side_bar': { - templateUrl: 'app/appbar/sidebarView.html', - controller: 'appSideBarCtrl', + templateUrl: 'app/appbar/sidebar.html', + controller: 'amCtrlSidebar', controllerAs: 'sidebarVm' }, '': { templateUrl: 'app/views/restricted.html' } + }, + resolve: { + deps: ['$ocLazyLoad', loadAppbarDeps] } }) // dashboard @@ -409,6 +410,15 @@ } }); + function loadAppbarDeps($ocLazyLoad) { + return $ocLazyLoad.load([ + 'app/appbar/headerbar.controller.js', + 'app/appbar/sidebar.controller.js', + 'app/appbar/appbar.factory.js', + 'app/lock/lockscreen.controller.js' + ]) + } + function loadLoginDeps($ocLazyLoad) { return $ocLazyLoad.load([ 'app/login/login.controller.js' diff --git a/src/app/appbar/appbar.factory.js b/src/app/appbar/appbar.factory.js new file mode 100644 index 00000000..6a055ecb --- /dev/null +++ b/src/app/appbar/appbar.factory.js @@ -0,0 +1,39 @@ +/** + * Factory for Appbar Components + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').factory('amAppbar', AppbarFactory); + + AppbarFactory.$inject = ['$q', '$rootScope', 'pdbMain']; + + function AppbarFactory($q, $rootScope, pdbMain) { + // initialize factory object and function maps + var factory = { + getPasscode: getPasscode + } + + return factory; + + // function definitions + + function getPasscode() { + var tracker = $q.defer(); + pdbMain.get($rootScope.amGlobals.configDocIds.settings).then(getSettingsObj).catch(failure); + return tracker.promise; + + function getSettingsObj(res) { + tracker.resolve(res.passcode); + } + + function failure(err) { + tracker.reject(err); + } + } + } +})(); \ No newline at end of file diff --git a/src/app/appbar/headerbar.controller.js b/src/app/appbar/headerbar.controller.js new file mode 100644 index 00000000..dfd90b0a --- /dev/null +++ b/src/app/appbar/headerbar.controller.js @@ -0,0 +1,76 @@ +/** + * Controller for Header Bar + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + const ammHelp = require('./automint_modules/am-help.js'); + + angular.module('automintApp').controller('amCtrlHeaderbar', HeaderBarController); + + HeaderBarController.$inject = ['$rootScope', '$scope', '$state', '$timeout', '$mdSidenav', 'amAppbar']; + + function HeaderBarController($rootScope, $scope, $state, $timeout, $mdSidenav, amAppbar) { + var vm = this; + + // map functions to view model + vm.toggleSideNavbar = buildDelayedToggler('main-nav-left'); + vm.openLockScreen = openLockScreen; + vm.openHelpWindow = openHelpWindow; + vm.addService = addService; + + // default execution steps + $rootScope.hidePreloader = true; + amAppbar.getPasscode().then(gps).catch(failure); + + // function definitions + + function addService() { + $state.go('restricted.services.add'); + } + + function openHelpWindow() { + ammHelp.openHelpWindow(); + } + + function gps(res) { + if (res == undefined) + return; + $rootScope.isPasscodeEnabled = res.enabled; + $rootScope.isAutomintLocked = res.enabled; + } + + function failure(err) { + $rootScope.isPasscodeEnabled = false; + } + + function openLockScreen() { + $rootScope.isAutomintLocked = true; + } + + // Supplies a function that will continue to operate until the time is up. + function debounce(func, wait, context) { + var timer; + return function debounced() { + var context = $scope, + args = Array.prototype.slice.call(arguments); + $timeout.cancel(timer); + timer = $timeout(function() { + timer = undefined; + func.apply(context, args); + }, wait || 10); + }; + } + + // Build handler to open/close a SideNav; when animation finishes report completion in console + function buildDelayedToggler(navID) { + return debounce(function() { + $mdSidenav(navID).toggle(); + }, 200); + } + } +})(); \ No newline at end of file diff --git a/src/app/appbar/headerView.html b/src/app/appbar/headerbar.html similarity index 100% rename from src/app/appbar/headerView.html rename to src/app/appbar/headerbar.html diff --git a/src/app/appbar/sidebar.controller.js b/src/app/appbar/sidebar.controller.js new file mode 100644 index 00000000..c6432285 --- /dev/null +++ b/src/app/appbar/sidebar.controller.js @@ -0,0 +1,91 @@ +/** + * Controller for Sidebar + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + const electron = require('electron').remote; + const ipcRenderer = require("electron").ipcRenderer; + const amApp = electron.app; + + angular.module('automintApp').controller('amCtrlSidebar', SidebarController); + + SidebarController.$inject = ['$rootScope', '$scope', '$state', '$http', '$mdSidenav']; + + function SidebarController($rootScope, $scope, $state, $http, $mdSidenav) { + var vm = this; + + // objects passed to view model + vm.items = [{ + name: 'Dashboard', + icon: 'dashboard', + state: 'restricted.dashboard' + }, { + name: 'Customers', + icon: 'group', + state: 'restricted.customers.all' + }, { + name: 'Treatments', + icon: 'local_car_wash', + state: 'restricted.treatments.master' + }, { + name: 'Inventory', + icon: 'build', + state: 'restricted.inventory.all' + }, { + name: 'Settings', + icon: 'settings', + state: 'restricted.settings' + }]; + vm.isAutomintUpdateAvailable = undefined; + + // map functions to view model + vm.openState = openState; + vm.doUpdate = doUpdate; + vm.isSelected = isSelected; + vm.goToDashboard = goToDashboard; + + // default execution steps + ipcRenderer.on('automint-updated', listenToAutomintUpdates); + getPackageFile(); + setCoverPic(); + + // function definitions + + function setCoverPic() { + var source = localStorage.getItem('cover-pic'); + $('#am-cover-pic').attr('src', (source) ? source : 'assets/img/logo-250x125px.png').width(250).height(125); + } + + function goToDashboard() { + $mdSidenav('main-nav-left').close() + $state.go(vm.items[0].state); + } + + function isSelected(index) { + return ($rootScope.sidebarItemIndex == index); + } + + function listenToAutomintUpdates(event, arg) { + vm.isAutomintUpdateAvailable = arg; + $scope.$apply(); + } + + function doUpdate() { + ipcRenderer.send('am-quit-update', true); + } + + function getPackageFile() { + vm.automintVersion = amApp.getVersion(); + } + + function openState(state) { + $mdSidenav('main-nav-left').close() + $state.go(state); + } + } +})(); \ No newline at end of file diff --git a/src/app/appbar/sidebarView.html b/src/app/appbar/sidebar.html similarity index 100% rename from src/app/appbar/sidebarView.html rename to src/app/appbar/sidebar.html diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 21871bb1..39998663 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -10,14 +10,14 @@ (function() { angular.module('automintApp').controller('amCtrlSeCI', ServiceAddController); - ServiceAddController.$inject = ['$scope', '$state', '$q', '$log', '$filter', '$timeout', '$mdEditDialog', '$mdDialog', '$mdSidenav', 'utils', 'amServices']; + ServiceAddController.$inject = ['$rootScope', '$scope', '$state', '$q', '$log', '$filter', '$timeout', '$mdEditDialog', '$mdDialog', '$mdSidenav', 'utils', 'amServices']; /* ====== NOTE ======= > Do not create new method named moment() since it is used by moment.js */ - function ServiceAddController($scope, $state, $q, $log, $filter, $timeout, $mdEditDialog, $mdDialog, $mdSidenav, utils, amServices) { + function ServiceAddController($rootScope, $scope, $state, $q, $log, $filter, $timeout, $mdEditDialog, $mdDialog, $mdSidenav, utils, amServices) { // initialize view model var vm = this; @@ -237,6 +237,8 @@ } function goToDashboard() { + if ($state.params.fromState == 'locked') + $rootScope.isAutomintLocked = true; $mdSidenav('main-nav-left').close() $state.go('restricted.dashboard'); } @@ -1098,7 +1100,7 @@ } break; case 'locked': - transitState = 'locked'; + $rootScope.isAutomintLocked = true; break; } } diff --git a/src/app/lock/lockscreen.controller.js b/src/app/lock/lockscreen.controller.js new file mode 100644 index 00000000..79a637b0 --- /dev/null +++ b/src/app/lock/lockscreen.controller.js @@ -0,0 +1,57 @@ +/** + * Controller for lockscreen + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlLock', LockscreenController); + + LockscreenController.$inject = ['$rootScope', '$state', '$window', 'amAppbar', 'utils']; + + function LockscreenController($rootScope, $state, $window, amAppbar, utils) { + // initialize view model and temporary assignments + var vm = this, passcode, skip = true; + var pageTitle = $rootScope.page_title; + $rootScope.page_title = "Automint"; + + // named assignments and function mappings to view model + vm.isMessage = false; + vm.unlock = unlock; + vm.addService = addService; + vm.changePasscode = changePasscode; + + // default execution steps + amAppbar.getPasscode().then(gps).catch(unlock); + + // function definitions + + function changePasscode() { + vm.isMessage = false; + } + + function addService() { + $rootScope.isAutomintLocked = false; + $state.go('restricted.services.add', { + fromState: 'locked' + }); + } + + function gps(res) { + passcode = res.code; + } + + function unlock() { + $rootScope.page_title = pageTitle; + if (vm.passcode != passcode) { + vm.message = "Wrong Passcode!!!"; + vm.isMessage = true; + return; + } + $rootScope.isAutomintLocked = false; + } + } +})(); \ No newline at end of file diff --git a/src/app/lock/lockscreen.html b/src/app/lock/lockscreen.html new file mode 100644 index 00000000..e12bad9b --- /dev/null +++ b/src/app/lock/lockscreen.html @@ -0,0 +1,67 @@ + +
+ + Add a Service + add + + Add a Service +
+ + + +
+ + +
+
+ + Unlock + lock_open + +
+
+ {{lockVm.message}} +
+
+
\ No newline at end of file diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index fd2536d0..0050d9af 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -18,6 +18,9 @@ // initialize view model var vm = this; + // temporary named assignments + var channel = undefined; + // named assignments for view model vm.isLogingIn = false; vm.username = ''; @@ -79,19 +82,26 @@ amLogin.login(success, failure); function success(res) { - if (res.data && (res.ok == true)) { + if (res.data && (res.statusText == "OK")) { if (res.data.userCtx && (res.data.userCtx.name != null) && res.data.userCtx.channels && (Object.keys(res.data.userCtx.channels).length > 0)) { if (res.data.userCtx.channels.length <= 1) { failure(); return; } - amLogin.saveLoginCredentials(true, vm.username, vm.password, res.data.userCtx.channels[1]).then(proceed).catch(docNotSaved); + channel = Object.keys(res.data.userCtx.channels)[1]; + amLogin.saveLoginCredentials(true, vm.username, vm.password, Object.keys(res.data.userCtx.channels)[1]).then(proceed).catch(docNotSaved); } } function proceed(res) { + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (channel ? '-' + channel : ''), + treatment: 'treatments' + (channel ? '-' + channel : ''), + inventory: 'inventory' + (channel ? '-' + channel : ''), + workshop: 'workshop' + (channel ? '-' + channel : '') + } $amRoot.dbAfterLogin(); - $location.path('/'); + $location.path('/dashboard'); } function docNotSaved(err) { diff --git a/src/app/login/login.factory.js b/src/app/login/login.factory.js index 51dfbc3d..8506bd2f 100644 --- a/src/app/login/login.factory.js +++ b/src/app/login/login.factory.js @@ -47,7 +47,7 @@ $rootScope.amGlobals = {}; $rootScope.amGlobals.creator = username; $rootScope.amGlobals.channel = channel; - pdbLocal.get('login').then(getLoginDoc).catch(writeLoginDoc); + pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(writeLoginDoc); return tracker.promise; function getLoginDoc(res) { @@ -60,7 +60,7 @@ function writeLoginDoc(err) { var doc = { - _id: 'login', + _id: constants.pdb_local_docs.login, isLoggedIn: isLoggedIn, username : username, password: password, diff --git a/src/app/views/initializing.html b/src/app/views/initializing.html new file mode 100644 index 00000000..9e222604 --- /dev/null +++ b/src/app/views/initializing.html @@ -0,0 +1 @@ +
hello world
\ No newline at end of file diff --git a/src/app/views/lockscreen.html b/src/app/views/lockscreen.html deleted file mode 100644 index b5df879d..00000000 --- a/src/app/views/lockscreen.html +++ /dev/null @@ -1,77 +0,0 @@ - -
- - Add a Service - add - - Add a Service -
- -
- - - - Unlock - - {{vm.message}} -
-
\ No newline at end of file diff --git a/src/assets/css/automint.css b/src/assets/css/automint.css index 684dfcd8..02f72532 100644 --- a/src/assets/css/automint.css +++ b/src/assets/css/automint.css @@ -413,22 +413,17 @@ md-sidenav.md-closed.md-locked-open-add-active { .am-lockscreen { z-index: 997; - display: none; width: 100%; height: 100%; } -.am-lockscreen.active { - display: block; -} - .am-lockscreen.ng-enter { - animation: fadeIn 0.5s both ease-in; + animation: fadeIn 300ms both ease-in; } .am-lockscreen.ng-leave { transform-origin: 0% 0%; - animation: fadeOut 1s both ease-in; + animation: fadeOut 300ms both ease-in; } diff --git a/src/index.html b/src/index.html index f6167670..d8605427 100644 --- a/src/index.html +++ b/src/index.html @@ -61,10 +61,10 @@
+
-
- +
From 1b2112faedac7b4e765fc9963f98180d5041cb4d Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 12 Aug 2016 15:43:08 +0530 Subject: [PATCH 64/91] Feature #191 | Cloud Sync [only login, !code] --- src/app/app.controller.js | 57 +- src/app/app.service.js | 73 ++- src/app/appbar/headerbar.controller.js | 13 +- .../customers/customers-add.controller.js | 23 +- .../customers/customers-edit.controller.js | 39 +- .../customers/customers-viewall.controller.js | 5 +- .../components/customers/customers.factory.js | 1 - .../customers/tmpl/vehicle-crud.controller.js | 2 - .../dashboard/dashboard.controller-deps.js | 6 +- .../dashboard/dashboard.controller.js | 26 +- .../inventory/inventory-edit.controller.js | 25 +- .../inventory/inventory-viewall.controller.js | 22 +- .../invoices/invoices-view.controller.js | 48 +- .../services/services-add.controller.js | 51 +- .../services/services-edit.controller.js | 36 +- .../services/services-viewall.controller.js | 29 +- .../components/services/services.factory.js | 7 +- .../settings/settings.controller.js | 40 +- .../memberships/memberships-add.controller.js | 21 +- .../memberships-edit.controller.js | 22 +- .../memberships-viewall.controller.js | 17 +- .../packages/packages-add.controller.js | 17 +- .../packages/packages-edit.controller.js | 27 +- .../packages/packages-viewall.controller.js | 17 +- .../regular/treatments-add.controller.js | 17 +- .../regular/treatments-edit.controller.js | 31 +- .../regular/treatments-viewall.controller.js | 21 +- src/app/lock/lockscreen.controller.js | 11 +- src/app/login/login.controller.js | 25 +- src/app/login/login.factory.js | 74 ++- src/app/login/login.html | 6 +- src/app/temp/app.service.js | 577 ------------------ src/automint_modules/am-mailer.js | 4 +- .../googleoauth/google-auth.js | 6 +- 34 files changed, 561 insertions(+), 835 deletions(-) delete mode 100644 src/app/temp/app.service.js diff --git a/src/app/app.controller.js b/src/app/app.controller.js index fdf96aa8..77aeb2e5 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -23,50 +23,27 @@ // function definitions function checkLoginState(res) { - if (res && res.username && res.password) { + if (res.username && res.password) { amLogin.loadCredentials(res.username, res.password); - amLogin.login(success, failure); - } else - doLogin(); - - function success(response) { - if (response.data && (response.statusText == "OK")) { - if (response.data.userCtx && (response.data.userCtx.name != null) && response.data.userCtx.channels && (Object.keys(response.data.userCtx.channels).length > 0)) { - if (Object.keys(response.data.userCtx.channels).length > 1) { - amLogin.saveLoginCredentials(true, res.username, res.password, Object.keys(response.data.userCtx.channels)[1]).then(proceed).catch(doLogin); - return; - } - } - } - utils.showSimpleToast('Please Login Again!'); - doLogin(); - } - - function failure(err) { - if (err.status == -1) { - if (res.isLoggedIn == true) { - proceed(); - return; + if (res.isLoggedIn == true) { + $rootScope.hidePreloader = true; + if (res.channel) + $rootScope.amGlobals.channel = res.channel; + if (res.username) + $rootScope.amGlobals.creator = res.username; + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') } + if ((res.username != undefined) && (res.username != '') && (res.password != undefined) && (res.password != '')) + $amRoot.syncDb(res.username, res.password); + $amRoot.dbAfterLogin(); + return; } - utils.showSimpleToast('Please Login Again!'); - doLogin(); - } - - function proceed() { - $rootScope.hidePreloader = true; - if (res.channel) - $rootScope.amGlobals.channel = res.channel; - if (res.username) - $rootScope.amGlobals.creator = res.username; - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (res.channel ? '-' + res.channel : ''), - treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), - inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), - workshop: 'workshop' + (res.channel ? '-' + res.channel : '') - } - $amRoot.dbAfterLogin(); } + doLogin(); } function doLogin(err) { diff --git a/src/app/app.service.js b/src/app/app.service.js index ddf81427..27207a9b 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -10,13 +10,14 @@ (function() { angular.module('automintApp').service('$amRoot', AutomintService); - AutomintService.$inject = ['$rootScope', '$state', '$location', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal']; + AutomintService.$inject = ['$rootScope', '$state', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory']; - function AutomintService($rootScope, $state, $location, constants, pdbMain, pdbCache, pdbLocal) { + function AutomintService($rootScope, $state, constants, pdbMain, pdbCache, pdbLocal, amFactory) { // set up service view model var vm = this; // named assignments to rootScope + $rootScope.isAmDbLoaded = false; if (!$rootScope.amGlobals) $rootScope.amGlobals = {}; if ($rootScope.amGlobals.configDocIds == undefined) { @@ -37,6 +38,7 @@ // map functions $rootScope.amGlobals.IsConfigDoc = IsConfigDoc; vm.initDb = initDb; + vm.syncDb = syncDb; vm.IsAutomintLoggedIn = IsAutomintLoggedIn; vm.dbAfterLogin = dbAfterLogin; @@ -60,6 +62,71 @@ pdbMain.setDatabase(constants.pdb_main); pdbCache.setDatabase(constants.pdb_cache); pdbLocal.setDatabase(constants.pdb_local); + + // pre-execution steps + IsAutomintLoggedIn().then(getLoginDoc).catch(failure); + + function getLoginDoc(res) { + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') + } + $rootScope.isAmDbLoaded = true; + } + + function failure(err) { + $rootScope.amGlobals.configDocIds = { + settings: 'settings', + treatment: 'treatments', + inventory: 'inventory', + workshop: 'workshop' + } + $rootScope.isAmDbLoaded = true; + } + } + + function syncDb(username, password) { + if ((username == '') || (username == undefined) || (password == '') || (password == undefined)) { + console.error('No Username or Password provided for database sync'); + return; + } + var url = amFactory.generateDbUrl(username, password, constants.sgw_w_main); + if ((url == undefined) || (url == '')) { + console.error('Username/Password/Database name missing from url'); + return; + } + pdbMain.sync(url).on('change', onChangedDb).on('paused', onPausedDb).on('active', onActiveDb).on('denied', onDeniedDb).on('complete', onCompleteDb).on('error', onErrorDb); + + function onChangedDb(info) { + // listen to on change event + console.info(info); + } + + function onPausedDb(err) { + // listen to on pause event\ + console.warn(err); + } + + function onActiveDb() { + // listen to on active event + } + + function onDeniedDb(err) { + // listen to on denied event + console.error(err); + } + + function onCompleteDb(info) { + // listen to on complete event + console.info(info); + } + + function onErrorDb(err) { + // listen to on error event + console.error(err); + } } // check for login @@ -78,7 +145,7 @@ live: true }).on('change', OnChangeMainDb); - $location.path('/dashboard'); + $state.go('restricted.dashboard'); } function OnChangeMainDb(change) { diff --git a/src/app/appbar/headerbar.controller.js b/src/app/appbar/headerbar.controller.js index dfd90b0a..b1235cdc 100644 --- a/src/app/appbar/headerbar.controller.js +++ b/src/app/appbar/headerbar.controller.js @@ -24,11 +24,20 @@ vm.addService = addService; // default execution steps - $rootScope.hidePreloader = true; - amAppbar.getPasscode().then(gps).catch(failure); + if ($rootScope.isAmDbLoaded) + headerbarDefaults(true, false); + else + $rootScope.$watch('isAmDbLoaded', headerbarDefaults); // function definitions + function headerbarDefaults(newValue, oldValue) { + if (newValue) { + $rootScope.hidePreloader = true; + amAppbar.getPasscode().then(gps).catch(failure); + } + } + function addService() { $state.go('restricted.services.add'); } diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index c0ab150a..988b95df 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -12,10 +12,10 @@ .controller('amCtrlCuCI', CustomerAddController) .controller('amCtrlMeD', MembershipEditDialogController); - CustomerAddController.$inject = ['$state', '$filter', '$q', '$log', '$mdDialog', 'utils', 'amCustomers']; + CustomerAddController.$inject = ['$rootScope', '$state', '$filter', '$q', '$log', '$mdDialog', 'utils', 'amCustomers']; MembershipEditDialogController.$inject = ['$mdDialog', '$filter', 'membership', 'treatments']; - function CustomerAddController($state, $filter, $q, $log, $mdDialog, utils, amCustomers) { + function CustomerAddController($rootScope, $state, $filter, $q, $log, $mdDialog, utils, amCustomers) { // initialize view model var vm = this; @@ -62,13 +62,22 @@ vm.changeVehicle = changeVehicle; // default execution steps - setTimeout(focusCustomerName, 300); - getMemberships(); - getRegularTreatments(); - getVehicleTypes(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + setTimeout(focusCustomerName, 300); + getMemberships(); + getRegularTreatments(); + getVehicleTypes(); + } + } + function IsVehicleSelected(id) { return (vm.currentVehicleId == id); } @@ -204,7 +213,7 @@ $mdDialog.show(confirm).then(performDelete, ignoreDelete); function performDelete() { - console.info('membership deleted'); + // do nothing } function ignoreDelete() { diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index af16312e..b8b19590 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -12,10 +12,10 @@ .controller('amCtrlCuUI', CustomerEditController) .controller('amCtrlMeD', MembershipEditDialogController); - CustomerEditController.$inject = ['$scope', '$state', '$q', '$log', '$filter', '$mdDialog', 'utils', 'amCustomers']; + CustomerEditController.$inject = ['$rootScope', '$scope', '$state', '$q', '$log', '$filter', '$mdDialog', 'utils', 'amCustomers']; MembershipEditDialogController.$inject = ['$mdDialog', '$filter', 'membership', 'treatments']; - function CustomerEditController($scope, $state, $q, $log, $filter, $mdDialog, utils, amCustomers) { + function CustomerEditController($rootScope, $scope, $state, $q, $log, $filter, $mdDialog, utils, amCustomers) { // initialize view model var vm = this; @@ -79,17 +79,25 @@ vm.checkExistingCustomerMobile = checkExistingCustomerMobile; // default execution steps - // $state.params.id = ($state.params.id == undefined) ? "usr-anand-kacha-772d071e-852c-4a45-aaaf-089d80f73449" : $state.params.id; - if ($state.params.id != undefined) { - getCurrencySymbol(); - getMemberships(getRegularTreatments, getVehicleTypes, getCustomer); - setTimeout(focusCustomerMobile, 300); - } else { - utils.showSimpleToast('Something went wrong!'); - $state.go('restricted.customers.all'); - } + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + if ($state.params.id != undefined) { + getCurrencySymbol(); + getMemberships(getRegularTreatments, getVehicleTypes, getCustomer); + setTimeout(focusCustomerMobile, 300); + } else { + utils.showSimpleToast('Something went wrong!'); + $state.go('restricted.customers.all'); + } + } + } + function checkExistingCustomerMobile(ev) { if (vm.user.mobile == '') @@ -257,7 +265,7 @@ $mdDialog.show(confirm).then(performDelete, ignoreDelete); function performDelete() { - console.info('membership deleted'); + // do nothing } function ignoreDelete() { @@ -593,7 +601,6 @@ } function failure(err) { - console.log(err); utils.showSimpleToast('Something went wrong!'); $state.go('restricted.customers.all'); } @@ -690,7 +697,7 @@ if (userDbInstance.user.name != vm.user.name) { userDbInstance._deleted = true; - amCustomers.saveCustomer(userDbInstance).then(logResponse).catch(logResponse); + amCustomers.saveCustomer(userDbInstance).then(doNothing).catch(doNothing); var prefixUser = 'usr-' + angular.lowercase(vm.user.name).replace(' ', '-'); userDbInstance._id = utils.generateUUID(prefixUser); delete userDbInstance._deleted; @@ -739,8 +746,8 @@ } } - function logResponse(res) { - console.info(res); + function doNothing(res) { + // do nothing } } diff --git a/src/app/components/customers/customers-viewall.controller.js b/src/app/components/customers/customers-viewall.controller.js index b33235e4..d8c29b2d 100644 --- a/src/app/components/customers/customers-viewall.controller.js +++ b/src/app/components/customers/customers-viewall.controller.js @@ -2,7 +2,7 @@ * Controller for View all Customers component * @author ndkcha * @since 0.4.1 - * @version 0.6.0 + * @version 0.7.0 */ /// @@ -75,7 +75,6 @@ } function failure(error) { - console.log(error); vm.customers = []; vm.query.total = 0; } @@ -110,7 +109,7 @@ } function ignoreDelete() { - console.info('nope'); + // do nothing } function success(res) { diff --git a/src/app/components/customers/customers.factory.js b/src/app/components/customers/customers.factory.js index 7eda2b11..61632cc0 100644 --- a/src/app/components/customers/customers.factory.js +++ b/src/app/components/customers/customers.factory.js @@ -324,7 +324,6 @@ } // when database rejects function failure(error) { - console.log(error); tracker.reject({ total: 0, customers: customers diff --git a/src/app/components/customers/tmpl/vehicle-crud.controller.js b/src/app/components/customers/tmpl/vehicle-crud.controller.js index 39e6d82d..853261d4 100644 --- a/src/app/components/customers/tmpl/vehicle-crud.controller.js +++ b/src/app/components/customers/tmpl/vehicle-crud.controller.js @@ -33,8 +33,6 @@ vm.save = save; vm.cancel = cancel; - // default execution steps - // function definitions // query search for manufacturers [autocomplete] diff --git a/src/app/components/dashboard/dashboard.controller-deps.js b/src/app/components/dashboard/dashboard.controller-deps.js index 1610640a..df21f96a 100644 --- a/src/app/components/dashboard/dashboard.controller-deps.js +++ b/src/app/components/dashboard/dashboard.controller-deps.js @@ -250,7 +250,7 @@ } function failure(err) { - console.warn(err); + // do nothing } } @@ -271,7 +271,7 @@ } function failure(err) { - console.warn(err); + // do nothing } } @@ -295,7 +295,7 @@ amDashboard.getNextDueCustomers(vm.nsdcTime).then(generateNdcData).catch(failure); function failure(err) { - console.warn(err); + // do nothing } } diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index f7cb9461..3d827b8f 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -12,9 +12,9 @@ angular.module('automintApp').controller('dashboardCtrl', DashboardController); - DashboardController.$inject = ['$state', '$filter', '$log', '$mdDialog', 'utils', 'amDashboard']; + DashboardController.$inject = ['$rootScope', '$state', '$filter', '$log', '$mdDialog', 'utils', 'amDashboard']; - function DashboardController($state, $filter, $log, $mdDialog, utils, amDashboard) { + function DashboardController($rootScope, $state, $filter, $log, $mdDialog, utils, amDashboard) { // initialize view model var vm = this; var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false; @@ -100,13 +100,22 @@ vm.IsReminderInPast = IsReminderInPast; // default execution steps - initCurrentTimeSet(); - getFilterMonths(); - processPreferences(); - getCurrencySymbol(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + initCurrentTimeSet(); + getFilterMonths(); + processPreferences(); + getCurrencySymbol(); + } + } + function openCurrencyDialog() { $mdDialog.show({ controller: 'amCtrlDashCurrency', @@ -257,7 +266,6 @@ amDashboard.getNewCustomers(vm.currentTimeSet).then(generateNcpData).catch(failure); amDashboard.getProblemsAndVehicleTypes(vm.currentTimeSet).then(sortProblemsAndVehicleTypes).catch(failure); changeNsdcTimeRange(vm.nsdcTimeRange[0]); - console.warn(err.message); } } @@ -305,7 +313,7 @@ } function failure(err) { - console.info('failed to get filter months'); + // do nothing } } @@ -532,7 +540,7 @@ } function failure(err) { - console.warn(err); + // do nothing } } })();; \ No newline at end of file diff --git a/src/app/components/inventory/inventory-edit.controller.js b/src/app/components/inventory/inventory-edit.controller.js index 367e4930..1a28e765 100644 --- a/src/app/components/inventory/inventory-edit.controller.js +++ b/src/app/components/inventory/inventory-edit.controller.js @@ -2,7 +2,7 @@ * Controller for Edit Inventory component * @author ndkcha * @since 0.6.1 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlInUI', EditInventoryController); - EditInventoryController.$inject = ['$state', 'utils', 'amInventory']; + EditInventoryController.$inject = ['$rootScope', '$state', 'utils', 'amInventory']; - function EditInventoryController($state, utils, amInventory) { + function EditInventoryController($rootScope, $state, utils, amInventory) { // Initialize view model var vm = this; @@ -33,13 +33,22 @@ vm.convertNameToTitleCase = convertNameToTitleCase; vm.save = save; // default execution steps - if ($state.params.name == undefined) { - OnFailedHit(); - return; - } - getInventory(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + if ($state.params.name == undefined) { + OnFailedHit(); + return; + } + getInventory(); + } + } function OnFailedHit() { utils.showSimpleToast('Something went wrong! Please Try Again!'); diff --git a/src/app/components/inventory/inventory-viewall.controller.js b/src/app/components/inventory/inventory-viewall.controller.js index 7a616be6..f542c8a4 100644 --- a/src/app/components/inventory/inventory-viewall.controller.js +++ b/src/app/components/inventory/inventory-viewall.controller.js @@ -2,7 +2,7 @@ * Controller for View All Inventories component * @author ndkcha * @since 0.6.1 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlInRA', ViewAllInventoryController); - ViewAllInventoryController.$inject = ['$state', 'utils', 'amInventory']; + ViewAllInventoryController.$inject = ['$rootScope', '$state', 'utils', 'amInventory']; - function ViewAllInventoryController($state, utils, amInventory) { + function ViewAllInventoryController($rootScope, $state, utils, amInventory) { // Initialize view model var vm = this; @@ -31,11 +31,20 @@ vm.changeDisplayAsList = changeDisplayAsList; // default execution steps - getInventories(); - getDisplayAsList(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getInventories(); + getDisplayAsList(); + } + } + function getDisplayAsList() { amInventory.getDisplayAsList().then(success).catch(failure); @@ -86,7 +95,8 @@ } function getInventories() { - vm.promise = amInventory.getInventories().then(success).catch(failure); + if ($rootScope.isAmDbLoaded) + vm.promise = amInventory.getInventories().then(success).catch(failure); function success(res) { vm.inventories = res; diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 8dbeee26..81ba0b7a 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -18,10 +18,10 @@ .controller('amCtrlIvRI', InvoicesViewController) .controller('amCtrlCmI', ConfirmMailController); - InvoicesViewController.$inject = ['$q', '$log', '$state', '$window', '$mdDialog', 'utils', 'amInvoices']; + InvoicesViewController.$inject = ['$rootScope', '$q', '$log', '$state', '$window', '$mdDialog', 'utils', 'amInvoices']; ConfirmMailController.$inject = ['$mdDialog', 'utils', 'user']; - function InvoicesViewController($q, $log, $state, $window, $mdDialog, utils, amInvoices) { + function InvoicesViewController($rootScope, $q, $log, $state, $window, $mdDialog, utils, amInvoices) { // initialize view model var vm = this; @@ -63,25 +63,35 @@ vm.currentPage = currentPage; vm.IsNotSinglePage = IsNotSinglePage; vm.IsCustomerNotAnonymus = IsCustomerNotAnonymus; - - // default execution steps - if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { - utils.showSimpleToast('Something went wrong. Please try again!') - $state.go('restricted.services.all'); - return; - } - getCurrencySymbol(); - fillInvoiceDetails(); - loadInvoiceWLogo(); - loadInvoiceFLogo(); - getIvAlignMargins(); - getInvoicePageSize(); // electron watchers eIpc.on('am-invoice-mail-sent', OnInvoiceMailSent); + // default execution steps + + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { + utils.showSimpleToast('Something went wrong. Please try again!') + $state.go('restricted.services.all'); + return; + } + getCurrencySymbol(); + fillInvoiceDetails(); + loadInvoiceWLogo(); + loadInvoiceFLogo(); + getIvAlignMargins(); + getInvoicePageSize(); + } + } + function IsCustomerNotAnonymus() { return (vm.user.name != 'Anonymous'); } @@ -420,7 +430,7 @@ } function failure(err) { - console.info('Cound not find margin settings'); + // do nothing } } @@ -504,7 +514,6 @@ rowCount += vm.service.inventories.length; inventoryCount += vm.service.inventories.length; } - console.log(rowCount); calculateSubtotal(); function iteratePackages(package) { @@ -603,7 +612,6 @@ } function failure(err) { - console.warn(err); vm.ivSettings = { display: { workshopLogo: false, @@ -683,12 +691,12 @@ amInvoices.saveCustomerEmail($state.params.userId, vm.user.email).then(respond).catch(respond); function respond(res) { - console.info(res); + // do nothing } } function cancelMailInvoice() { - console.info('cancelled'); + // do nothing } } diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 39998663..ebf6ebff 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -201,29 +201,39 @@ vm.IsCustomerSelected = IsCustomerSelected; vm.openCustomerProfile = openCustomerProfile; - // default execution steps - setCoverPic(); - changeUserInfoState(true); // ammToDo: Enable this while commiting - setTimeout(focusUserName, 700); - // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting - buildDelayedToggler('service-details-left'); - getCurrencySymbol(); - getDefaultServiceType(); - getTreatmentDisplayFormat(); - getInventoriesSettings(); - getVehicleTypes(); - getRegularTreatments(); - getInventories(); - getMemberships(); - getLastInvoiceNo(); - getLastEstimateNo(); - getLastJobCardNo(); - // watchers $(window).on('resize', OnWindowResize); + // default execution steps + + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + setCoverPic(); + changeUserInfoState(true); // ammToDo: Enable this while commiting + setTimeout(focusUserName, 700); + // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting + buildDelayedToggler('service-details-left'); + getCurrencySymbol(); + getDefaultServiceType(); + getTreatmentDisplayFormat(); + getInventoriesSettings(); + getVehicleTypes(); + getRegularTreatments(); + getInventories(); + getMemberships(); + getLastInvoiceNo(); + getLastEstimateNo(); + getLastJobCardNo(); + } + } + function openCustomerProfile() { $state.go('restricted.customers.edit', { id: vm.user.id @@ -796,7 +806,7 @@ $mdDialog.show(confirm).then(performDelete, ignoreDelete); function performDelete() { - console.info('deleted'); + // do nothing } function ignoreDelete() { @@ -972,7 +982,7 @@ } function failure(err) { - console.warn(err); + // do nothing } } @@ -1473,7 +1483,6 @@ } function failure(err) { - console.log(err); setDefaultVehicle(); } } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 238300f3..b560d9e4 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -10,14 +10,14 @@ (function() { angular.module('automintApp').controller('amCtrlSeUI', ServiceEditController); - ServiceEditController.$inject = ['$scope', '$state', '$q', '$log', '$filter', '$timeout', '$mdEditDialog', '$mdDialog', '$mdSidenav', 'utils', 'amServices']; + ServiceEditController.$inject = ['$rootScope', '$scope', '$state', '$q', '$log', '$filter', '$timeout', '$mdEditDialog', '$mdDialog', '$mdSidenav', 'utils', 'amServices']; /* ====== NOTE ======= > Do not create new method named moment() since it is used by moment.js */ - function ServiceEditController($scope, $state, $q, $log, $filter, $timeout, $mdEditDialog, $mdDialog, $mdSidenav, utils, amServices) { + function ServiceEditController($rootScope, $scope, $state, $q, $log, $filter, $timeout, $mdEditDialog, $mdDialog, $mdSidenav, utils, amServices) { // initialize view model var vm = this; @@ -196,20 +196,29 @@ vm.IsCustomerSelected = IsCustomerSelected; vm.openCustomerProfile = openCustomerProfile; - // default execution steps - setCoverPic(); - buildDelayedToggler('service-details-left'); - changeServiceInfoState(true); - getCurrencySymbol(); - getVehicleTypes(); - getPackages(); - getMemberships(); - // watchers $(window).on('resize', OnWindowResize); + // default execution steps + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + setCoverPic(); + buildDelayedToggler('service-details-left'); + changeServiceInfoState(true); + getCurrencySymbol(); + getVehicleTypes(); + getPackages(); + getMemberships(); + } + } + function openCustomerProfile() { $state.go('restricted.customers.edit', { id: vm.user.id @@ -814,7 +823,7 @@ $mdDialog.show(confirm).then(performDelete, ignoreDelete); function performDelete() { - console.info('deleted'); + // do nothing } function ignoreDelete() { @@ -1145,7 +1154,7 @@ } function failure(err) { - console.warn(err); + // do nothing } } @@ -2282,7 +2291,6 @@ // !(save successfull) function failure(err) { - console.info(err); utils.showSimpleToast('Failed to update. Please Try Again!'); } } diff --git a/src/app/components/services/services-viewall.controller.js b/src/app/components/services/services-viewall.controller.js index 764da66f..b41cd623 100644 --- a/src/app/components/services/services-viewall.controller.js +++ b/src/app/components/services/services-viewall.controller.js @@ -12,9 +12,9 @@ angular.module('automintApp').controller('amCtrlSeRA', ServiceViewAllController); - ServiceViewAllController.$inject = ['$scope', '$state', '$filter', '$timeout', '$mdDialog', 'utils', 'amServices']; + ServiceViewAllController.$inject = ['$rootScope', '$scope', '$state', '$filter', '$timeout', '$mdDialog', 'utils', 'amServices']; - function ServiceViewAllController($scope, $state, $filter, $timeout, $mdDialog, utils, amServices) { + function ServiceViewAllController($rootScope, $scope, $state, $filter, $timeout, $mdDialog, utils, amServices) { // initialize view model var vm = this, queryChangedPromise, cacheLoadTimeout = false, isDataLoaded = false, isPreferencesLoaded = false, filterRange, isFirstTimeWsq = true; @@ -52,12 +52,23 @@ // default execution steps $scope.$watch('vm.serviceQuery', watchServiceQuery); - getCurrencySymbol(); - getFilterMonths(processPreferences); - initCurrentTimeSet(); + + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getCurrencySymbol(); + getFilterMonths(processPreferences); + initCurrentTimeSet(); + getServices(); + } + } + function IsCustomerAnonymous(name) { return (name == 'Anonymous'); } @@ -111,7 +122,6 @@ function failure(err) { callback.apply(callingFunction, Array.prototype.slice.call(callbackArgs, 1)); - console.info('failed to get filter months'); } } @@ -283,7 +293,6 @@ function failure(err) { isPreferencesLoaded = true; getServices(); - console.warn(err.message); } } @@ -320,7 +329,8 @@ // fill datatable with list of services function getServices() { vm.showPaginationBar = (vm.serviceQuery == ''); - vm.promise = (isPreferencesLoaded) ? amServices.getServices(vm.currentTimeSet, vm.serviceQuery).then(success).catch(failure) : undefined; + if ($rootScope.isAmDbLoaded) + vm.promise = (isPreferencesLoaded) ? amServices.getServices(vm.currentTimeSet, vm.serviceQuery).then(success).catch(failure) : undefined; function success(res) { isDataLoaded = true; @@ -369,7 +379,7 @@ } function ignoreDelete() { - console.info('nope'); + // do nothing } @@ -382,7 +392,6 @@ } function failure(err) { - console.info(err); utils.showSimpleToast('Service can not be deleted at moment. Please Try Again!'); } } diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 6589e48e..75af2aaa 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -391,7 +391,6 @@ } } function failure(err) { - console.log(err); tracker.reject(err); } } @@ -682,7 +681,7 @@ name = utils.convertToTitleCase(name); if (name != newUser.name) { res._deleted = true; - pdbMain.save(res).then(logResponse).catch(logResponse); + pdbMain.save(res).then(doNothing).catch(doNothing); res._id = utils.generateUUID(prefixUser); delete res._deleted; delete res._rev; @@ -715,8 +714,8 @@ } } - function logResponse(res) { - console.info(res); + function doNothing(res) { + // do nothing } function noUserFound(err) { diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 32a4b494..73f46c0b 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -134,24 +134,33 @@ default: break; } - getPasscode(); - getDefaultServiceType(); - getAmAppDataPath(); - getWorkshopDetails(); - getInvoiceSettings(); - loadInvoiceWLogo(); - loadInvoiceFLogo(); - getIvAlignMargins(); - getAllTaxSettings(); - getCurrencySymbol(); - getInvoicePageSize(); - // changeInvoiceTab(true) // testing purposes amTODO: remove it - // changeTaxTab(true); // testing purposes amTODO: remove it - + + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // default execution steps [END] // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getPasscode(); + getDefaultServiceType(); + getAmAppDataPath(); + getWorkshopDetails(); + getInvoiceSettings(); + loadInvoiceWLogo(); + loadInvoiceFLogo(); + getIvAlignMargins(); + getAllTaxSettings(); + getCurrencySymbol(); + getInvoicePageSize(); + // changeInvoiceTab(true) // testing purposes amTODO: remove it + // changeTaxTab(true); // testing purposes amTODO: remove it + } + } + function uploadInvoiceFLogo() { angular.element(document.querySelector('#am-upload-invoice-f-logo')).click(); } @@ -263,7 +272,7 @@ } function failure(err) { - console.warn(err); + // do nothing } } @@ -480,7 +489,6 @@ } function failure(err) { - console.warn(err); utils.showSimpleToast('Could not save margin! Please Try Again!'); } } diff --git a/src/app/components/treatments/memberships/memberships-add.controller.js b/src/app/components/treatments/memberships/memberships-add.controller.js index 6b0e4bcf..7c30ba29 100644 --- a/src/app/components/treatments/memberships/memberships-add.controller.js +++ b/src/app/components/treatments/memberships/memberships-add.controller.js @@ -2,7 +2,7 @@ * Controller for Add Membership component * @author ndkcha * @since 0.5.0 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlMsCI', MembershipAddController); - MembershipAddController.$inject = ['$q', '$filter', '$state', 'utils', 'amTreatments']; + MembershipAddController.$inject = ['$rootScope', '$q', '$filter', '$state', 'utils', 'amTreatments']; - function MembershipAddController($q, $filter, $state, utils, amTreatments) { + function MembershipAddController($rootScope, $q, $filter, $state, utils, amTreatments) { // initialize view model var vm = this; @@ -42,9 +42,10 @@ vm.selectedTreatments = []; // default execution steps - changeOccurencesLabel(); - changeDurationLabel(); - getVehicleTypes(getTreatments); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function maps vm.goBack = goBack; @@ -64,6 +65,14 @@ // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + changeOccurencesLabel(); + changeDurationLabel(); + getVehicleTypes(getTreatments); + } + } + function convertTnToTitleCase() { vm.treatment.details = utils.autoCapitalizeWord(vm.treatment.details); } diff --git a/src/app/components/treatments/memberships/memberships-edit.controller.js b/src/app/components/treatments/memberships/memberships-edit.controller.js index e6cff398..93e88478 100644 --- a/src/app/components/treatments/memberships/memberships-edit.controller.js +++ b/src/app/components/treatments/memberships/memberships-edit.controller.js @@ -2,7 +2,7 @@ * Controller for Edit Membership component * @author ndkcha * @since 0.5.0 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlMsUI', MembershipEditController); - MembershipEditController.$inject = ['$q', '$filter', '$state', 'utils', 'amTreatments']; + MembershipEditController.$inject = ['$rootScope', '$q', '$filter', '$state', 'utils', 'amTreatments']; - function MembershipEditController($q, $filter, $state, utils, amTreatments) { + function MembershipEditController($rootScope, $q, $filter, $state, utils, amTreatments) { // initialize view model var vm = this, oMembershipName; @@ -45,9 +45,10 @@ errorAndExit(); return; } - changeOccurencesLabel(); - changeDurationLabel(); - getVehicleTypes(getTreatments, getMembershipInfo); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function maps vm.goBack = goBack; @@ -66,6 +67,14 @@ vm.convertTnToTitleCase = convertTnToTitleCase; // function definitions + + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + changeOccurencesLabel(); + changeDurationLabel(); + getVehicleTypes(getTreatments, getMembershipInfo); + } + } function convertTnToTitleCase() { vm.treatment.details = utils.autoCapitalizeWord(vm.treatment.details); @@ -176,7 +185,6 @@ } } function failure(err) { - console.info(err); errorAndExit(); } } diff --git a/src/app/components/treatments/memberships/memberships-viewall.controller.js b/src/app/components/treatments/memberships/memberships-viewall.controller.js index acadc5f4..4659db30 100644 --- a/src/app/components/treatments/memberships/memberships-viewall.controller.js +++ b/src/app/components/treatments/memberships/memberships-viewall.controller.js @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlMsRA', MembershipsViewAll); - MembershipsViewAll.$inject = ['$state', '$filter', 'utils', 'amTreatments']; + MembershipsViewAll.$inject = ['$rootScope', '$state', '$filter', 'utils', 'amTreatments']; - function MembershipsViewAll($state, $filter, utils, amTreatments) { + function MembershipsViewAll($rootScope, $state, $filter, utils, amTreatments) { // initialize view model var vm = this; @@ -35,11 +35,20 @@ vm.IsMembershipVisible = IsMembershipVisible; // default execution steps - getCurrencySymbol(); - getMemberships(changeExpandValues); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getCurrencySymbol(); + getMemberships(changeExpandValues); + } + } + function getCurrencySymbol() { amTreatments.getCurrencySymbol().then(success).catch(failure); diff --git a/src/app/components/treatments/packages/packages-add.controller.js b/src/app/components/treatments/packages/packages-add.controller.js index 0f352ed6..84d4db90 100644 --- a/src/app/components/treatments/packages/packages-add.controller.js +++ b/src/app/components/treatments/packages/packages-add.controller.js @@ -2,7 +2,7 @@ * Controller for Add Package component * @author ndkcha * @since 0.5.0 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlPkCI', PackageAddController); - PackageAddController.$inject = ['$q', '$filter', '$state', 'utils', 'amTreatments']; + PackageAddController.$inject = ['$rootScope', '$q', '$filter', '$state', 'utils', 'amTreatments']; - function PackageAddController($q, $filter, $state, utils, amTreatments) { + function PackageAddController($rootScope, $q, $filter, $state, utils, amTreatments) { // initialize view model var vm = this; @@ -31,7 +31,10 @@ vm.selectedTreatments = []; // default execution steps - getVehicleTypes(getTreatments); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function maps vm.goBack = goBack; @@ -47,6 +50,12 @@ // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getVehicleTypes(getTreatments); + } + } + function calculateSubTotal(type) { var total = 0; vm.selectedTreatments.forEach(iterateTreatments); diff --git a/src/app/components/treatments/packages/packages-edit.controller.js b/src/app/components/treatments/packages/packages-edit.controller.js index 1950a6fb..8404d239 100644 --- a/src/app/components/treatments/packages/packages-edit.controller.js +++ b/src/app/components/treatments/packages/packages-edit.controller.js @@ -2,7 +2,7 @@ * Controller for Edit Package component * @author ndkcha * @since 0.5.0 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlPkUI', PackageAddController); - PackageAddController.$inject = ['$q', '$filter', '$state', 'utils', 'amTreatments']; + PackageAddController.$inject = ['$rootScope', '$q', '$filter', '$state', 'utils', 'amTreatments']; - function PackageAddController($q, $filter, $state, utils, amTreatments) { + function PackageAddController($rootScope, $q, $filter, $state, utils, amTreatments) { // initialize view model var vm = this, oPackageName; @@ -30,12 +30,11 @@ vm.selectedTreatments = []; // default execution steps - if ($state.params.name == undefined || $state.params.name == '') { - errorAndExit(); - return; - } - getVehicleTypes(getTreatments, getPackageInfo); - + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + // function maps vm.goBack = goBack; vm.changeNameLabel = changeNameLabel; @@ -50,6 +49,16 @@ // function definitions + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + if ($state.params.name == undefined || $state.params.name == '') { + errorAndExit(); + return; + } + getVehicleTypes(getTreatments, getPackageInfo); + } + } + function calculateSubTotal(type) { var total = 0; vm.selectedTreatments.forEach(iterateTreatments); diff --git a/src/app/components/treatments/packages/packages-viewall.controller.js b/src/app/components/treatments/packages/packages-viewall.controller.js index 3c3223ea..78033270 100644 --- a/src/app/components/treatments/packages/packages-viewall.controller.js +++ b/src/app/components/treatments/packages/packages-viewall.controller.js @@ -2,7 +2,7 @@ * Controller for View All Packages compoenent * @author ndkcha * @since 0.5.0 - * @version 0.6.0 + * @version 0.7.0 */ /// @@ -10,9 +10,9 @@ (function() { angular.module('automintApp').controller('amCtrlPkRA', PackagesViewAllController); - PackagesViewAllController.$inject = ['$state', '$filter', 'utils', 'amTreatments']; + PackagesViewAllController.$inject = ['$rootScope', '$state', '$filter', 'utils', 'amTreatments']; - function PackagesViewAllController($state, $filter, utils, amTreatments) { + function PackagesViewAllController($rootScope, $state, $filter, utils, amTreatments) { // initialize view model var vm = this; @@ -34,9 +34,18 @@ vm.calculateTotal = calculateTotal; // default execution steps - getPackages(changeExpandValues); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getPackages(changeExpandValues); + } + } function calculateTotal(vehicletype, index) { var total = 0; diff --git a/src/app/components/treatments/regular/treatments-add.controller.js b/src/app/components/treatments/regular/treatments-add.controller.js index f45e4ee6..deee3172 100644 --- a/src/app/components/treatments/regular/treatments-add.controller.js +++ b/src/app/components/treatments/regular/treatments-add.controller.js @@ -2,7 +2,7 @@ * Controller for Add Treatments component * @author ndkcha * @since 0.4.1 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -11,9 +11,9 @@ angular.module('automintApp') .controller('amCtrlTrCI', TreatmentAddController); - TreatmentAddController.$inject = ['$state', 'utils', 'amTreatments']; + TreatmentAddController.$inject = ['$rootScope', '$state', 'utils', 'amTreatments']; - function TreatmentAddController($state, utils, amTreatments) { + function TreatmentAddController($rootScope, $state, utils, amTreatments) { // initialize view model object var vm = this; @@ -35,9 +35,18 @@ vm.convertVtToTitleCase = convertVtToTitleCase; // default execution steps - getVehicleTypes(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + getVehicleTypes(); + } + } function goBack() { $state.go('restricted.treatments.master', { diff --git a/src/app/components/treatments/regular/treatments-edit.controller.js b/src/app/components/treatments/regular/treatments-edit.controller.js index 6eca8376..a0d0702d 100644 --- a/src/app/components/treatments/regular/treatments-edit.controller.js +++ b/src/app/components/treatments/regular/treatments-edit.controller.js @@ -2,7 +2,7 @@ * Controller for Edit Treatments component * @author ndkcha * @since 0.4.1 - * @version 0.6.1 + * @version 0.7.0 */ /// @@ -11,9 +11,9 @@ angular.module('automintApp') .controller('amCtrlTrUI', TreatmentsEditController); - TreatmentsEditController.$inject = ['$state', '$filter', 'utils', 'amTreatments']; + TreatmentsEditController.$inject = ['$rootScope', '$state', '$filter', 'utils', 'amTreatments']; - function TreatmentsEditController($state, $filter, utils, amTreatments) { + function TreatmentsEditController($rootScope, $state, $filter, utils, amTreatments) { // initialize view model object var vm = this; @@ -38,16 +38,25 @@ vm.convertVtToTitleCase = convertVtToTitleCase; // default execution steps - if (treatmentName == '' || treatmentName == undefined) { - utils.showSimpleToast('Something went wrong! Please Try Again!'); - $state.go('restricted.treatments.master', { - openTab: 'treatments' - }); - return; - } - getVehicleTypes(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + if (treatmentName == '' || treatmentName == undefined) { + utils.showSimpleToast('Something went wrong! Please Try Again!'); + $state.go('restricted.treatments.master', { + openTab: 'treatments' + }); + return; + } + getVehicleTypes(); + } + } function convertVtToTitleCase(rate) { rate.type = utils.convertToTitleCase(rate.type); diff --git a/src/app/components/treatments/regular/treatments-viewall.controller.js b/src/app/components/treatments/regular/treatments-viewall.controller.js index 68bbe759..e992d366 100644 --- a/src/app/components/treatments/regular/treatments-viewall.controller.js +++ b/src/app/components/treatments/regular/treatments-viewall.controller.js @@ -2,7 +2,7 @@ * Controller for View All Treatments component * @author ndkcha * @since 0.4.1 - * @version 0.5.0 + * @version 0.7.0 */ /// @@ -11,9 +11,9 @@ angular.module('automintApp') .controller('amCtrlTrRA', TreatmentsViewAll); - TreatmentsViewAll.$inject = ['$scope', '$state', 'utils', 'amTreatments']; + TreatmentsViewAll.$inject = ['$rootScope', '$scope', '$state', 'utils', 'amTreatments']; - function TreatmentsViewAll($scope, $state, utils, amTreatments) { + function TreatmentsViewAll($rootScope, $scope, $state, utils, amTreatments) { // initialize view model var vm = this; @@ -38,14 +38,23 @@ vm.getRate = getRate; // default execution steps - amTreatments.getTreatmentSettings().then(treatmentSettingFound).catch(noSettingFound); - getVehicleTypes(); - getTreatments(); + if ($rootScope.isAmDbLoaded) + defaultExecutionSteps(true, false); + else + $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // watchers $scope.$watch('trVm.stgDisplayAsList', listenToDisplayAsList); // function definitions + + function defaultExecutionSteps(newValue, oldValue) { + if (newValue) { + amTreatments.getTreatmentSettings().then(treatmentSettingFound).catch(noSettingFound); + getVehicleTypes(); + getTreatments(); + } + } // listeners function listenToDisplayAsList(newValue, oldValue) { diff --git a/src/app/lock/lockscreen.controller.js b/src/app/lock/lockscreen.controller.js index 79a637b0..3937ec8a 100644 --- a/src/app/lock/lockscreen.controller.js +++ b/src/app/lock/lockscreen.controller.js @@ -25,10 +25,19 @@ vm.changePasscode = changePasscode; // default execution steps - amAppbar.getPasscode().then(gps).catch(unlock); + if ($rootScope.isAmDbLoaded) + lockDefaults(true, false); + else + $rootScope.$watch('isAmDbLoaded', lockDefaults); // function definitions + function lockDefaults(newValue, oldValue) { + if (newValue) { + amAppbar.getPasscode().then(gps).catch(unlock); + } + } + function changePasscode() { vm.isMessage = false; } diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index 0050d9af..65df8647 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -35,6 +35,7 @@ vm.changeCode = changeCode; vm.changeUserDetails = changeUserDetails; vm.submit = submit; + vm.OnKeyDown = OnKeyDown; // default execution steps setTimeout(focusUsername, 300); @@ -63,6 +64,11 @@ vm.message = undefined; } + function OnKeyDown(event) { + if (event.keyCode == 13) + submit(); + } + function submit() { vm.message = undefined; if ((vm.username == '') && (vm.password == '') && (vm.code == '')) { @@ -78,8 +84,13 @@ return; } vm.isLogingIn = true; - amLogin.loadCredentials(vm.username, vm.password); - amLogin.login(success, failure); + if ((vm.username != undefined) && (vm.username != '') && (vm.password != undefined) && (vm.password != '')) { + amLogin.loadCredentials(vm.username, vm.password); + amLogin.login(success, failure); + } else if ((vm.code != undefined) && (vm.code != '')) { + $amRoot.dbAfterLogin(); + $location.path('/dashboard'); + } function success(res) { if (res.data && (res.statusText == "OK")) { @@ -100,8 +111,14 @@ inventory: 'inventory' + (channel ? '-' + channel : ''), workshop: 'workshop' + (channel ? '-' + channel : '') } - $amRoot.dbAfterLogin(); - $location.path('/dashboard'); + amLogin.changeExistingDocs().then(p1).catch(p1); + + function p1(res) { + if ((vm.username != undefined) && (vm.username != '') && (vm.password != undefined) && (vm.password != '')) + $amRoot.syncDb(vm.username, vm.password); + $amRoot.dbAfterLogin(); + $location.path('/dashboard'); + } } function docNotSaved(err) { diff --git a/src/app/login/login.factory.js b/src/app/login/login.factory.js index 8506bd2f..5ae894d2 100644 --- a/src/app/login/login.factory.js +++ b/src/app/login/login.factory.js @@ -23,7 +23,8 @@ var factory = { loadCredentials: loadCredentials, login: login, - saveLoginCredentials: saveLoginCredentials + saveLoginCredentials: saveLoginCredentials, + changeExistingDocs: changeExistingDocs } return factory; @@ -38,7 +39,9 @@ function login(success, failure) { $http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.amGlobals.authHeaderData; - $http.get(amFactory.generateAuthUrl(constants.sgw_w_main)).then(success, failure); + $http.get(amFactory.generateAuthUrl(constants.sgw_w_main), { + timeout: 2000 + }).then(success, failure); } function saveLoginCredentials(isLoggedIn, username, password, channel) { @@ -79,8 +82,8 @@ } function changeExistingDocs() { - var tracker = $q.defer() - if (!$rootScope.amGlobals || ($rootScope.amGlobals && !$rootScope.amGlobals.creator) || ($rootScope.amGlobals && $rootScope.amGlobals.channel)) + var tracker = $q.defer(); + if (!$rootScope.amGlobals || ($rootScope.amGlobals && !$rootScope.amGlobals.creator) || ($rootScope.amGlobals && !$rootScope.amGlobals.channel)) return 404; pdbMain.getAll().then(getAllDocs).catch(failure); return tracker.promise; @@ -88,25 +91,86 @@ function getAllDocs(res) { var docsToSave = []; res.rows.forEach(iterateRows); + pdbMain.saveAll(docsToSave).then(success).catch(failure); function iterateRows(row) { switch (row.id) { case 'settings': convertSettingsDoc(row); break; + case 'treatments': + convertTreatmentDoc(row); + break; + case 'workshop': + convertWorkshopDoc(row); + break; + case 'inventory': + convertInventoryDoc(row); + break; default: convertUserDoc(row); break; } function convertUserDoc(row) { - console.log(row); + row.doc.channel = $rootScope.amGlobals.channel; + row.doc.creator = $rootScope.amGlobals.creator; + docsToSave.push(row.doc); + } + + function convertSettingsDoc(row) { + var sdoc = $.extend({}, row.doc); + row.doc._deleted = true; + delete sdoc._rev + sdoc._id = $rootScope.amGlobals.configDocIds.settings; + sdoc.creator = $rootScope.amGlobals.creator; + sdoc.channel = $rootScope.amGlobals.channel; + docsToSave.push(row.doc); + docsToSave.push(sdoc); + } + + function convertTreatmentDoc(row) { + var tdoc = $.extend({}, row.doc); + row.doc._deleted = true; + delete tdoc._rev; + tdoc._id = $rootScope.amGlobals.configDocIds.treatment; + tdoc.creator = $rootScope.amGlobals.creator; + tdoc.channel = $rootScope.amGlobals.channel; + docsToSave.push(row.doc); + docsToSave.push(tdoc); + } + + function convertWorkshopDoc(row) { + var wdoc = $.extend({}, row.doc); + row.doc._deleted = true; + delete wdoc._rev; + wdoc._id = $rootScope.amGlobals.configDocIds.workshop; + wdoc.creator = $rootScope.amGlobals.creator; + wdoc.channel = $rootScope.amGlobals.channel; + docsToSave.push(row.doc); + docsToSave.push(wdoc); + } + + function convertInventoryDoc(row) { + var idoc = $.extend({}, row.doc); + row.doc._deleted = true; + delete idoc._rev; + idoc._id = $rootScope.amGlobals.configDocIds.inventory; + idoc.creator = $rootScope.amGlobals.creator; + idoc.channel = $rootScope.amGlobals.channel; + docsToSave.push(row.doc); + docsToSave.push(idoc); } } } + function success(res) { + tracker.resolve(res); + } + function failure(err) { console.error(err); + tracker.reject(err); } } } diff --git a/src/app/login/login.html b/src/app/login/login.html index 07175153..96003bfe 100644 --- a/src/app/login/login.html +++ b/src/app/login/login.html @@ -37,18 +37,18 @@
- +
- +
or
- +
diff --git a/src/app/temp/app.service.js b/src/app/temp/app.service.js deleted file mode 100644 index ce9f81ed..00000000 --- a/src/app/temp/app.service.js +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Closure for root level service - * @author ndkcha - * @since 0.4.1 - * @version 0.7.0 - */ - -/// - -(function() { - angular.module('automintApp') - .service('$amRoot', AutomintService); - - AutomintService.$inject = ['$rootScope', '$state', '$q', '$log', 'utils', 'constants', 'pdbCustomers', 'pdbConfig', 'pdbCommon', 'amFactory', 'pdbCache']; - - function AutomintService($rootScope, $state, $q, $log, utils, constants, pdbCustomers, pdbConfig, pdbCommon, amFactory, pdbCache) { - // set up service object - var sVm = this; - var blockViews = true; - - // keep track of current configuration of application - sVm.docIds = {}; - sVm.username = ''; - - // map functions - sVm.initDb = initDb; - sVm.syncDb = syncDb; - sVm.ccViews = ccViews; - sVm.isWorkshopId = isWorkshopId; - sVm.isTreatmentId = isTreatmentId; - sVm.isSettingsId = isSettingsId; - sVm.isInventoryId = isInventoryId; - sVm.updateConfigReferences = updateConfigReferences; - - // named assignments - var successResponse = { - success: true - } - var failureResponse = { - success: false - } - - // initialize databases - function initDb() { - // setup local databases - pdbConfig.setDatabase(constants.pdb_w_config); - pdbCustomers.setDatabase(constants.pdb_w_customers); - pdbCommon.setDatabase(constants.pdb_common); - pdbCache.setDatabase(constants.pdb_cache); - - // check and create views - manageDbVersions().then(ccViews).catch(ccViews); - - // listen to changes in local db - OnCustomerDbChanged(); - OnConfigDbChanged(); - - // setup server iteraction - // oneWayReplication(); - // syncDb(); - } - - function manageDbVersions() { - var tracker = $q.defer(); - isSettingsId().then(getSettingsDoc).catch(failure); - return tracker.promise; - - function getSettingsDoc(res) { - pdbConfig.get(sVm.docIds.settings).then(getSettingsObject).catch(writeSettingsObject); - } - - function getSettingsObject(res) { - if (!res.dbversion) - res.dbversion = 0; - switch (res.dbversion) { - case 0: - applyPatch1().then(proceed).catch(proceed); - break; - } - - function proceed(res) { - res.dbversion = constants.db_version; - pdbConfig.save(res).then(success).catch(failure); - } - } - - function writeSettingsObject(err) { - var doc = { - _id: utils.generateUUID('sttngs'), - creator: sVm.username, - dbversion: constants.db_version - } - applyPatch1().then(proceed).catch(proceed); - - function proceed(res) { - pdbConfig.save(doc).then(success).catch(failure); - } - } - - function success(res) { - tracker.resolve(res); - } - - function failure(err) { - tracker.reject(err); - } - } - - function applyPatch1() { - var tracker = $q.defer(); - pdbCustomers.getAll().then(success).catch(failure); - return tracker.promise; - - function success(res) { - var customers = []; - res.rows.forEach(iterateRows); - pdbCustomers.saveAll(customers).then(saveSuccess).catch(failure); - - function iterateRows(row) { - if (row.doc && row.doc.user && row.doc.user.vehicles) { - Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); - customers.push(row.doc); - } - - function iterateVehicles(vId) { - if (row.doc.user.vehicles[vId].services) - Object.keys(row.doc.user.vehicles[vId].services).forEach(iterateServices); - - function iterateServices(sId) { - if (row.doc.user.vehicles[vId].services[sId].status == 'billed') - row.doc.user.vehicles[vId].services[sId].status = 'due'; - if (row.doc.user.vehicles[vId].services[sId].state == undefined || row.doc.user.vehicles[vId].services[sId].state == '') - row.doc.user.vehicles[vId].services[sId].state = 'Bill'; - } - } - } - } - - function saveSuccess(res) { - tracker.resolve(res); - } - - function failure(err) { - tracker.reject(err); - } - } - - function ccViews(force) { - // service module - - pdbCustomers.getAll().then(success).catch(failure); - - // view service - function success(res) { - pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); - pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(viewvehiclessuccess).catch(viewvehiclessuccess); - - function viewvehiclessuccess(vvcdoc) { - var vdocToSave = {}; - res.rows.forEach(iterateRows); - vdocToSave._id = constants.pdb_cache_views.view_next_due_vehicles; - if (vvcdoc._rev) - vdocToSave._rev = vvcdoc._rev; - pdbCache.save(vdocToSave); - - function iterateRows(row) { - if (row.doc.user.vehicles) - Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); - - function iterateVehicles(vId) { - var vehicle = row.doc.user.vehicles[vId]; - if (!vehicle.nextdue) - return; - var cd = moment(vehicle.nextdue).format('MMM YYYY'); - if (vdocToSave[cd] == undefined) - vdocToSave[cd] = {}; - vdocToSave[cd][vId] = { - cstmr_id: row.id, - cstmr_name: row.doc.user.name, - cstmr_mobile: row.doc.user.mobile, - vhcl_reg: vehicle.reg, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_nextdue: vehicle.nextdue - } - } - } - } - - function vsuv(cachedoc) { - var docsToSave = {}, isChanged = false; - res.rows.forEach(iterateRows); - docsToSave._id = constants.pdb_cache_views.view_services; - if (cachedoc._rev) - docsToSave._rev = cachedoc._rev; - pdbCache.save(docsToSave); - - function iterateRows(row) { - if (row.doc.user.vehicles) - Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); - - function iterateVehicles(vId) { - var vehicle = row.doc.user.vehicles[vId]; - if (vehicle.services) - Object.keys(vehicle.services).forEach(iterateServices); - - function iterateServices(sId) { - var service = vehicle.services[sId]; - var cd = moment(service.date).format('MMM YYYY'); - var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); - cd = angular.lowercase(cd).replace(' ', '-'); - if (service._deleted == true) { - if (cachedoc[cd][sId] != undefined) - isChanged = true; - return; - } - if (docsToSave[cd] == undefined) - docsToSave[cd] = {}; - docsToSave[cd][sId] = { - cstmr_id: row.id, - cstmr_name: row.doc.user.name, - cstmr_mobile: row.doc.user.mobile, - vhcl_id: vId, - vhcl_reg: vehicle.reg, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_nextdue: vehicle.nextdue, - srvc_date: service.date, - srvc_cost: service.cost, - srvc_status: service.status, - srvc_state: service.state, - srvc_payreceived: payreceived - }; - } - } - } - } - } - - function failure(err) { - console.warn(err); - } - } - - function OnCustomerDbChanged() { - pdbCustomers.OnDbChanged({ - since: 'now', - live: true - }).on('change', onChange).on('complete', onComplete).on('error', onError); - - function onChange(change) { - if (change.deleted == true) { - ccViews(); - return; - } - var curdoc; - - pdbCustomers.get(change.id, { - revs_info: true - }).then(getCurrentVersion); - - function getCurrentVersion(cdoc) { - curdoc = cdoc; - if (cdoc._revs_info.length > 1) { - pdbCustomers.get(change.id, { - rev: cdoc._revs_info[1].rev - }).then(getLastVersion); - } else - getLastVersion(); - } - - function getLastVersion(ldoc) { - pdbCache.get(constants.pdb_cache_views.view_services).then(vsuv).catch(vsuv); - pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(vndvs).catch(vndvs); - - function vndvs(cachedoc) { - if (cachedoc.error == true) { - cachedoc = { - _id: constants.pdb_cache_views.view_services - } - } - if (curdoc.user.vehicles) - Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); - pdbCache.save(cachedoc); - - function iterateVehicles(vId) { - var vehicle = curdoc.user.vehicles[vId]; - var lastVehicle = undefined, lvcd = undefined; - if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId]) - lastVehicle = ldoc.user.vehicles[vId]; - if (lastVehicle && lastVehicle.nextdue) - lvcd = moment(lastVehicle.nextdue).format('MMM YYYY'); - if (!vehicle.nextdue) { - if (lastVehicle) { - if (lastVehicle.nextdue) { - if (lvcd && cachedoc[lvcd] && cachedoc[lvcd][vId]) - delete cachedoc[lvcd][vId]; - } - } - return; - } - var cd = moment(vehicle.nextdue).format('MMM YYYY'); - if (lastVehicle && lvcd) { - if ((cachedoc[lvcd] != undefined) && (cachedoc[lvcd][vId] != undefined)) - delete cachedoc[lvcd][vId]; - } - if (cachedoc[cd] == undefined) - cachedoc[cd] = {}; - cachedoc[cd][vId] = { - cstmr_id: change.id, - cstmr_name: curdoc.user.name, - cstmr_mobile: curdoc.user.mobile, - vhcl_reg: vehicle.reg, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_nextdue: vehicle.nextdue, - } - } - } - - function vsuv(cachedoc) { - if (cachedoc.error == true) { - cachedoc = { - _id: constants.pdb_cache_views.view_services - } - } - - if (curdoc.user.vehicles) - Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); - pdbCache.save(cachedoc); - - function iterateVehicles(vId) { - var vehicle = curdoc.user.vehicles[vId]; - if (vehicle.services) - Object.keys(vehicle.services).forEach(iterateServices); - - function iterateServices(sId) { - var service = vehicle.services[sId]; - var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); - var cd = moment(service.date).format('MMM YYYY'); - cd = angular.lowercase(cd).replace(' ', '-'); - if (service._deleted == true) { - if (cachedoc[cd] && cachedoc[cd][sId]) - delete cachedoc[cd][sId]; - return; - } - if (ldoc && ldoc.user && ldoc.user.vehicles && ldoc.user.vehicles[vId] && ldoc.user.vehicles[vId].services && ldoc.user.vehicles[vId].services[sId]) { - var lvd = moment(ldoc.user.vehicles[vId].services[sId].date).format('MMM YYYY'); - lvd = angular.lowercase(lvd).replace(' ', '-'); - if (cachedoc[lvd][sId] != undefined) - delete cachedoc[lvd][sId]; - } - if (cachedoc[cd] == undefined) - cachedoc[cd] = {}; - cachedoc[cd][sId] = { - cstmr_id: change.id, - cstmr_name: curdoc.user.name, - cstmr_mobile: curdoc.user.mobile, - vhcl_id: vId, - vhcl_reg: vehicle.reg, - vhcl_manuf: vehicle.manuf, - vhcl_model: vehicle.model, - vhcl_nextdue: vehicle.nextdue, - srvc_date: service.date, - srvc_cost: service.cost, - srvc_status: service.status, - srvc_state: service.state, - srvc_payreceived: payreceived - }; - } - } - } - } - } - - function onComplete(info) { - // console.log(info); - } - - function onError(error) { - // console.log(error); - } - } - - function OnConfigDbChanged() { - pdbConfig.OnDbChanged({ - since: 'now', - live: true, - include_docs: true - }).on('change', onChange).on('complete', onComplete).on('error', onError); - - function onChange(change) { - // console.log(change); - } - - function onComplete(info) { - // console.log(info); - } - - function onError(error) { - // console.log(error); - } - } - - function updateConfigReferences() { - var tracker = $q.defer(); - pdbConfig.getAll().then(successQuery).catch(failedQuery); - return tracker.promise; - - // if promise returns with all documents, update configurations - function successQuery(res) { - res.rows.forEach(iterateDocuments); - tracker.resolve(successResponse); - - // iterate through documents to match id(s) of documents - function iterateDocuments(element) { - if (element.id.match(/\btrtmnt-/i)) - sVm.docIds.treatment = element.id; - if (element.id.match(/\bwrkshp-/i)) - sVm.docIds.workshop = element.id; - if (element.id.match(/\bsttngs-/i)) - sVm.docIds.settings = element.id; - if (element.id.match(/\binvntry-/i)) - sVm.docIds.inventory = element.id; - } - } - - // if promise returns with error - function failedQuery(err) { - tracker.reject(failureResponse); - } - } - - // check if database is syncable to remote - function isSyncable() { - var tracker = $q.defer(); - var workshopUser = $.extend({}, successResponse); - - isWorkshopId().then(setCredentials).catch(noWorkshopUser); - return tracker.promise; - - // if workshop document is tracker, look for username and password inside document - function setCredentials(res) { - pdbConfig.get(sVm.docIds.workshop).then(setWorkshopUser).catch(noWorkshopUser); - - // if workshop users existing, return with username and password - function setWorkshopUser(res) { - if (res.user) { - workshopUser.username = res.user.username; - workshopUser.password = res.user.password; - tracker.resolve(workshopUser); - } else - noWorkshopUser(); - } - } - - // if no workshop, return with failed response bool - function noWorkshopUser(error) { - tracker.reject(failureResponse); - } - } - - // setup sync (bidirectional replication) - function syncDb() { - isSyncable().then().catch(); - - // if sync details found - function runSync(res) { - if (res.success) { - sVm.username = res.username; - // construct database url for workshop configuration and customers' db - dbConfigUrl = amFactory.generateDbUrl(res.username, res.password, constants.sgw_w_config); - dbCustomersUrl = amFactory.generateDbUrl(res.username, res.password, constants.sgw_w_customers); - - // sync database - pdbConfig.sync(dbConfigUrl) - .on('change', onChangedDb) - .on('paused', onPausedDb) - .on('active', onActiveDb) - .on('denied', onDeniedDb) - .on('complete', onCompleteDb) - .on('error', onErrorDb); - - pdbCustomers.sync(dbCustomersUrl) - .on('change', onChangedDb) - .on('paused', onPausedDb) - .on('active', onActiveDb) - .on('denied', onDeniedDb) - .on('complete', onCompleteDb) - .on('error', onErrorDb); - } - } - - // no sync details found - function noSync(error) { - $log.debug('cannot sync at moment! no sync details found'); - } - } - - // setup one-way replication - function oneWayReplication() { - // setup common database replication - pdbCommon.replicate(constants.db_url + constants.sgw_common) - .on('change', onChangedDb) - .on('paused', onPausedDb) - .on('active', onActiveDb) - .on('denied', onDeniedDb) - .on('complete', onCompleteDb) - .on('error', onErrorDb); - } - - // check if treatment's document id is loaded to current docId object - function isTreatmentId() { - return isDocId('trtmnt'); - } - - // check if workshop's document id is loaded to current docId object - function isWorkshopId() { - return isDocId('wrkshp'); - } - - // check if settings' document id is loaded to current docId object - function isSettingsId() { - return isDocId('sttngs'); - } - - // check if inventory's document id is loaded to current docId object - function isInventoryId() { - return isDocId('invntry'); - } - - // the check function - function isDocId(query) { - var tracker = $q.defer(); - if (sVm.docIds[query] == '' || sVm.docIds[query] == undefined) - sVm.updateConfigReferences().then(configUpdated).catch(updateFailed); - else - updateFailed(); - return tracker.promise; - - function configUpdated(res) { - tracker.resolve(successResponse); - } - - function updateFailed(err) { - tracker.resolve(failureResponse); - } - } - - // database listeners - - function onChangedDb(info) { - // listen to on change event - } - - function onPausedDb(err) { - // listen to on pause event\ - } - - function onActiveDb() { - // listen to on active event - } - - function onDeniedDb(err) { - // listen to on denied event - } - - function onCompleteDb(info) { - // listen to on complete event - updateConfigReferences(); - } - - function onErrorDb(err) { - // listen to on error event - } - } -})(); \ No newline at end of file diff --git a/src/automint_modules/am-mailer.js b/src/automint_modules/am-mailer.js index afdb1bd3..5856c522 100644 --- a/src/automint_modules/am-mailer.js +++ b/src/automint_modules/am-mailer.js @@ -2,7 +2,7 @@ * Module to send emails * @author ndkcha * @since 0.4.1 - * @version 0.6.0 + * @version 0.7.0 */ /// @@ -45,7 +45,7 @@ html: emailTemplate }, function(err, email) { if (err) { - console.warn(err); + console.error(err); return; } diff --git a/src/automint_modules/googleoauth/google-auth.js b/src/automint_modules/googleoauth/google-auth.js index aea04e1c..6610da57 100644 --- a/src/automint_modules/googleoauth/google-auth.js +++ b/src/automint_modules/googleoauth/google-auth.js @@ -2,7 +2,7 @@ * Module to authenticate user to Google Server * @author ndkcha * @since 0.4.1 - * @version 0.6.4 + * @version 0.7.0 */ /// @@ -42,7 +42,7 @@ // process file containing client secrets and extract its contents for authorization function processClientSecrets(err, content) { if (err) { - console.warn('Error loading content: ' + err); + console.error('Error loading content: ' + err); return; } authorize(JSON.parse(content), cbFn); @@ -108,7 +108,7 @@ function handleToken(err, token) { if (err) { - console.warn('Error while trying to retrieve access token', err); + console.error('Error while trying to retrieve access token', err); return; } oAuth2Client.credentials = token; From 3e83cd46f51a4d3d7f2a6168f239dbfb90358f0d Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 12 Aug 2016 16:26:48 +0530 Subject: [PATCH 65/91] Fixes in #202 #205 202. (1) when vehicle is not mentioned, it should not say '(Vehicle)', in service list. 202. (2) when customer name is not mentioned and vehicle is mentioned, it doesn't show the vehicle's name anywhere. 205. it's not working as per expected. --- src/app/components/customers/customers-edit.controller.js | 5 +++++ .../components/customers/customers-viewall.controller.js | 6 ++++++ src/app/components/customers/customers_edit.html | 2 +- src/app/components/customers/customers_viewAll.html | 2 +- src/app/components/services/services-edit.controller.js | 1 + src/app/components/services/services-viewall.controller.js | 5 +++++ src/app/components/services/services.factory.js | 4 ++-- src/app/components/services/services_add.html | 4 ++-- src/app/components/services/services_viewAll.html | 2 +- 9 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index b8b19590..5894716c 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -77,6 +77,7 @@ vm.editVehicle = editVehicle; vm.convertNameToTitleCase = convertNameToTitleCase; vm.checkExistingCustomerMobile = checkExistingCustomerMobile; + vm.IsVehicleAnonymous = IsVehicleAnonymous; // default execution steps if ($rootScope.isAmDbLoaded) @@ -85,6 +86,10 @@ $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); // function definitions + function IsVehicleAnonymous(service) { + return (service.vhcl_reg == 'Vehicle'); + } + function defaultExecutionSteps(newValue, oldValue) { if (newValue) { if ($state.params.id != undefined) { diff --git a/src/app/components/customers/customers-viewall.controller.js b/src/app/components/customers/customers-viewall.controller.js index d8c29b2d..1a76da17 100644 --- a/src/app/components/customers/customers-viewall.controller.js +++ b/src/app/components/customers/customers-viewall.controller.js @@ -36,11 +36,17 @@ vm.deleteCustomer = deleteCustomer; vm.editCustomer = editCustomer; vm.changeQueryMode = changeQueryMode; + vm.IsVehicleAnonymous = IsVehicleAnonymous; // default watchers $scope.$watch('vm.customerQuery', watchCustomerQuery); + getCustomers(); // function definitions + + function IsVehicleAnonymous(vehicle) { + return (vehicle.reg == 'Vehicle'); + } function watchCustomerQuery(newValue, oldValue) { if(queryChangedPromise){ diff --git a/src/app/components/customers/customers_edit.html b/src/app/components/customers/customers_edit.html index d0bc6e85..383ef097 100644 --- a/src/app/components/customers/customers_edit.html +++ b/src/app/components/customers/customers_edit.html @@ -260,7 +260,7 @@
- + diff --git a/src/app/components/customers/customers_viewAll.html b/src/app/components/customers/customers_viewAll.html index f403de51..5badee83 100644 --- a/src/app/components/customers/customers_viewAll.html +++ b/src/app/components/customers/customers_viewAll.html @@ -37,7 +37,7 @@ - + - + From 6d4244479df9621b58028d4bdc0db15a6e18cd7e Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 14 Aug 2016 16:27:19 +0530 Subject: [PATCH 66/91] Feature #171 | Backup Restore [functionality] 171. Upload previous database dump generated from Automint App. --- .../settings/settings-backup.factory.js | 6 +- .../settings/settings-importdata.service.js | 503 +++++++++++++----- .../settings/settings.controller.js | 18 +- src/app/components/settings/settings.html | 2 +- 4 files changed, 396 insertions(+), 133 deletions(-) diff --git a/src/app/components/settings/settings-backup.factory.js b/src/app/components/settings/settings-backup.factory.js index e0307118..d6ebb8c4 100644 --- a/src/app/components/settings/settings-backup.factory.js +++ b/src/app/components/settings/settings-backup.factory.js @@ -8,6 +8,9 @@ /// (function() { + const electron = require('electron').remote; + const amApp = electron.app; + angular.module('automintApp').factory('amBackup', BackupFactory); BackupFactory.$inject = ['$q', 'pdbMain', 'pdbLocal']; @@ -38,7 +41,8 @@ } function foundDocsToBackup(data) { - backupDocument.backupTime = Date.now(); + backupDocument.backupTime = moment().format(); + backupDocument.automintVersion = amApp.getVersion(); backupDocument.main = {} backupDocument.main.doc = []; backupDocument.local = {}; diff --git a/src/app/components/settings/settings-importdata.service.js b/src/app/components/settings/settings-importdata.service.js index 44fd40c8..5f5bb12d 100644 --- a/src/app/components/settings/settings-importdata.service.js +++ b/src/app/components/settings/settings-importdata.service.js @@ -20,148 +20,394 @@ var tracker; // function mappings - sVm.compileCSVFile = compileCSVFile; + sVm.checkTypeofFile = checkTypeofFile; sVm.cleanUp = cleanUp; - // function declarations and definations - function compileCSVFile(files) { + function checkTypeofFile(files) { tracker = $q.defer(); - readFile(files[0], readerCallback); + checkFile(files[0]); return tracker.promise; - // declarations and definations - - // initiate reading of CSV file - function readFile(f, callback) { + function checkFile(file) { var reader = new FileReader(); - reader.readAsText(f); - reader.onload = onReaderLoad; + reader.readAsText(file); + reader.onload = onRLoad; - function onReaderLoad(event) { - var csvArray = $.csv2Array(event.target.result); - callback(csvArray); + function onRLoad(event) { + var isCsvFile, isJsonFile; + try { + var csvArray = $.csv2Array(event.target.result); + isCsvFile = true; + csvCallback(csvArray); + } catch(exception) { + isCsvFile = false; + } + if (isCsvFile) + return; + try { + var jsonObj = JSON.parse(event.target.result); + isJsonFile = true; + jsonCallback(jsonObj); + } catch(exception) { + isJsonFile = false; + } + if (isJsonFile) + return; + tracker.reject({ + success: false, + message: 'Invalid File!' + }); } } + } - // parse CSV file - function readerCallback(csvArray) { - var customers = []; - var csvMap = {}; - var csvFields = csvArray[0]; - tracker.notify({ - current: 0, - total: csvFields.length - }); - for (var j = 0; j < csvFields.length; j++) { - var fieldName = csvFields[j].toLowerCase(); - switch (fieldName) { - case 'mobile': - case 'mo': - case 'mobile number': - csvMap.mobile = j; - break; - case 'name': - csvMap.name = j; - break; - case 'reg': - case 'registration': - case 'registration number': - case 'vehicle number': - case 'registration no': - case 'vehicle no': - csvMap.reg = j; - break; - case 'model': - case 'car': - csvMap.model = j; - break; - case 'manufacturer': - case 'company': - csvMap.manuf = j; - break; - } + function jsonCallback(restore) { + $q.all([ + pdbMain.getAll(), + pdbMain.get($rootScope.amGlobals.configDocIds.settings) + ]).then(getAllDocs).catch(getAllDocs); + + function getAllDocs(combores) { + var docsToSave = []; + var res = combores[0]; + var setconfig = combores[1]; + + if (restore.customers && restore.customers.doc) + restore.customers.doc.forEach(iterateCustomers); + + if (restore.config && restore.config.doc) + restore.config.doc.forEach(iterateConfig); + + pdbMain.saveAll(docsToSave).then(success).catch(failure); + + function success(res) { + tracker.resolve({ + success: true, + message: 'Backup has been restored! You may now delete the file.' + }) } - for (var row = 1; row < csvArray.length; row++) { - // hold different values coming from csv file - var mobile = csvArray[row][csvMap.mobile]; - var name = csvArray[row][csvMap.name]; - var reg = csvArray[row][csvMap.reg]; - var manuf = csvArray[row][csvMap.manuf]; - var model = csvArray[row][csvMap.model]; - - // check if at least one entry has been found, skip otherwise - if (!(mobile || name || reg || manuf || model)) - continue; - // encode objects from derived data - var user = { - mobile: mobile ? mobile : '', - name: name ? utils.convertToTitleCase(name) : '' + function failure(err) { + console.error(err); + tracker.reject({ + success: false, + message: 'Could not restore backup at moment! Please Try Again Later!' + }) + } + + function iterateConfig(config) { + var doc = $.extend({}, config); + doc.creator = $rootScope.amGlobals.creator; + doc.channel = $rootScope.amGlobals.channel; + if (config._id.match(/\btrtmnt-/i)) + doc._id = $rootScope.amGlobals.configDocIds.treatment; + else if (config._id.match(/\bwrkshp-/i)) + doc._id = $rootScope.amGlobals.configDocIds.workshop; + else if (config._id.match(/\bsttngs-/i)) + doc._id = $rootScope.amGlobals.configDocIds.settings; + else if (config._id.match(/\binvntry-/i)) + doc._id = $rootScope.amGlobals.configDocIds.inventory; + + delete doc._rev; + + if (doc._id == $rootScope.amGlobals.configDocIds.settings) { + if (doc.dbversion) + delete doc.dbversion; + if (doc.settings && doc.settings.serviceTax) { + if (!doc.settings.tax) + doc.settings.tax = {}; + doc.settings.tax['Service Tax'] = { + type: doc.settings.serviceTax.taxIncType, + isTaxApplied: doc.settings.serviceTax.applyTax, + isForTreatments: true, + isForInventory: false, + percent: doc.settings.serviceTax.tax + } + delete doc.settings['serviceTax']; + } + if (doc.settings && doc.settings.servicetax) { + if (!doc.settings.tax) + doc.settings.tax = {}; + doc.settings.tax['Service Tax'] = { + type: doc.settings.servicetax.taxIncType, + isTaxApplied: doc.settings.servicetax.applyTax, + isForTreatments: true, + isForInventory: false, + percent: doc.settings.servicetax.tax + } + delete doc.settings['servicetax']; + } + if (doc.settings && doc.settings.vat) { + if (!doc.settings.tax) + doc.settings.tax = {}; + doc.settings.tax['VAT'] = { + type: doc.settings.vat.taxIncType, + isTaxApplied: doc.settings.vat.applyTax, + isForTreatments: false, + isForInventory: true, + percent: doc.settings.vat.tax + } + delete doc.settings['vat']; + } } - var vehicle = { - reg: reg ? reg.toUpperCase() : '', - manuf: manuf ? manuf : '', - model: model ? model : '' + + if (res) { + var confound = $filter('filter')(res.rows, comparator, true); + + if (confound.length == 1) { + doc._rev = confound[0].doc._rev; + doc.currency = confound[0].doc.currency; + } + + function comparator(value, index) { + if (value.doc && (value.doc._id == doc._id)) + return true; + } } - // reduce duplicates in imported file - var found = $filter('filter')(customers, { - name: name, - mobile: mobile - }, true); - if (found.length > 0) { - var vfound = $filter('filter')(found[0].vehicles, { - reg: reg - }, true); - if (vfound.length > 0) - continue; - else { - if ((reg || manuf || model) && (reg != '' || manuf != '' || model != '')) - found[0].vehicles.push(vehicle); + docsToSave.push(doc); + } + + function iterateCustomers(customer) { + if ($rootScope.amGlobals.IsConfigDoc(customer._id)) + return; + var doc, custfound; + if (res.error) { + custfound = { + length: 0 } - } else { - user.vehicles = []; - if ((reg || manuf || model) && (reg != '' || manuf != '' || model != '')) - user.vehicles.push(vehicle); - customers.push(user); + } else + custfound = $filter('filter')(res.rows, comparator, true); + + if (custfound.length == 1) { + // doc = already existing user + // customer = imported user + doc = custfound[0].doc; + if (doc.user && customer.user) + Object.keys(customer.user).forEach(changeNewFieldsInUser); + + if (customer.user.vehicles) + Object.keys(customer.user.vehicles).forEach(iterateVehicles); + } else if (customer.user) { + // doc = user to be inserted + // customer = imported + var prefixUser = 'usr-' + angular.lowercase(customer.user.name).replace(' ', '-'); + doc = { + _id: utils.generateUUID(prefixUser), + channel: $rootScope.amGlobals.channel, + creator: $rootScope.amGlobals.creator, + user: {} + }; + + if (customer.user) + Object.keys(customer.user).forEach(changeNewFieldsInUser); + + if (customer.user.vehicles) + Object.keys(customer.user.vehicles).forEach(iterateVehicles); + } + docsToSave.push(doc); + + function iterateVehicles(vId) { + var tempv = customer.user.vehicles[vId], isDuplicateVehicle = false; + var newVehicleId; + if (!doc.user.vehicles) { + doc.user.vehicles = {}; + if (newVehicleId == undefined) + newVehicleId = utils.generateUUID('vhcl') + doc.user.vehicles[newVehicleId] = tempv; + } else { + Object.keys(doc.user.vehicles).forEach(iterateExstVehicle); + if (!isDuplicateVehicle) { + if (newVehicleId == undefined) + newVehicleId = utils.generateUUID('vhcl') + doc.user.vehicles[newVehicleId] = tempv; + } + } + + if (doc.user.vehicles[newVehicleId].services) + Object.keys(doc.user.vehicles[newVehicleId].services).forEach(iterateServices); + + function iterateServices(sId) { + var temps = doc.user.vehicles[newVehicleId].services[sId]; + var service = $.extend({}, temps); + if (temps.serviceTax) { + delete service['serviceTax']; + if (!service.taxes) + service.taxes = {}; + service.taxes['Service Tax'] = { + type: temps.serviceTax.taxIncType, + percent: temps.serviceTax.tax, + isTaxApplied: temps.serviceTax.applyTax, + isForTreatments: true, + isForInventory: false + } + } + if (temps.vat) { + delete service['vat']; + if (!service.taxes) + service.taxes = {}; + service.taxes['VAT'] = { + type: temps.vat.taxIncType, + percent: temps.vat.tax, + isTaxApplied: temps.vat.applyTax, + isForTreatments: false, + isForInventory: true + } + } + + if (service.problems) + Object.keys(service.problems).forEach(iterateProblems); + if (service.inventories) + Object.keys(service.inventories).forEach(iterateInventory); + + doc.user.vehicles[newVehicleId].services[sId] = service; + + function iterateInventory(inventory) { + var tempi = service.inventories[inventory]; + if (temps.vat && temps.vat.applyTax) { + if (temps.vat.taxIncType == 'inclusive') { + tempi.amount = parseFloat(tempi.rate) + parseFloat(tempi.tax); + tempi.amount = parseFloat(tempi.amount); + tempi.amount = (tempi.amount % 1 != 0) ? tempi.amount.toFixed(2) : parseInt(tempi.amount); + } else + tempi.amount = parseFloat(tempi.rate); + } else + tempi.amount = parseFloat(tempi.rate); + delete tempi.tax; + service.inventories[inventory] = tempi; + } + + function iterateProblems(problem) { + var tempp = service.problems[problem]; + if (temps.serviceTax && temps.serviceTax.applyTax) { + if (temps.serviceTax.taxIncType == 'inclusive') { + tempp.amount = parseFloat(tempp.rate) + parseFloat(tempp.tax); + tempp.amount = parseFloat(tempp.amount); + tempp.amount = (tempp.amount % 1 != 0) ? tempp.amount.toFixed(2) : parseInt(tempp.amount); + } else + tempp.amount = parseFloat(tempp.rate); + } else + tempp.amount = parseFloat(tempp.rate); + delete tempp.tax; + service.problems[problem] = tempp; + } + } + + function iterateExstVehicle(evId) { + var extv = doc.user.vehicles[evId]; + if ((tempv.reg == extv.reg) || (vId == evId)) { + newVehicleId = evId; + extv.reg = tempv.reg; + extv.manuf = (tempv.manuf ? tempv.manuf : extv.manuf); + extv.model = (tempv.model ? tempv.model : extv.model); + extv.type = (tempv.type ? tempv.type : extv.type); + isDuplicateVehicle = true; + } + } + } + + function changeNewFieldsInUser(key) { + if ((key == 'vehicles') || (customer.user[key] == '') || (angular.isObject(customer.user[key]) && (Object.keys(customer.user[key]).length == 0))) + return; + doc.user[key] = customer.user[key]; + } + + function comparator(value, index, array) { + if (value.doc && value.doc.user && value.doc.user.mobile && customer.user && customer.user.mobile && (value.doc.user.mobile != '') && (customer.user.mobile != '') && (value.doc.user.mobile == customer.user.mobile)) + return true; } } + } + } - updateDatabase(customers); + // parse CSV file + function csvCallback(csvArray) { + var customers = []; + var csvMap = {}; + var csvFields = csvArray[0]; + for (var j = 0; j < csvFields.length; j++) { + var fieldName = csvFields[j].toLowerCase(); + switch (fieldName) { + case 'mobile': + case 'mo': + case 'mobile number': + csvMap.mobile = j; + break; + case 'name': + csvMap.name = j; + break; + case 'reg': + case 'registration': + case 'registration number': + case 'vehicle number': + case 'registration no': + case 'vehicle no': + csvMap.reg = j; + break; + case 'model': + case 'car': + csvMap.model = j; + break; + case 'manufacturer': + case 'company': + csvMap.manuf = j; + break; + } } + for (var row = 1; row < csvArray.length; row++) { + // hold different values coming from csv file + var mobile = csvArray[row][csvMap.mobile]; + var name = csvArray[row][csvMap.name]; + var reg = csvArray[row][csvMap.reg]; + var manuf = csvArray[row][csvMap.manuf]; + var model = csvArray[row][csvMap.model]; - function getDocuments() { - var tracker = $q.defer(); - var documents = []; - pdbMain.getAll().then(docFound).catch(failure); - return tracker.promise; + // check if at least one entry has been found, skip otherwise + if (!(mobile || name || reg || manuf || model)) + continue; - function docFound(res) { - res.rows.forEach(iterateDocuments); - tracker.resolve(documents); + // encode objects from derived data + var user = { + mobile: mobile ? mobile : '', + name: name ? utils.convertToTitleCase(name) : '' } - - function failure(err) { - tracker.reject(documents); + var vehicle = { + reg: reg ? reg.toUpperCase() : '', + manuf: manuf ? manuf : '', + model: model ? model : '' } - function iterateDocuments(element) { - if ($rootScope.amGlobals.IsConfigDoc(element.id)) - return; - documents.push(element.doc); + // reduce duplicates in imported file + var found = $filter('filter')(customers, { + name: name, + mobile: mobile + }, true); + if (found.length > 0) { + var vfound = $filter('filter')(found[0].vehicles, { + reg: reg + }, true); + if (vfound.length > 0) + continue; + else { + if ((reg || manuf || model) && (reg != '' || manuf != '' || model != '')) + found[0].vehicles.push(vehicle); + } + } else { + user.vehicles = []; + if ((reg || manuf || model) && (reg != '' || manuf != '' || model != '')) + user.vehicles.push(vehicle); + customers.push(user); } } + updateDatabase(customers); + function updateDatabase(customers) { + var addedCustomerCount = 0; getDocuments().then(matchAndUpdate).catch(failure); function matchAndUpdate(res) { - var addedCustomerCount = 0; var customersToSave = []; - tracker.notify({ - current: 0, - total: customers.length - }); customers.forEach(iterateCustomers); function iterateCustomers(customer) { @@ -188,7 +434,7 @@ existingVehicleInDb = true; } if (!existingVehicleInDb) { - var prefixVehicle = 'vhcl' + ((vehicle.manuf && vehicle.model) ? '-' + angular.lowercase(vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vehicle.model).replace(' ', '-') : ''); + var prefixVehicle = 'vhcl'; dbUser.user.vehicles[utils.generateUUID('vhcl')] = vehicle; } } @@ -207,28 +453,23 @@ customer.vehicles.forEach(iterateVehicles); function iterateVehicles(vehicle) { - var prefixVehicle = 'vhcl' + ((vehicle.manuf && vehicle.model) ? '-' + angular.lowercase(vehicle.manuf).replace(' ', '-') + '-' + angular.lowercase(vehicle.model).replace(' ', '-') : ''); + var prefixVehicle = 'vhcl'; targetUser.user.vehicles[utils.generateUUID(prefixVehicle)] = vehicle; } } else delete targetUser.user.vehicles; addedCustomerCount++; } - tracker.notify({ - current: addedCustomerCount, - total: customers.length - }); customersToSave.push(targetUser); } pdbMain.saveAll(customersToSave).then(saveSuccess).catch(failure); - tracker.resolve({ - success: true, - message: addedCustomerCount + " customer(s) added!" - }); } function saveSuccess(res) { - $log.info('Import Successfull!'); + tracker.resolve({ + success: true, + message: addedCustomerCount + ' customers have been imported!' + }) } function failure(error) { @@ -238,6 +479,28 @@ }); } } + + function getDocuments() { + var tracker = $q.defer(); + var documents = []; + pdbMain.getAll().then(docFound).catch(failure); + return tracker.promise; + + function docFound(res) { + res.rows.forEach(iterateDocuments); + tracker.resolve(documents); + } + + function failure(err) { + tracker.reject(documents); + } + + function iterateDocuments(element) { + if ($rootScope.amGlobals.IsConfigDoc(element.id)) + return; + documents.push(element.doc); + } + } } function cleanUp() { diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 73f46c0b..9869f1d3 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -272,6 +272,7 @@ } function failure(err) { + console.log(err); // do nothing } } @@ -662,23 +663,18 @@ // callback function to import csv files function handleUploadedFile(e) { - vm.currentCsvProgress = 0; + vm.isCsvProcessed = true; var files = e.target.files || e.originalEvent.dataTransfer.files; - amImportdata.compileCSVFile(files).then(displayToastMessage).catch(displayToastMessage).finally(cleanUp, updates); + amImportdata.checkTypeofFile(files).then(displayToastMessage).catch(displayToastMessage).finally(cleanUp); function displayToastMessage(res) { utils.showSimpleToast(res.message); - vm.currentCsvProgress = 100; - vm.isCsvProcessed = (vm.currentCsvProgress < 100); + vm.isCsvProcessed = false; } function cleanUp(res) { amImportdata.cleanUp(); - } - - function updates(res) { - vm.currentCsvProgress = (res.total == 0) ? 0 : ((res.current * 100) / res.total); - vm.isCsvProcessed = (vm.currentCsvProgress < 100); + defaultExecutionSteps(true, false); } } @@ -715,7 +711,7 @@ } function failure(err) { - $log.info('No workshop details found!'); + // do nothing } } @@ -790,7 +786,7 @@ } function failure(err) { - $log.info('Cannot find invoice settings!'); + // do nothing } } diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 9faa983d..545cf29b 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -112,7 +112,7 @@ or
Drop CSV File - +
First row of the CSV file must be the header row, divided into fields like this;
name, mobile, registration, manufacturer, model.
From 0938b93eb323c09cd70f2bc29369fd1bef0b6472 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 15 Aug 2016 16:56:39 +0530 Subject: [PATCH 67/91] Features #191 #185 | Licensing & Cloud [!chpwd] 191. Cloud Sync Implementation 185. Licensing --- src/app/app.controller.js | 59 ++++++- src/app/app.service.js | 89 +++++++++- .../dashboard/dashboard.controller.js | 15 +- .../settings/settings.controller.js | 1 - src/app/login/login.controller.js | 153 +++++++++++++----- src/app/login/login.factory.js | 104 +++++++++++- src/app/login/login.html | 2 +- 7 files changed, 370 insertions(+), 53 deletions(-) diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 77aeb2e5..4e41308d 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -18,14 +18,29 @@ var vm = this; // default execution steps + $rootScope.isFirstLoad = true; $amRoot.IsAutomintLoggedIn().then(checkLoginState).catch(doLogin); // function definitions function checkLoginState(res) { + var curdate = moment().format(), isLoggedIn = false; if (res.username && res.password) { - amLogin.loadCredentials(res.username, res.password); - if (res.isLoggedIn == true) { + res.isLoggedIn = isLoggedIn; + if (!res.license) { + doLogin(); + return; + } else { + if ((res.license.starts == undefined) || (res.license.ends == undefined)) { + doLogin(); + return; + } + var startdate = moment(res.license.starts, 'YYYY-MM-DD').format(); + var enddate = moment(res.license.ends, 'YYYY-MM-DD').format(); + if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) + isLoggedIn = true; + } + if (isLoggedIn == true) { $rootScope.hidePreloader = true; if (res.channel) $rootScope.amGlobals.channel = res.channel; @@ -37,12 +52,50 @@ inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), workshop: 'workshop' + (res.channel ? '-' + res.channel : '') } - if ((res.username != undefined) && (res.username != '') && (res.password != undefined) && (res.password != '')) + var isSyncableDb = ((res.username != undefined) && (res.username != '') && (res.password != undefined) && (res.password != '')); + if (!res.cloud) + isSyncableDb = false; + else if ((res.cloud.starts == undefined) || (res.cloud.ends == undefined)) + isSyncableDb = false; + else { + var cloudstart = moment(res.cloud.starts, 'YYYY-MM-DD').format(); + var cloudend = moment(res.cloud.ends, 'YYYY-MM-DD').format(); + isSyncableDb = ((cloudstart.localeCompare(curdate) < 0) && (cloudend.localeCompare(curdate) > 0)); + if (!isSyncableDb) + utils.showSimpleToast('Your cloud services have expired! Please Contact Automint Care!'); + } + if (isSyncableDb) $amRoot.syncDb(res.username, res.password); $amRoot.dbAfterLogin(); return; } } + if (res.activation) { + res.isLoggedIn = isLoggedIn; + if ((res.activation.startdate == undefined) || (res.activation.enddate == undefined)) { + doLogin(); + return; + } + var activationstart = moment(res.activation.startdate, 'YYYY-MM-DD').format(); + var activationend = moment(res.activation.enddate, 'YYYY-MM-DD').format(); + if ((activationstart.localeCompare(curdate) < 0) && (activationend.localeCompare(curdate) > 0)) + isLoggedIn = true; + if (isLoggedIn == true) { + $rootScope.hidePreloader = true; + if (res.channel) + $rootScope.amGlobals.channel = res.channel; + if (res.username) + $rootScope.amGlobals.creator = res.username; + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') + } + $amRoot.dbAfterLogin(); + return; + } + } doLogin(); } diff --git a/src/app/app.service.js b/src/app/app.service.js index 27207a9b..1c5a8cee 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -8,6 +8,8 @@ /// (function() { + const ipcRenderer = require('electron').ipcRenderer; + angular.module('automintApp').service('$amRoot', AutomintService); AutomintService.$inject = ['$rootScope', '$state', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory']; @@ -97,7 +99,7 @@ console.error('Username/Password/Database name missing from url'); return; } - pdbMain.sync(url).on('change', onChangedDb).on('paused', onPausedDb).on('active', onActiveDb).on('denied', onDeniedDb).on('complete', onCompleteDb).on('error', onErrorDb); + $rootScope.amDbSync = pdbMain.sync(url).on('change', onChangedDb).on('paused', onPausedDb).on('active', onActiveDb).on('denied', onDeniedDb).on('complete', onCompleteDb).on('error', onErrorDb); function onChangedDb(info) { // listen to on change event @@ -145,7 +147,92 @@ live: true }).on('change', OnChangeMainDb); + if ($rootScope.checkAutomintValidity == undefined) + $rootScope.checkAutomintValidity = setInterval(checkAutomintValidity, 1000*60*60*24); + $state.go('restricted.dashboard'); + + function checkAutomintValidity() { + IsAutomintLoggedIn().then(checkLogin).catch(restartApp); + + function checkLogin(res) { + var curdate = moment().format(), isLoggedIn = false; + if (res.username && res.password) { + res.isLoggedIn = isLoggedIn; + if (!res.license) { + isLoggedIn = false; + return; + } else { + if ((res.license.starts == undefined) || (res.license.ends == undefined)) { + restartApp(); + return; + } + var startdate = moment(res.license.starts, 'YYYY-MM-DD').format(); + var enddate = moment(res.license.ends, 'YYYY-MM-DD').format(); + if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) + isLoggedIn = true; + } + if (isLoggedIn == true) { + $rootScope.hidePreloader = true; + if (res.channel) + $rootScope.amGlobals.channel = res.channel; + if (res.username) + $rootScope.amGlobals.creator = res.username; + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') + } + var isSyncableDb = ((res.username != undefined) && (res.username != '') && (res.password != undefined) && (res.password != '')); + if (!res.cloud) + isSyncableDb = false; + else if ((res.cloud.starts == undefined) || (res.cloud.ends == undefined)) + isSyncableDb = false; + else { + var cloudstart = moment(res.cloud.starts, 'YYYY-MM-DD').format(); + var cloudend = moment(res.cloud.ends, 'YYYY-MM-DD').format(); + isSyncableDb = ((cloudstart.localeCompare(curdate) < 0) && (cloudend.localeCompare(curdate) > 0)); + if (!isSyncableDb) + utils.showSimpleToast('Your cloud services have expired! Please Contact Automint Care!'); + } + if (!isSyncableDb) + $rootScope.amDbSync.cancel(); + return; + } + } + if (res.activation) { + res.isLoggedIn = isLoggedIn; + if ((res.activation.startdate == undefined) || (res.activation.enddate == undefined)) { + restartApp(); + return; + } + var activationstart = moment(res.activation.startdate, 'YYYY-MM-DD').format(); + var activationend = moment(res.activation.enddate, 'YYYY-MM-DD').format(); + if ((activationstart.localeCompare(curdate) < 0) && (activationend.localeCompare(curdate) > 0)) + isLoggedIn = true; + if (isLoggedIn == true) { + $rootScope.hidePreloader = true; + if (res.channel) + $rootScope.amGlobals.channel = res.channel; + if (res.username) + $rootScope.amGlobals.creator = res.username; + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') + } + return; + } + } + restartApp(); + } + + function restartApp(err) { + ipcRenderer.send('am-do-restart', true); + } + } } function OnChangeMainDb(change) { diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index 3d827b8f..a22b43de 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -17,7 +17,7 @@ function DashboardController($rootScope, $state, $filter, $log, $mdDialog, utils, amDashboard) { // initialize view model var vm = this; - var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false; + var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false, isCurrencyLoadedLate = false; // named assignments to keep track of UI vm.totalCustomersServed = 0; @@ -99,7 +99,14 @@ vm.IsNoNextDueReminders = IsNoNextDueReminders; vm.IsReminderInPast = IsReminderInPast; + // default execution steps + if ($rootScope.isFirstLoad == true) { + $rootScope.isFirstLoad = false; + isCurrencyLoadedLate = true; + $rootScope.hidePreloader = false; + setTimeout(getCurrencySymbol, 2000); + } if ($rootScope.isAmDbLoaded) defaultExecutionSteps(true, false); else @@ -112,7 +119,8 @@ initCurrentTimeSet(); getFilterMonths(); processPreferences(); - getCurrencySymbol(); + if (!isCurrencyLoadedLate) + getCurrencySymbol(); } } @@ -153,13 +161,16 @@ } function getCurrencySymbol() { + isCurrencyLoadedLate = false; amDashboard.getCurrencySymbol().then(success).catch(failure); function success(res) { + $rootScope.hidePreloader = true; vm.currencySymbol = res; } function failure(err) { + $rootScope.hidePreloader = true; openCurrencyDialog(); } } diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 9869f1d3..40f22e84 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -272,7 +272,6 @@ } function failure(err) { - console.log(err); // do nothing } } diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index 65df8647..7083caaa 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -19,7 +19,7 @@ var vm = this; // temporary named assignments - var channel = undefined; + var channel = undefined, license = undefined, cloud = undefined, isLoggedIn = false; // named assignments for view model vm.isLogingIn = false; @@ -36,12 +36,18 @@ vm.changeUserDetails = changeUserDetails; vm.submit = submit; vm.OnKeyDown = OnKeyDown; + vm.convertToCodeToUppercase = convertToCodeToUppercase; // default execution steps setTimeout(focusUsername, 300); // function definitions + function convertToCodeToUppercase() { + vm.code = vm.code.toUpperCase(); + changeCode(); + } + function focusUsername() { $('#ami-username').focus(); } @@ -88,60 +94,131 @@ amLogin.loadCredentials(vm.username, vm.password); amLogin.login(success, failure); } else if ((vm.code != undefined) && (vm.code != '')) { - $amRoot.dbAfterLogin(); - $location.path('/dashboard'); + amLogin.activate(vm.code, activate, failure) } - function success(res) { - if (res.data && (res.statusText == "OK")) { - if (res.data.userCtx && (res.data.userCtx.name != null) && res.data.userCtx.channels && (Object.keys(res.data.userCtx.channels).length > 0)) { - if (res.data.userCtx.channels.length <= 1) { - failure(); - return; - } - channel = Object.keys(res.data.userCtx.channels)[1]; - amLogin.saveLoginCredentials(true, vm.username, vm.password, Object.keys(res.data.userCtx.channels)[1]).then(proceed).catch(docNotSaved); - } + function activate(res) { + if (res.data == undefined) { + failure(); + return; + } + if (res.data.used) { + vm.message = "The code is already in use! You cannot use it again!"; + vm.isLogingIn = false; + return; } + var startdate = moment(res.data.starts, 'YYYY-MM-DD').format(); + var enddate = moment(res.data.ends, 'YYYY-MM-DD').format(); + var curdate = moment().format(); + if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) + isLoggedIn = true; + amLogin.saveActivationDetails(isLoggedIn, vm.code, res.data.starts, res.data.ends).then(proceed).catch(docNotSaved); + } - function proceed(res) { - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (channel ? '-' + channel : ''), - treatment: 'treatments' + (channel ? '-' + channel : ''), - inventory: 'inventory' + (channel ? '-' + channel : ''), - workshop: 'workshop' + (channel ? '-' + channel : '') + function success(res) { + if (res.data && res.data.mint_code) { + switch (res.data.mint_code) { + case "AU100": + processLicense(res.data.userCtx, res.data.license); + break; + case "AU200": + processMintCode(200); + break; + case "AU321": + case "AU322": + processMintCode(300); + break; + case "AU330": + processMintCode(330); + break; } - amLogin.changeExistingDocs().then(p1).catch(p1); + } else + failure(); - function p1(res) { - if ((vm.username != undefined) && (vm.username != '') && (vm.password != undefined) && (vm.password != '')) - $amRoot.syncDb(vm.username, vm.password); - $amRoot.dbAfterLogin(); - $location.path('/dashboard'); + function processLicense(userData, licenseData) { + if (userData && (userData.name != null) && userData.channels && (Object.keys(userData.channels).length > 1)) { + channel = Object.keys(userData.channels)[1]; + amLogin.saveLoginCredentials($rootScope.amGlobals.credentials.username, $rootScope.amGlobals.credentials.password, channel).then(checkLicense).catch(docNotSaved); + } else + checkLicense(); + + function checkLicense() { + if (licenseData) { + var startdate = moment(licenseData.license.starts, 'YYYY-MM-DD').format(); + var enddate = moment(licenseData.license.ends, 'YYYY-MM-DD').format(); + var curdate = moment().format(); + if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) + isLoggedIn = true; + amLogin.saveLicenseDetails(isLoggedIn, licenseData.license, licenseData.cloud).then(proceed).catch(docNotSaved); + } else + processMintCode(330); } } + } - function docNotSaved(err) { - console.error(err); - failure(); - } + function docNotSaved(err) { + console.error(err); + failure(); } - function failure(err) { - if (!err) { - vm.message = "Something went wrong! Please contact Automint Care!"; - vm.isLogingIn = false; + function proceed(res) { + if (!isLoggedIn) { + processMintCode(-1); return; } - switch (err.status) { - case 401: - vm.message = "Invalid Username/Password!"; + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (channel ? '-' + channel : ''), + treatment: 'treatments' + (channel ? '-' + channel : ''), + inventory: 'inventory' + (channel ? '-' + channel : ''), + workshop: 'workshop' + (channel ? '-' + channel : '') + } + + var isDbToBeChanged = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.credentials != undefined) && ($rootScope.amGlobals.credentials.username != undefined) && ($rootScope.amGlobals.credentials.username != '') && ($rootScope.amGlobals.credentials.password != undefined) && ($rootScope.amGlobals.credentials.password != '')); + if (isDbToBeChanged) + amLogin.changeExistingDocs().then(p1).catch(p1); + else + p1(); + + function p1(res) { + var isSyncableDb = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.credentials != undefined) && ($rootScope.amGlobals.credentials.username != undefined) && ($rootScope.amGlobals.credentials.username != '') && ($rootScope.amGlobals.credentials.password != undefined) && ($rootScope.amGlobals.credentials.password != '')); + if (($rootScope.amGlobals == undefined) || ($rootScope.amGlobals.validity == undefined) || ($rootScope.amGlobals.validity.cloud == undefined)) + isSyncableDb = false; + else { + var cloudstart = moment($rootScope.amGlobals.validity.cloud.starts, 'YYYY-MM-DD').format(); + var cloudend = moment($rootScope.amGlobals.validity.cloud.ends, 'YYYY-MM-DD').format(); + var curdate = moment().format(); + isSyncableDb = ((cloudstart.localeCompare(curdate) < 0) && (cloudend.localeCompare(curdate) > 0)); + if (!isSyncableDb) + utils.showSimpleToast('Your cloud services have expired! Please Contact Automint Care!'); + } + if (isSyncableDb) + $rootScope.amDbSync = $amRoot.syncDb($rootScope.amGlobals.credentials.username, $rootScope.amGlobals.credentials.password); + $amRoot.dbAfterLogin(); + $location.path('/dashboard'); + } + } + + function processMintCode(code) { + switch (code) { + case -1: + vm.message = "Your license has expired!"; + break; + case 200: + vm.message = "Incorrect Username or Password"; break; - default: - vm.message = "Please check your internet connectivity!"; + case 300: + vm.message = "There seems to be problem at Server! Please try again or contact Automint Care!"; + break; + case 330: + vm.message = "No Licensing Details Found for " + $rootScope.amGlobals.credentials.username + "! Please Try Again or Contact Automint Care!"; break; } vm.isLogingIn = false; + } + + function failure(err) { + vm.message = "Please check your internet connectivity!"; + vm.isLogingIn = false; console.error(err); } } diff --git a/src/app/login/login.factory.js b/src/app/login/login.factory.js index 5ae894d2..a176b0de 100644 --- a/src/app/login/login.factory.js +++ b/src/app/login/login.factory.js @@ -24,7 +24,10 @@ loadCredentials: loadCredentials, login: login, saveLoginCredentials: saveLoginCredentials, - changeExistingDocs: changeExistingDocs + changeExistingDocs: changeExistingDocs, + saveLicenseDetails: saveLicenseDetails, + activate: activate, + saveActivationDetails: saveActivationDetails } return factory; @@ -34,17 +37,106 @@ function loadCredentials(username, password) { if (!$rootScope.amGlobals) $rootScope.amGlobals = {}; - $rootScope.amGlobals.authHeaderData = amLoginBase64.encode(username + ':' + password); + $rootScope.amGlobals.credentials = { + username: username, + password: password + } } function login(success, failure) { - $http.defaults.headers.common['Authorization'] = 'Basic ' + $rootScope.amGlobals.authHeaderData; - $http.get(amFactory.generateAuthUrl(constants.sgw_w_main), { + $http({ + method: 'POST', + url: amFactory.generateAuthUrl(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: $.param({ + name: $rootScope.amGlobals.credentials.username, + password: $rootScope.amGlobals.credentials.password + }), timeout: 2000 }).then(success, failure); } - function saveLoginCredentials(isLoggedIn, username, password, channel) { + function activate(code, success, failure) { + $http.get(amFactory.generateActivationUrl(code)).then(success, failure); + } + + function saveActivationDetails(isLoggedIn, code, startdate, enddate) { + var tracker = $q.defer(); + pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(writeLoginDoc); + return tracker.promise; + + function getLoginDoc(res) { + res.isLoggedIn = isLoggedIn; + res.activation = { + code: code, + startdate: startdate, + enddate: enddate + } + pdbLocal.save(res).then(success).catch(failure); + } + + function writeLoginDoc(err) { + var doc = { + _id: constants.pdb_local_docs.login, + isLoggedIn: isLoggedIn, + activation: { + code: code, + startdate: startdate, + enddate: enddate + } + } + pdbLocal.save(doc).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function saveLicenseDetails(isLoggedIn, license, cloud) { + var tracker = $q.defer(); + if ($rootScope.amGlobals == undefined) + $rootScope.amGlobals = {}; + if ($rootScope.amGlobals.validity == undefined) + $rootScope.amGlobals.validity = {}; + $rootScope.amGlobals.validity.license = license; + $rootScope.amGlobals.validity.cloud = cloud; + pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(writeLoginDoc); + return tracker.promise; + + function getLoginDoc(res) { + res.isLoggedIn = isLoggedIn + res.license = license; + res.cloud = cloud; + pdbLocal.save(res).then(success).catch(failure); + } + + function writeLoginDoc(err) { + var doc = { + _id: constants.pdb_local_docs.login, + isLoggedIn: isLoggedIn, + license: license, + cloud: cloud + } + pdbLocal.save(doc).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } + } + + function saveLoginCredentials(username, password, channel) { var tracker = $q.defer(); if ($rootScope.amGlobals == undefined) $rootScope.amGlobals = {}; @@ -54,7 +146,6 @@ return tracker.promise; function getLoginDoc(res) { - res.isLoggedIn = isLoggedIn; res.username = username; res.password = password; res.channel = channel; @@ -64,7 +155,6 @@ function writeLoginDoc(err) { var doc = { _id: constants.pdb_local_docs.login, - isLoggedIn: isLoggedIn, username : username, password: password, channel: channel diff --git a/src/app/login/login.html b/src/app/login/login.html index 96003bfe..a11ad56d 100644 --- a/src/app/login/login.html +++ b/src/app/login/login.html @@ -48,7 +48,7 @@
- +
From 421e1131036a12aad757419784d82ce68de8b9b2 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 20 Aug 2016 15:52:05 +0530 Subject: [PATCH 68/91] Features #191 #185 [finished] 191. Cloud Sync Implementation 185. Licensing --- src/app/app.controller.js | 89 +---- src/app/app.service.js | 143 ++----- src/app/app.states.js | 1 + src/app/appbar/headerbar.controller.js | 6 + src/app/appbar/headerbar.html | 4 + .../customers/customers-add.controller.js | 19 +- .../customers/customers-edit.controller.js | 25 +- .../dashboard/dashboard.controller.js | 30 +- .../inventory/inventory-edit.controller.js | 19 +- .../inventory/inventory-viewall.controller.js | 13 +- .../invoices/invoices-view.controller.js | 31 +- .../services/services-add.controller.js | 41 +- .../services/services-edit.controller.js | 23 +- .../services/services-viewall.controller.js | 17 +- .../settings/settings.controller.js | 281 +++++++++++--- src/app/components/settings/settings.html | 84 ++++- .../tmpl/changepassword.controller.js | 108 ++++++ .../settings/tmpl/changepassword.html | 29 ++ .../memberships/memberships-add.controller.js | 15 +- .../memberships-edit.controller.js | 15 +- .../memberships-viewall.controller.js | 13 +- .../packages/packages-add.controller.js | 11 +- .../packages/packages-edit.controller.js | 19 +- .../packages/packages-viewall.controller.js | 11 +- .../regular/treatments-add.controller.js | 11 +- .../regular/treatments-edit.controller.js | 25 +- .../regular/treatments-viewall.controller.js | 15 +- src/app/login/login.controller.js | 136 +------ src/app/login/login.factory.js | 97 ++--- src/app/login/login.html | 2 +- src/app/login/login.service.js | 354 ++++++++++++++++++ src/app/views/initializing.html | 2 +- src/index.html | 5 +- 33 files changed, 1015 insertions(+), 679 deletions(-) create mode 100644 src/app/components/settings/tmpl/changepassword.controller.js create mode 100644 src/app/components/settings/tmpl/changepassword.html create mode 100644 src/app/login/login.service.js diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 4e41308d..0ddf4050 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -11,95 +11,30 @@ angular.module('automintApp').controller('amCtrl', HomeController); - HomeController.$inject = ['$rootScope', '$state', '$amRoot', 'utils', 'amLogin']; + HomeController.$inject = ['$rootScope', '$state', '$amRoot', '$amLicense', 'utils']; - function HomeController($rootScope, $state, $amRoot, utils, amLogin) { + function HomeController($rootScope, $state, $amRoot, $amLicense, utils, amLogin) { // initialize view model var vm = this; // default execution steps $rootScope.isFirstLoad = true; - $amRoot.IsAutomintLoggedIn().then(checkLoginState).catch(doLogin); + $amLicense.checkLogin().then(success).catch(failure); // function definitions - function checkLoginState(res) { - var curdate = moment().format(), isLoggedIn = false; - if (res.username && res.password) { - res.isLoggedIn = isLoggedIn; - if (!res.license) { - doLogin(); - return; - } else { - if ((res.license.starts == undefined) || (res.license.ends == undefined)) { - doLogin(); - return; - } - var startdate = moment(res.license.starts, 'YYYY-MM-DD').format(); - var enddate = moment(res.license.ends, 'YYYY-MM-DD').format(); - if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) - isLoggedIn = true; + function success(res) { + if (res.isLoggedIn) { + if (res.isCloudEnabled) { + if (res.isCloudForceEnabled != false) + $amRoot.syncDb(); } - if (isLoggedIn == true) { - $rootScope.hidePreloader = true; - if (res.channel) - $rootScope.amGlobals.channel = res.channel; - if (res.username) - $rootScope.amGlobals.creator = res.username; - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (res.channel ? '-' + res.channel : ''), - treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), - inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), - workshop: 'workshop' + (res.channel ? '-' + res.channel : '') - } - var isSyncableDb = ((res.username != undefined) && (res.username != '') && (res.password != undefined) && (res.password != '')); - if (!res.cloud) - isSyncableDb = false; - else if ((res.cloud.starts == undefined) || (res.cloud.ends == undefined)) - isSyncableDb = false; - else { - var cloudstart = moment(res.cloud.starts, 'YYYY-MM-DD').format(); - var cloudend = moment(res.cloud.ends, 'YYYY-MM-DD').format(); - isSyncableDb = ((cloudstart.localeCompare(curdate) < 0) && (cloudend.localeCompare(curdate) > 0)); - if (!isSyncableDb) - utils.showSimpleToast('Your cloud services have expired! Please Contact Automint Care!'); - } - if (isSyncableDb) - $amRoot.syncDb(res.username, res.password); - $amRoot.dbAfterLogin(); - return; - } - } - if (res.activation) { - res.isLoggedIn = isLoggedIn; - if ((res.activation.startdate == undefined) || (res.activation.enddate == undefined)) { - doLogin(); - return; - } - var activationstart = moment(res.activation.startdate, 'YYYY-MM-DD').format(); - var activationend = moment(res.activation.enddate, 'YYYY-MM-DD').format(); - if ((activationstart.localeCompare(curdate) < 0) && (activationend.localeCompare(curdate) > 0)) - isLoggedIn = true; - if (isLoggedIn == true) { - $rootScope.hidePreloader = true; - if (res.channel) - $rootScope.amGlobals.channel = res.channel; - if (res.username) - $rootScope.amGlobals.creator = res.username; - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (res.channel ? '-' + res.channel : ''), - treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), - inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), - workshop: 'workshop' + (res.channel ? '-' + res.channel : '') - } - $amRoot.dbAfterLogin(); - return; - } - } - doLogin(); + $amRoot.dbAfterLogin(false); + } else + failure(); } - function doLogin(err) { + function failure(err) { $rootScope.hidePreloader = true; $state.go('login'); } diff --git a/src/app/app.service.js b/src/app/app.service.js index 1c5a8cee..cb2dd3da 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -34,6 +34,12 @@ $rootScope.amGlobals.creator = ''; if ($rootScope.amGlobals.channel == undefined) $rootScope.amGlobals.channel = ''; + if ($rootScope.busyApp == undefined) { + $rootScope.busyApp = { + show: false, + message: '' + } + } // named assignments to keep track of current configuration of application service @@ -41,7 +47,6 @@ $rootScope.amGlobals.IsConfigDoc = IsConfigDoc; vm.initDb = initDb; vm.syncDb = syncDb; - vm.IsAutomintLoggedIn = IsAutomintLoggedIn; vm.dbAfterLogin = dbAfterLogin; // function definitions @@ -64,32 +69,10 @@ pdbMain.setDatabase(constants.pdb_main); pdbCache.setDatabase(constants.pdb_cache); pdbLocal.setDatabase(constants.pdb_local); - - // pre-execution steps - IsAutomintLoggedIn().then(getLoginDoc).catch(failure); - - function getLoginDoc(res) { - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (res.channel ? '-' + res.channel : ''), - treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), - inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), - workshop: 'workshop' + (res.channel ? '-' + res.channel : '') - } - $rootScope.isAmDbLoaded = true; - } - - function failure(err) { - $rootScope.amGlobals.configDocIds = { - settings: 'settings', - treatment: 'treatments', - inventory: 'inventory', - workshop: 'workshop' - } - $rootScope.isAmDbLoaded = true; - } } - function syncDb(username, password) { + function syncDb() { + var username = $rootScope.amGlobals.credentials.username, password = $rootScope.amGlobals.credentials.password; if ((username == '') || (username == undefined) || (password == '') || (password == undefined)) { console.error('No Username or Password provided for database sync'); return; @@ -103,12 +86,12 @@ function onChangedDb(info) { // listen to on change event - console.info(info); + console.info('pdbSync', info); } function onPausedDb(err) { // listen to on pause event\ - console.warn(err); + console.warn('pdbSync', err); } function onActiveDb() { @@ -117,26 +100,21 @@ function onDeniedDb(err) { // listen to on denied event - console.error(err); + console.error('pdbSync', err); } function onCompleteDb(info) { // listen to on complete event - console.info(info); + console.info('pdbSync', info); } function onErrorDb(err) { // listen to on error event - console.error(err); + console.error('pdbSync', err); } } - // check for login - function IsAutomintLoggedIn() { - return pdbLocal.get(constants.pdb_local_docs.login); - } - - function dbAfterLogin() { + function dbAfterLogin(wait) { // handle database migration if any and generate cache docs after the process // no such migrations right now generateCacheDocs(); @@ -150,86 +128,33 @@ if ($rootScope.checkAutomintValidity == undefined) $rootScope.checkAutomintValidity = setInterval(checkAutomintValidity, 1000*60*60*24); - $state.go('restricted.dashboard'); + if (wait) + setTimeout(transit, 2000); + else + $state.go('restricted.dashboard'); + + function transit() { + $state.go('restricted.dashboard'); + } function checkAutomintValidity() { - IsAutomintLoggedIn().then(checkLogin).catch(restartApp); - - function checkLogin(res) { - var curdate = moment().format(), isLoggedIn = false; - if (res.username && res.password) { - res.isLoggedIn = isLoggedIn; - if (!res.license) { - isLoggedIn = false; - return; - } else { - if ((res.license.starts == undefined) || (res.license.ends == undefined)) { - restartApp(); - return; - } - var startdate = moment(res.license.starts, 'YYYY-MM-DD').format(); - var enddate = moment(res.license.ends, 'YYYY-MM-DD').format(); - if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) - isLoggedIn = true; - } - if (isLoggedIn == true) { - $rootScope.hidePreloader = true; - if (res.channel) - $rootScope.amGlobals.channel = res.channel; - if (res.username) - $rootScope.amGlobals.creator = res.username; - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (res.channel ? '-' + res.channel : ''), - treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), - inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), - workshop: 'workshop' + (res.channel ? '-' + res.channel : '') - } - var isSyncableDb = ((res.username != undefined) && (res.username != '') && (res.password != undefined) && (res.password != '')); - if (!res.cloud) - isSyncableDb = false; - else if ((res.cloud.starts == undefined) || (res.cloud.ends == undefined)) - isSyncableDb = false; - else { - var cloudstart = moment(res.cloud.starts, 'YYYY-MM-DD').format(); - var cloudend = moment(res.cloud.ends, 'YYYY-MM-DD').format(); - isSyncableDb = ((cloudstart.localeCompare(curdate) < 0) && (cloudend.localeCompare(curdate) > 0)); - if (!isSyncableDb) - utils.showSimpleToast('Your cloud services have expired! Please Contact Automint Care!'); - } - if (!isSyncableDb) + $amLicense.checkLogin().then(success).catch(failure); + + function success(res) { + if (res.isLoggedIn) { + if ((!res.isCloudEnabled) || (res.isCloudEnabled && (res.isCloudForceEnabled == false))) { + if ($rootScope.amDbSync) $rootScope.amDbSync.cancel(); - return; - } - } - if (res.activation) { - res.isLoggedIn = isLoggedIn; - if ((res.activation.startdate == undefined) || (res.activation.enddate == undefined)) { - restartApp(); - return; - } - var activationstart = moment(res.activation.startdate, 'YYYY-MM-DD').format(); - var activationend = moment(res.activation.enddate, 'YYYY-MM-DD').format(); - if ((activationstart.localeCompare(curdate) < 0) && (activationend.localeCompare(curdate) > 0)) - isLoggedIn = true; - if (isLoggedIn == true) { - $rootScope.hidePreloader = true; - if (res.channel) - $rootScope.amGlobals.channel = res.channel; - if (res.username) - $rootScope.amGlobals.creator = res.username; - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (res.channel ? '-' + res.channel : ''), - treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), - inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), - workshop: 'workshop' + (res.channel ? '-' + res.channel : '') - } - return; } + } else { + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = "Your license has expired! Try Loging Again or Contact Automint Care!"; + setTimeout(failure, 1000); } - restartApp(); } - function restartApp(err) { + function failure(err) { + $rootScope.busyApp.message = "Your license has expired! Restarting App. Please Wait..."; ipcRenderer.send('am-do-restart', true); } } diff --git a/src/app/app.states.js b/src/app/app.states.js index 68e52c2d..c639888e 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -541,6 +541,7 @@ } function loadSettingsDeps($ocLazyLoad) { return $ocLazyLoad.load([ + 'app/components/settings/tmpl/changepassword.controller.js', 'app/components/settings/settings.controller.js', 'app/components/settings/settings-backup.factory.js', 'app/components/settings/settings-login.factory.js', diff --git a/src/app/appbar/headerbar.controller.js b/src/app/appbar/headerbar.controller.js index b1235cdc..0df47f2a 100644 --- a/src/app/appbar/headerbar.controller.js +++ b/src/app/appbar/headerbar.controller.js @@ -9,6 +9,7 @@ (function() { const ammHelp = require('./automint_modules/am-help.js'); + const ipcRenderer = require('electron').ipcRenderer; angular.module('automintApp').controller('amCtrlHeaderbar', HeaderBarController); @@ -22,6 +23,7 @@ vm.openLockScreen = openLockScreen; vm.openHelpWindow = openHelpWindow; vm.addService = addService; + vm.relaunch = relaunch; // default execution steps if ($rootScope.isAmDbLoaded) @@ -31,6 +33,10 @@ // function definitions + function relaunch() { + ipcRenderer.send('am-do-restart', true); + } + function headerbarDefaults(newValue, oldValue) { if (newValue) { $rootScope.hidePreloader = true; diff --git a/src/app/appbar/headerbar.html b/src/app/appbar/headerbar.html index 2955c7a3..efaaac4e 100644 --- a/src/app/appbar/headerbar.html +++ b/src/app/appbar/headerbar.html @@ -7,6 +7,10 @@
{{ module_title }}
+ + refresh + Relaunch App + add Add Service diff --git a/src/app/components/customers/customers-add.controller.js b/src/app/components/customers/customers-add.controller.js index 988b95df..8819c5bd 100644 --- a/src/app/components/customers/customers-add.controller.js +++ b/src/app/components/customers/customers-add.controller.js @@ -62,22 +62,13 @@ vm.changeVehicle = changeVehicle; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); - + setTimeout(focusCustomerName, 300); + getMemberships(); + getRegularTreatments(); + getVehicleTypes(); + // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - setTimeout(focusCustomerName, 300); - getMemberships(); - getRegularTreatments(); - getVehicleTypes(); - } - } - function IsVehicleSelected(id) { return (vm.currentVehicleId == id); } diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 5894716c..c9560cd2 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -80,29 +80,20 @@ vm.IsVehicleAnonymous = IsVehicleAnonymous; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + if ($state.params.id != undefined) { + getCurrencySymbol(); + getMemberships(getRegularTreatments, getVehicleTypes, getCustomer); + setTimeout(focusCustomerMobile, 300); + } else { + utils.showSimpleToast('Something went wrong!'); + $state.go('restricted.customers.all'); + } // function definitions function IsVehicleAnonymous(service) { return (service.vhcl_reg == 'Vehicle'); } - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - if ($state.params.id != undefined) { - getCurrencySymbol(); - getMemberships(getRegularTreatments, getVehicleTypes, getCustomer); - setTimeout(focusCustomerMobile, 300); - } else { - utils.showSimpleToast('Something went wrong!'); - $state.go('restricted.customers.all'); - } - } - } - function checkExistingCustomerMobile(ev) { if (vm.user.mobile == '') diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index a22b43de..6ea43057 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -17,7 +17,7 @@ function DashboardController($rootScope, $state, $filter, $log, $mdDialog, utils, amDashboard) { // initialize view model var vm = this; - var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false, isCurrencyLoadedLate = false; + var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false; // named assignments to keep track of UI vm.totalCustomersServed = 0; @@ -101,29 +101,14 @@ // default execution steps - if ($rootScope.isFirstLoad == true) { - $rootScope.isFirstLoad = false; - isCurrencyLoadedLate = true; - $rootScope.hidePreloader = false; - setTimeout(getCurrencySymbol, 2000); - } - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + $rootScope.hidePreloader = true; + initCurrentTimeSet(); + getFilterMonths(); + processPreferences(); + getCurrencySymbol(); // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - initCurrentTimeSet(); - getFilterMonths(); - processPreferences(); - if (!isCurrencyLoadedLate) - getCurrencySymbol(); - } - } - function openCurrencyDialog() { $mdDialog.show({ controller: 'amCtrlDashCurrency', @@ -161,16 +146,13 @@ } function getCurrencySymbol() { - isCurrencyLoadedLate = false; amDashboard.getCurrencySymbol().then(success).catch(failure); function success(res) { - $rootScope.hidePreloader = true; vm.currencySymbol = res; } function failure(err) { - $rootScope.hidePreloader = true; openCurrencyDialog(); } } diff --git a/src/app/components/inventory/inventory-edit.controller.js b/src/app/components/inventory/inventory-edit.controller.js index 1a28e765..95991539 100644 --- a/src/app/components/inventory/inventory-edit.controller.js +++ b/src/app/components/inventory/inventory-edit.controller.js @@ -33,22 +33,13 @@ vm.convertNameToTitleCase = convertNameToTitleCase; vm.save = save; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + if ($state.params.name == undefined) { + OnFailedHit(); + return; + } + getInventory(); // function definitions - - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - if ($state.params.name == undefined) { - OnFailedHit(); - return; - } - getInventory(); - } - } function OnFailedHit() { utils.showSimpleToast('Something went wrong! Please Try Again!'); diff --git a/src/app/components/inventory/inventory-viewall.controller.js b/src/app/components/inventory/inventory-viewall.controller.js index f542c8a4..dfff27fe 100644 --- a/src/app/components/inventory/inventory-viewall.controller.js +++ b/src/app/components/inventory/inventory-viewall.controller.js @@ -31,20 +31,11 @@ vm.changeDisplayAsList = changeDisplayAsList; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + getInventories(); + getDisplayAsList(); // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getInventories(); - getDisplayAsList(); - } - } - function getDisplayAsList() { amInventory.getDisplayAsList().then(success).catch(failure); diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 81ba0b7a..6b78c7f3 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -69,29 +69,20 @@ // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { + utils.showSimpleToast('Something went wrong. Please try again!') + $state.go('restricted.services.all'); + return; + } + getCurrencySymbol(); + fillInvoiceDetails(); + loadInvoiceWLogo(); + loadInvoiceFLogo(); + getIvAlignMargins(); + getInvoicePageSize(); // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - if ($state.params.userId == undefined || $state.params.vehicleId == undefined || $state.params.serviceId == undefined) { - utils.showSimpleToast('Something went wrong. Please try again!') - $state.go('restricted.services.all'); - return; - } - getCurrencySymbol(); - fillInvoiceDetails(); - loadInvoiceWLogo(); - loadInvoiceFLogo(); - getIvAlignMargins(); - getInvoicePageSize(); - } - } - function IsCustomerNotAnonymus() { return (vm.user.name != 'Anonymous'); } diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index ebf6ebff..a660f158 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -206,34 +206,25 @@ // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + setCoverPic(); + changeUserInfoState(true); // ammToDo: Enable this while commiting + setTimeout(focusUserName, 700); + // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting + buildDelayedToggler('service-details-left'); + getCurrencySymbol(); + getDefaultServiceType(); + getTreatmentDisplayFormat(); + getInventoriesSettings(); + getVehicleTypes(); + getRegularTreatments(); + getInventories(); + getMemberships(); + getLastInvoiceNo(); + getLastEstimateNo(); + getLastJobCardNo(); // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - setCoverPic(); - changeUserInfoState(true); // ammToDo: Enable this while commiting - setTimeout(focusUserName, 700); - // changeServiceInfoState(true); // ammToDo: Testing Purpose, Disable while commiting - buildDelayedToggler('service-details-left'); - getCurrencySymbol(); - getDefaultServiceType(); - getTreatmentDisplayFormat(); - getInventoriesSettings(); - getVehicleTypes(); - getRegularTreatments(); - getInventories(); - getMemberships(); - getLastInvoiceNo(); - getLastEstimateNo(); - getLastJobCardNo(); - } - } - function openCustomerProfile() { $state.go('restricted.customers.edit', { id: vm.user.id diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index ef4b705f..565e104a 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -200,25 +200,16 @@ $(window).on('resize', OnWindowResize); // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + setCoverPic(); + buildDelayedToggler('service-details-left'); + changeServiceInfoState(true); + getCurrencySymbol(); + getVehicleTypes(); + getPackages(); + getMemberships(); // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - setCoverPic(); - buildDelayedToggler('service-details-left'); - changeServiceInfoState(true); - getCurrencySymbol(); - getVehicleTypes(); - getPackages(); - getMemberships(); - } - } - function openCustomerProfile() { $state.go('restricted.customers.edit', { id: vm.user.id diff --git a/src/app/components/services/services-viewall.controller.js b/src/app/components/services/services-viewall.controller.js index 3d30700f..1abc5aaa 100644 --- a/src/app/components/services/services-viewall.controller.js +++ b/src/app/components/services/services-viewall.controller.js @@ -54,10 +54,10 @@ // default execution steps $scope.$watch('vm.serviceQuery', watchServiceQuery); - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + getCurrencySymbol(); + getFilterMonths(processPreferences); + initCurrentTimeSet(); + getServices(); // function definitions @@ -65,15 +65,6 @@ return (reg == 'Vehicle'); } - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getCurrencySymbol(); - getFilterMonths(processPreferences); - initCurrentTimeSet(); - getServices(); - } - } - function IsCustomerAnonymous(name) { return (name == 'Anonymous'); } diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 40f22e84..47a457d0 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -17,18 +17,24 @@ angular.module('automintApp').controller('amCtrlSettings', SettingsController); - SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', 'utils', 'amBackup', 'amLoginSettings', 'amImportdata', 'amIvSettings', 'amTaxSettings', 'amSettings']; + SettingsController.$inject = ['$rootScope', '$scope', '$state', '$log', '$mdDialog', '$amLicense', '$amRoot', 'utils', 'amBackup', 'amLoginSettings', 'amImportdata', 'amIvSettings', 'amTaxSettings', 'amSettings', 'amLogin']; - function SettingsController($rootScope, $scope, $state, $log, utils, amBackup, amLoginSettings, amImportdata, amIvSettings, amTaxSettings, amSettings) { + function SettingsController($rootScope, $scope, $state, $log, $mdDialog, $amLicense, $amRoot, utils, amBackup, amLoginSettings, amImportdata, amIvSettings, amTaxSettings, amSettings, amLogin) { // initialize view model var vm = this; - + // temporary named assignments - var olino = 0, oljbno = 0, oleno = 0; - var oPasscode = '1234', oPasscodeEnabled; - var oIvAlignTop = '', oIvAlignBottom = ''; + var olino = 0, + oljbno = 0, + oleno = 0; + var oPasscode = '1234', + oPasscodeEnabled; + var oIvAlignTop = '', + oIvAlignBottom = ''; var ivEmailSubject, oIvFacebookLink, oIvInstagramLink, oIvTwitterLink, oIvWorkshopName, oIvWorkshopPhone, oIvWorkshopAddress1, oIvWorkshopAddress2, oIvWorkshopCity, oDefaultServiceType; var currentTaxFocusIndex = -1; + var csMessage, + isAutomintLoggedIn; // named assignments to keep track of UI [BEGIN] vm.user = { @@ -68,8 +74,18 @@ vm.currencySymbol = "Rs."; vm.invoicePageSizeList = ['Single Page', 'A4']; vm.invoicePageSize = vm.invoicePageSizeList[1]; + vm.cloudSettings = { + username: '', + password: '', + message: undefined, + enable: false, + isLicensed: false, + cloudenddate: undefined, + isCloudEnabled: false + }; + vm.isLoginBoxOpen = false; // named assignments to keep track of UI [END] - + // function maps [BEGIN] vm.changeInvoiceTab = changeInvoiceTab; vm.changeUsernameLabel = changeUsernameLabel; @@ -124,6 +140,12 @@ vm.saveInvoicePageSize = saveInvoicePageSize; vm.changeInvoiceFLogo = changeInvoiceFLogo; vm.uploadInvoiceFLogo = uploadInvoiceFLogo; + vm.changeCloudTab = changeCloudTab; + vm.cloudSettings.OnKeyDown = csOnKeyDown; + vm.cloudSettings.changeUserDetails = csChangeuserDetails; + vm.cloudSettings.submit = csSubmit; + vm.cloudSettings.changeAvailability = csChangeAvailability; + vm.cloudSettings.changePassword = csChangePassword; // function maps [END] // default execution steps [BEGIN] @@ -134,33 +156,174 @@ default: break; } - - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + loadBlock(); // default execution steps [END] // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getPasscode(); - getDefaultServiceType(); - getAmAppDataPath(); - getWorkshopDetails(); - getInvoiceSettings(); - loadInvoiceWLogo(); - loadInvoiceFLogo(); - getIvAlignMargins(); - getAllTaxSettings(); - getCurrencySymbol(); - getInvoicePageSize(); - // changeInvoiceTab(true) // testing purposes amTODO: remove it - // changeTaxTab(true); // testing purposes amTODO: remove it + function loadBlock() { + if ($rootScope.busyApp.show == true) + $rootScope.busyApp.message = 'Loading Data'; + getPasscode(); + getDefaultServiceType(); + getAmAppDataPath(); + getWorkshopDetails(); + getInvoiceSettings(); + loadInvoiceWLogo(); + loadInvoiceFLogo(); + getIvAlignMargins(); + getAllTaxSettings(); + getCurrencySymbol(); + getInvoicePageSize(); + // changeCloudTab(true) // testing purposes amTODO: remove it + getLoginState(); + // changeInvoiceTab(true) // testing purposes amTODO: remove it + // changeTaxTab(true); // testing purposes amTODO: remove it + } + + function csChangePassword(event) { + $mdDialog.show({ + controller: 'amCtrlSeChPwd', + controllerAs: 'vm', + templateUrl: 'app/components/settings/tmpl/changepassword.html', + parent: angular.element(document.body), + targetEvent: event, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (res == true) { + if ($rootScope.amDbSync) + $rootScope.amDbSync.cancel(); + $amRoot.syncDb(); + getLoginState(); + } + } + } + + function csChangeAvailability() { + vm.isLoginBoxOpen = vm.cloudSettings.enable; + if (vm.cloudSettings.isLicensed && vm.cloudSettings.isCloudEnabled) + amLogin.cloudForceEnabled(vm.cloudSettings.enable).then(success).catch(failure); + + function success(res) { + if (vm.cloudSettings.enable == true) { + if ($rootScope.amDbSync) + $rootScope.amDbSync.cancel(); + $amRoot.syncDb(); + } else if ($rootScope.amDbSync) + $rootScope.amDbSync.cancel(); + vm.cloudSettings.isCloudForceEnabled = vm.cloudSettings.enable; + + utils.showSimpleToast('Automint Cloud Services has been ' + (vm.cloudSettings.enable ? 'enabled' : 'disabled') + ' successfully'); + } + + function failure(failure) { + utils.showSimpleToast('Could not ' + (vm.cloudSettings.enable ? 'enable' : 'disable') +' Audomint Cloud Service at moment! Please Try Again!'); + vm.cloudSettings.enable = vm.cloudSettings.isCloudForceEnabled; + } + } + + function getLoginState() { + $amLicense.checkLogin().then(success).catch(failure); + + function success(res) { + vm.cloudSettings.enable = ((res.isCloudEnabled != undefined) ? res.isCloudEnabled : false); + if (vm.cloudSettings.enable == true) + vm.cloudSettings.isCloudForceEnabled = ((res.isCloudForceEnabled != undefined) ? res.isCloudForceEnabled : vm.cloudSettings.enable); + vm.cloudSettings.enable = vm.cloudSettings.isCloudForceEnabled; + vm.cloudSettings.isCloudEnabled = res.isCloudEnabled; + vm.cloudSettings.isLicensed = ((res.isLoggedIn == true) && (res.type == 'license')); + vm.cloudSettings.cloudenddate = ((res.cloudenddate != undefined) ? res.cloudenddate : undefined); + if (res.type != 'license') + csMessage = 'You need to login to enable Automint Cloud Services'; + else { + vm.cloudSettings.username = $rootScope.amGlobals.credentials.username; + if (res.cloudenddate) { + var ced = moment(res.cloudenddate, 'YYYY-MM-DD').format('D MMMM YYYY'); + csMessage = (res.isCloudEnabled == true) ? ('Your license expires on ' + ced) : ('Your license has expired on ' + ced + '. Please contact Automint Care to extend it'); + } + } + vm.cloudSettings.message = csMessage; + vm.isLoginBoxOpen = vm.cloudSettings.enable; + } + + function failure(err) { + $mdDialog.show( + $mdDialog.alert() + .parent(document.body) + .clickOutsideToClose(false) + .title('License Error!') + .textContent('Automint cannot identify your license. Please Login Again or Contact Automint Care!') + .ariaLabel('License Error') + .ok('Got it!') + ).then(restartApp).catch(restartApp); + + function restartApp(res) { + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Restarting App due to License Error. Please Wait..'; + ipcRenderer.send('am-do-restart', true); + } + console.error(err); + } + } + + function csSubmit() { + if ((vm.cloudSettings.username == '') && (vm.cloudSettings.password == '')) { + vm.cloudSettings.message = 'Please Enter Username and Password!'; + return; + } + if ((vm.cloudSettings.username != '') && (vm.cloudSettings.password == '')) { + vm.cloudSettings.message = 'Please Enter Password!'; + return; + } + if ((vm.cloudSettings.password != '') && (vm.cloudSettings.username == '')) { + vm.cloudSettings.message = 'Please Enter Username!'; + return; + } + + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Loging In'; + $amLicense.loadCredentials(vm.cloudSettings.username, vm.cloudSettings.password); + $amLicense.login(false).then(success).catch(failure); + + function success(res) { + if (res.isLoggedIn) { + vm.cloudSettings.isLicensed = (res.isLoggedIn == true); + if (res.isSyncableDb) { + $amRoot.syncDb(); + $rootScope.busyApp.message = 'Syncing'; + setTimeout(syncDone, 2000); + } else + $rootScope.busyApp.show = false; + } else + getLoginState(); + + function syncDone() { + loadBlock(); + $rootScope.busyApp.show = false; + } + } + + function failure(err) { + $rootScope.busyApp.show = false; + vm.cloudSettings.message = err; } } + function csChangeuserDetails() { + vm.cloudSettings.message = csMessage; + } + + function csOnKeyDown(event) { + if (event.keyCode == 13) + csSubmit(); + } + + function changeCloudTab(bool) { + vm.cloudTab = true; + } + function uploadInvoiceFLogo() { angular.element(document.querySelector('#am-upload-invoice-f-logo')).click(); } @@ -190,7 +353,7 @@ loadInvoiceFLogo(); } } - + // load workshop logo in invoice settings function loadInvoiceFLogo() { var source = localStorage.getItem('invoice-f-pic'); @@ -333,9 +496,12 @@ } function setAmAppDataPath(move) { - var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); + var newPath = dialog.showOpenDialog({ + properties: ['openDirectory'] + }); if (newPath) { - $rootScope.isAppBeingRestarted = true; + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Restarting App. Please Wait..'; ammPreferences.storePreference('automint.userDataPath', newPath[0]); if (move) { fse.copy(amApp.getPath('userData'), newPath[0], { @@ -344,7 +510,7 @@ } else removeSuccess(); } - + function success(res) { if (res) console.error(res); @@ -444,7 +610,7 @@ function resetLastEstimateNo(estimateno, reset) { amIvSettings.changeLastEstimateNo((estimateno == undefined) ? 0 : estimateno).then(respond).catch(respond); - + function respond(res) { getInvoiceSettings(); if (reset != true) @@ -571,12 +737,12 @@ return; saveWorkshopDetails(); } - + // default tab settings function changeInvoiceTab(bool) { vm.invoiceTab = bool; } - + function OnBlurLastInvoiceNumber(ar, arg) { if (vm.ivSettings.lastInvoiceNumber == '' || vm.ivSettings.lastInvoiceNumber == null || vm.ivSettings.lastInvoiceNumber == undefined) vm.ivSettings.lastInvoiceNumber = 0; @@ -584,13 +750,14 @@ return; resetLastInvoiceNo(vm.ivSettings.lastInvoiceNumber); } - + // listen to changes in input fields [BEGIN] // general settings function changeUsernameLabel(force) { vm.isUsername = (force != undefined || vm.user.username != ''); vm.label_username = vm.isUsername ? 'Username:' : 'Enter Username:'; } + function changePasswordLabel(force) { vm.isPassword = (force != undefined || vm.user.password != ''); vm.label_password = vm.isPassword ? 'Password:' : 'Enter Password:'; @@ -600,18 +767,22 @@ vm.isWorkshopName = (force != undefined || vm.workshop.name != ''); vm.label_workshopName = vm.isWorkshopName ? 'Workshop Name:' : 'Enter Workshop Name:'; } + function changeWorkshopPhoneLabel(force) { vm.isWorkshopPhone = (force != undefined || vm.workshop.phone != ''); vm.label_workshopPhone = vm.isWorkshopPhone ? 'Phone Number:' : 'Enter Phone Number:'; } + function changeWorkshopAddress1Label(force) { vm.isWorkshopAddress1 = (force != undefined || vm.workshop.address1 != ''); vm.label_workshopAddress1 = vm.isWorkshopAddress1 ? 'Address Line 1:' : 'Enter Address Line 1:'; } + function changeWorkshopAddress2Label(force) { vm.isWorkshopAddress2 = (force != undefined || vm.workshop.address2 != ''); vm.label_workshopAddress2 = vm.isWorkshopAddress2 ? 'Address Line 2:' : 'Enter Address Line 2:'; } + function changeWorkshopCityLabel(force) { vm.isWorkshopCity = (force != undefined || vm.workshop.city != ''); vm.label_workshopCity = vm.isWorkshopCity ? 'City:' : 'Enter City:'; @@ -623,6 +794,7 @@ function uploadCSV() { angular.element(document.querySelector('#csv-file-select')).click(); } + function uploadCover() { angular.element(document.querySelector('#am-upload-cover-pic')).click(); } @@ -673,7 +845,7 @@ function cleanUp(res) { amImportdata.cleanUp(); - defaultExecutionSteps(true, false); + loadBlock(); } } @@ -686,12 +858,12 @@ $log.info(res); } } - + // functions defined for invoice settings // get workshop details from database function getWorkshopDetails() { amIvSettings.getWorkshopDetails().then(getWorkshopObject).catch(failure); - + function getWorkshopObject(res) { vm.workshop = res; oIvWorkshopName = res.name; @@ -708,7 +880,7 @@ changeWorkshopAddress2Label(); changeWorkshopCityLabel(); } - + function failure(err) { // do nothing } @@ -743,11 +915,11 @@ return; saveWorkshopDetails(); } - + // save workshop details to database function saveWorkshopDetails() { amIvSettings.saveWorkshopDetails(vm.workshop).then(success).catch(failure); - + function success(res) { oIvWorkshopName = vm.workshop.name; oIvWorkshopPhone = vm.workshop.phone; @@ -759,16 +931,16 @@ oIvInstagramLink = vm.workshop.social.instagram; utils.showSimpleToast('Workshop details updated successfully!'); } - + function failure(err) { utils.showSimpleToast('Failied to update workshop details! Please Try Again!'); } } - + // get invoice settings function getInvoiceSettings() { amIvSettings.getInvoiceSettings().then(success).catch(failure); - + function success(res) { vm.ivSettings = res; if (!vm.ivSettings.lastJobCardNo) @@ -777,18 +949,18 @@ vm.ivSettings.lastEstimateNo = 0; if (!vm.ivSettings.lastInvoiceNumber) vm.ivSettings.lastInvoiceNumber = 0; - + olino = res.lastInvoiceNumber; oljbno = res.lastJobCardNo; oleno = res.lastEstimateNo; ivEmailSubject = res.emailsubject; } - + function failure(err) { // do nothing } } - + // change workshop logo in invoice settings function changeInvoiceWLogo(e) { var files = e.target.files; @@ -815,41 +987,42 @@ loadInvoiceWLogo(); } } - + // load workshop logo in invoice settings function loadInvoiceWLogo() { var source = localStorage.getItem('invoice-w-pic'); vm.invoiceWLogo = source; } - + // reset invoice number sequence function resetLastInvoiceNo(invoiceno, reset) { amIvSettings.changeLastInvoiceNo((invoiceno == undefined) ? 0 : invoiceno).then(respond).catch(respond); - + function respond(res) { getInvoiceSettings(); if (reset != true) utils.showActionToast('Invoice number has been ' + ((invoiceno == undefined) ? 'reset' : 'changed'), 'Undo', resetLastInvoiceNo, olino, true); } } - + // change display settings function saveIvDisplaySettings() { amIvSettings.saveIvDisplaySettings(vm.ivSettings.display).then(success).catch(failure); - + function success(res) { utils.showSimpleToast('Settings saved successfully!'); } + function failure(err) { utils.showSimpleToast('Could not save settings at moment. Please try again!'); } } - + function saveIvEmailSubject(es, reset) { if (vm.ivSettings.emailsubject == undefined || ((ivEmailSubject == vm.ivSettings.emailsubject) && !es)) return; amIvSettings.saveIvEmailSubject((es == undefined) ? vm.ivSettings.emailsubject : es).then(respond).catch(respond); - + function respond(res) { getInvoiceSettings(); if (ivEmailSubject != undefined) { diff --git a/src/app/components/settings/settings.html b/src/app/components/settings/settings.html index 545cf29b..7d8c268c 100644 --- a/src/app/components/settings/settings.html +++ b/src/app/components/settings/settings.html @@ -96,7 +96,7 @@ - +
@@ -197,7 +197,7 @@
- +
@@ -398,7 +398,7 @@ background: #FAFAFA; } - + @@ -441,5 +441,83 @@ + + + + + +
+ Automint Cloud + + Enable Cloud Services +
+ +
+
+
+
\ No newline at end of file diff --git a/src/app/components/settings/tmpl/changepassword.controller.js b/src/app/components/settings/tmpl/changepassword.controller.js new file mode 100644 index 00000000..5739b0df --- /dev/null +++ b/src/app/components/settings/tmpl/changepassword.controller.js @@ -0,0 +1,108 @@ +/** + * Controller for Change Password Dialog box + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + const ipcRenderer = require('electron').ipcRenderer; + + angular.module('automintApp').controller('amCtrlSeChPwd', ChangePasswordController); + + ChangePasswordController.$inject = ['$rootScope', '$amLicense', '$mdDialog', '$amRoot']; + + function ChangePasswordController($rootScope, $amLicense, $mdDialog, $amRoot) { + // initialize view model + var vm = this; + + // temporary assignments for the instance + // no such assignments for now + + // named assignments to view model + vm.oldPassword = ''; + vm.newPassword = ''; + + // function mappings to view model + vm.OnKeyDown = OnKeyDown; + vm.submit = submit; + vm.OnChangePasswords = OnChangePasswords; + + // default execution steps + setTimeout(focusCurrentPassword, 300); + + // function definitions + + function OnChangePasswords() { + vm.message = undefined; + } + + function OnKeyDown(event) { + if (event.keyCode == 13) + submit(); + } + + function focusCurrentPassword() { + $('#ami-cur-password').focus(); + } + + function focusNewPassword() { + $('#ami-new-password').focus(); + } + + function submit() { + if (vm.oldPassword == '') { + vm.message = 'Please enter current password'; + setTimeout(focusCurrentPassword, 300); + return; + } + if (vm.newPassword == '') { + vm.message = 'Please enter new password'; + setTimeout(focusNewPassword, 300); + return; + } + if (vm.oldPassword != $rootScope.amGlobals.credentials.password) { + vm.message = 'Incorrect current password'; + setTimeout(focusCurrentPassword, 300); + return; + } + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Changing Password. Please Wait..'; + $amLicense.changePassword($rootScope.amGlobals.credentials.username, vm.newPassword).then(success).catch(failure); + + function success(res) { + $rootScope.busyApp.show = false; + $mdDialog.hide(true); + } + + function failure(err) { + $rootScope.busyApp.show = false; + if (err.error_code == 1) { + doLogin(err.error_message); + return; + } + vm.message = err.error_message; + } + + function doLogin(message) { + $mdDialog.show( + $mdDialog.alert() + .parent(document.body) + .clickOutsideToClose(false) + .title('License Error!') + .textContent(message) + .ariaLabel('License Error') + .ok('Got it!') + ).then(restartApp).catch(restartApp); + + function restartApp(res) { + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Restarting App due to License Error. Please Wait..'; + ipcRenderer.send('am-do-restart', true); + } + } + } + } +})(); \ No newline at end of file diff --git a/src/app/components/settings/tmpl/changepassword.html b/src/app/components/settings/tmpl/changepassword.html new file mode 100644 index 00000000..e0d70bbb --- /dev/null +++ b/src/app/components/settings/tmpl/changepassword.html @@ -0,0 +1,29 @@ + + + + +
+ + {{$root.amGlobals.credentials.username}} +
+
+ + +
+
+ + +
+
+ {{vm.message}} + + Submit + send + +
+
+
\ No newline at end of file diff --git a/src/app/components/treatments/memberships/memberships-add.controller.js b/src/app/components/treatments/memberships/memberships-add.controller.js index 7c30ba29..6522d91c 100644 --- a/src/app/components/treatments/memberships/memberships-add.controller.js +++ b/src/app/components/treatments/memberships/memberships-add.controller.js @@ -42,10 +42,9 @@ vm.selectedTreatments = []; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + changeOccurencesLabel(); + changeDurationLabel(); + getVehicleTypes(getTreatments); // function maps vm.goBack = goBack; @@ -65,14 +64,6 @@ // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - changeOccurencesLabel(); - changeDurationLabel(); - getVehicleTypes(getTreatments); - } - } - function convertTnToTitleCase() { vm.treatment.details = utils.autoCapitalizeWord(vm.treatment.details); } diff --git a/src/app/components/treatments/memberships/memberships-edit.controller.js b/src/app/components/treatments/memberships/memberships-edit.controller.js index 93e88478..b34bf435 100644 --- a/src/app/components/treatments/memberships/memberships-edit.controller.js +++ b/src/app/components/treatments/memberships/memberships-edit.controller.js @@ -45,10 +45,9 @@ errorAndExit(); return; } - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + changeOccurencesLabel(); + changeDurationLabel(); + getVehicleTypes(getTreatments, getMembershipInfo); // function maps vm.goBack = goBack; @@ -67,14 +66,6 @@ vm.convertTnToTitleCase = convertTnToTitleCase; // function definitions - - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - changeOccurencesLabel(); - changeDurationLabel(); - getVehicleTypes(getTreatments, getMembershipInfo); - } - } function convertTnToTitleCase() { vm.treatment.details = utils.autoCapitalizeWord(vm.treatment.details); diff --git a/src/app/components/treatments/memberships/memberships-viewall.controller.js b/src/app/components/treatments/memberships/memberships-viewall.controller.js index 4659db30..518ad2d9 100644 --- a/src/app/components/treatments/memberships/memberships-viewall.controller.js +++ b/src/app/components/treatments/memberships/memberships-viewall.controller.js @@ -35,20 +35,11 @@ vm.IsMembershipVisible = IsMembershipVisible; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + getCurrencySymbol(); + getMemberships(changeExpandValues); // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getCurrencySymbol(); - getMemberships(changeExpandValues); - } - } - function getCurrencySymbol() { amTreatments.getCurrencySymbol().then(success).catch(failure); diff --git a/src/app/components/treatments/packages/packages-add.controller.js b/src/app/components/treatments/packages/packages-add.controller.js index 84d4db90..744aa9e3 100644 --- a/src/app/components/treatments/packages/packages-add.controller.js +++ b/src/app/components/treatments/packages/packages-add.controller.js @@ -31,10 +31,7 @@ vm.selectedTreatments = []; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + getVehicleTypes(getTreatments); // function maps vm.goBack = goBack; @@ -50,12 +47,6 @@ // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getVehicleTypes(getTreatments); - } - } - function calculateSubTotal(type) { var total = 0; vm.selectedTreatments.forEach(iterateTreatments); diff --git a/src/app/components/treatments/packages/packages-edit.controller.js b/src/app/components/treatments/packages/packages-edit.controller.js index 8404d239..65b07dd9 100644 --- a/src/app/components/treatments/packages/packages-edit.controller.js +++ b/src/app/components/treatments/packages/packages-edit.controller.js @@ -30,10 +30,11 @@ vm.selectedTreatments = []; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + if ($state.params.name == undefined || $state.params.name == '') { + errorAndExit(); + return; + } + getVehicleTypes(getTreatments, getPackageInfo); // function maps vm.goBack = goBack; @@ -49,16 +50,6 @@ // function definitions - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - if ($state.params.name == undefined || $state.params.name == '') { - errorAndExit(); - return; - } - getVehicleTypes(getTreatments, getPackageInfo); - } - } - function calculateSubTotal(type) { var total = 0; vm.selectedTreatments.forEach(iterateTreatments); diff --git a/src/app/components/treatments/packages/packages-viewall.controller.js b/src/app/components/treatments/packages/packages-viewall.controller.js index 78033270..c6b24a16 100644 --- a/src/app/components/treatments/packages/packages-viewall.controller.js +++ b/src/app/components/treatments/packages/packages-viewall.controller.js @@ -34,18 +34,9 @@ vm.calculateTotal = calculateTotal; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + getPackages(changeExpandValues); // function definitions - - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getPackages(changeExpandValues); - } - } function calculateTotal(vehicletype, index) { var total = 0; diff --git a/src/app/components/treatments/regular/treatments-add.controller.js b/src/app/components/treatments/regular/treatments-add.controller.js index deee3172..45d0a43e 100644 --- a/src/app/components/treatments/regular/treatments-add.controller.js +++ b/src/app/components/treatments/regular/treatments-add.controller.js @@ -35,18 +35,9 @@ vm.convertVtToTitleCase = convertVtToTitleCase; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + getVehicleTypes(); // function definitions - - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - getVehicleTypes(); - } - } function goBack() { $state.go('restricted.treatments.master', { diff --git a/src/app/components/treatments/regular/treatments-edit.controller.js b/src/app/components/treatments/regular/treatments-edit.controller.js index a0d0702d..8ebcb60f 100644 --- a/src/app/components/treatments/regular/treatments-edit.controller.js +++ b/src/app/components/treatments/regular/treatments-edit.controller.js @@ -38,25 +38,16 @@ vm.convertVtToTitleCase = convertVtToTitleCase; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + if (treatmentName == '' || treatmentName == undefined) { + utils.showSimpleToast('Something went wrong! Please Try Again!'); + $state.go('restricted.treatments.master', { + openTab: 'treatments' + }); + return; + } + getVehicleTypes(); // function definitions - - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - if (treatmentName == '' || treatmentName == undefined) { - utils.showSimpleToast('Something went wrong! Please Try Again!'); - $state.go('restricted.treatments.master', { - openTab: 'treatments' - }); - return; - } - getVehicleTypes(); - } - } function convertVtToTitleCase(rate) { rate.type = utils.convertToTitleCase(rate.type); diff --git a/src/app/components/treatments/regular/treatments-viewall.controller.js b/src/app/components/treatments/regular/treatments-viewall.controller.js index e992d366..5013c2cc 100644 --- a/src/app/components/treatments/regular/treatments-viewall.controller.js +++ b/src/app/components/treatments/regular/treatments-viewall.controller.js @@ -38,23 +38,14 @@ vm.getRate = getRate; // default execution steps - if ($rootScope.isAmDbLoaded) - defaultExecutionSteps(true, false); - else - $rootScope.$watch('isAmDbLoaded', defaultExecutionSteps); + amTreatments.getTreatmentSettings().then(treatmentSettingFound).catch(noSettingFound); + getVehicleTypes(); + getTreatments(); // watchers $scope.$watch('trVm.stgDisplayAsList', listenToDisplayAsList); // function definitions - - function defaultExecutionSteps(newValue, oldValue) { - if (newValue) { - amTreatments.getTreatmentSettings().then(treatmentSettingFound).catch(noSettingFound); - getVehicleTypes(); - getTreatments(); - } - } // listeners function listenToDisplayAsList(newValue, oldValue) { diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index 7083caaa..4ecc59f1 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -12,9 +12,9 @@ angular.module('automintApp').controller('amLoginCtrl', LoginController); - LoginController.$inject = ['$rootScope', '$amRoot', '$location', 'amLogin']; + LoginController.$inject = ['$rootScope', '$amRoot', '$location', '$amLicense', 'amLogin']; - function LoginController($rootScope, $amRoot, $location, amLogin) { + function LoginController($rootScope, $amRoot, $location, $amLicense, amLogin) { // initialize view model var vm = this; @@ -91,135 +91,23 @@ } vm.isLogingIn = true; if ((vm.username != undefined) && (vm.username != '') && (vm.password != undefined) && (vm.password != '')) { - amLogin.loadCredentials(vm.username, vm.password); - amLogin.login(success, failure); - } else if ((vm.code != undefined) && (vm.code != '')) { - amLogin.activate(vm.code, activate, failure) - } - - function activate(res) { - if (res.data == undefined) { - failure(); - return; - } - if (res.data.used) { - vm.message = "The code is already in use! You cannot use it again!"; - vm.isLogingIn = false; - return; - } - var startdate = moment(res.data.starts, 'YYYY-MM-DD').format(); - var enddate = moment(res.data.ends, 'YYYY-MM-DD').format(); - var curdate = moment().format(); - if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) - isLoggedIn = true; - amLogin.saveActivationDetails(isLoggedIn, vm.code, res.data.starts, res.data.ends).then(proceed).catch(docNotSaved); - } + $amLicense.loadCredentials(vm.username, vm.password); + $amLicense.login(false).then(success).catch(failure); + } else if ((vm.code != undefined) && (vm.code != '')) + $amLicense.login(true, vm.code).then(success).catch(failure); function success(res) { - if (res.data && res.data.mint_code) { - switch (res.data.mint_code) { - case "AU100": - processLicense(res.data.userCtx, res.data.license); - break; - case "AU200": - processMintCode(200); - break; - case "AU321": - case "AU322": - processMintCode(300); - break; - case "AU330": - processMintCode(330); - break; - } + if (res.isLoggedIn) { + if (res.isSyncableDb) + $amRoot.syncDb(); + $amRoot.dbAfterLogin(true); } else - failure(); - - function processLicense(userData, licenseData) { - if (userData && (userData.name != null) && userData.channels && (Object.keys(userData.channels).length > 1)) { - channel = Object.keys(userData.channels)[1]; - amLogin.saveLoginCredentials($rootScope.amGlobals.credentials.username, $rootScope.amGlobals.credentials.password, channel).then(checkLicense).catch(docNotSaved); - } else - checkLicense(); - - function checkLicense() { - if (licenseData) { - var startdate = moment(licenseData.license.starts, 'YYYY-MM-DD').format(); - var enddate = moment(licenseData.license.ends, 'YYYY-MM-DD').format(); - var curdate = moment().format(); - if ((startdate.localeCompare(curdate) < 0) && (enddate.localeCompare(curdate) > 0)) - isLoggedIn = true; - amLogin.saveLicenseDetails(isLoggedIn, licenseData.license, licenseData.cloud).then(proceed).catch(docNotSaved); - } else - processMintCode(330); - } - } - } - - function docNotSaved(err) { - console.error(err); - failure(); - } - - function proceed(res) { - if (!isLoggedIn) { - processMintCode(-1); - return; - } - $rootScope.amGlobals.configDocIds = { - settings: 'settings' + (channel ? '-' + channel : ''), - treatment: 'treatments' + (channel ? '-' + channel : ''), - inventory: 'inventory' + (channel ? '-' + channel : ''), - workshop: 'workshop' + (channel ? '-' + channel : '') - } - - var isDbToBeChanged = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.credentials != undefined) && ($rootScope.amGlobals.credentials.username != undefined) && ($rootScope.amGlobals.credentials.username != '') && ($rootScope.amGlobals.credentials.password != undefined) && ($rootScope.amGlobals.credentials.password != '')); - if (isDbToBeChanged) - amLogin.changeExistingDocs().then(p1).catch(p1); - else - p1(); - - function p1(res) { - var isSyncableDb = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.credentials != undefined) && ($rootScope.amGlobals.credentials.username != undefined) && ($rootScope.amGlobals.credentials.username != '') && ($rootScope.amGlobals.credentials.password != undefined) && ($rootScope.amGlobals.credentials.password != '')); - if (($rootScope.amGlobals == undefined) || ($rootScope.amGlobals.validity == undefined) || ($rootScope.amGlobals.validity.cloud == undefined)) - isSyncableDb = false; - else { - var cloudstart = moment($rootScope.amGlobals.validity.cloud.starts, 'YYYY-MM-DD').format(); - var cloudend = moment($rootScope.amGlobals.validity.cloud.ends, 'YYYY-MM-DD').format(); - var curdate = moment().format(); - isSyncableDb = ((cloudstart.localeCompare(curdate) < 0) && (cloudend.localeCompare(curdate) > 0)); - if (!isSyncableDb) - utils.showSimpleToast('Your cloud services have expired! Please Contact Automint Care!'); - } - if (isSyncableDb) - $rootScope.amDbSync = $amRoot.syncDb($rootScope.amGlobals.credentials.username, $rootScope.amGlobals.credentials.password); - $amRoot.dbAfterLogin(); - $location.path('/dashboard'); - } - } - - function processMintCode(code) { - switch (code) { - case -1: - vm.message = "Your license has expired!"; - break; - case 200: - vm.message = "Incorrect Username or Password"; - break; - case 300: - vm.message = "There seems to be problem at Server! Please try again or contact Automint Care!"; - break; - case 330: - vm.message = "No Licensing Details Found for " + $rootScope.amGlobals.credentials.username + "! Please Try Again or Contact Automint Care!"; - break; - } - vm.isLogingIn = false; + failure('Your license has expired!'); } function failure(err) { - vm.message = "Please check your internet connectivity!"; vm.isLogingIn = false; - console.error(err); + vm.message = err; } } } diff --git a/src/app/login/login.factory.js b/src/app/login/login.factory.js index a176b0de..f61e53d3 100644 --- a/src/app/login/login.factory.js +++ b/src/app/login/login.factory.js @@ -21,54 +21,62 @@ function LoginFactory($rootScope, $http, $q, amLoginBase64, amFactory, constants, pdbLocal, pdbMain) { // initialize factory and functino mappings var factory = { - loadCredentials: loadCredentials, - login: login, - saveLoginCredentials: saveLoginCredentials, + saveLicense: saveLicense, changeExistingDocs: changeExistingDocs, - saveLicenseDetails: saveLicenseDetails, - activate: activate, - saveActivationDetails: saveActivationDetails + saveActivationDetails: saveActivationDetails, + setLoginState: setLoginState, + savePassword: savePassword, + cloudForceEnabled: cloudForceEnabled } return factory; // function definitions - function loadCredentials(username, password) { - if (!$rootScope.amGlobals) - $rootScope.amGlobals = {}; - $rootScope.amGlobals.credentials = { - username: username, - password: password + function cloudForceEnabled(force) { + var tracker = $q.defer(); + pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(failure); + return tracker.promise; + + function getLoginDoc(res) { + res.isCloudForceEnabled = force; + pdbLocal.save(res).then(success).catch(failure); } - } - function login(success, failure) { - $http({ - method: 'POST', - url: amFactory.generateAuthUrl(), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data: $.param({ - name: $rootScope.amGlobals.credentials.username, - password: $rootScope.amGlobals.credentials.password - }), - timeout: 2000 - }).then(success, failure); + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } } - function activate(code, success, failure) { - $http.get(amFactory.generateActivationUrl(code)).then(success, failure); + function savePassword(password) { + var tracker = $q.defer(); + pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(failure); + return tracker.promise; + + function getLoginDoc(res) { + res.password = password; + pdbLocal.save(res).then(success).catch(failure); + } + + function success(res) { + tracker.resolve(res); + } + + function failure(err) { + tracker.reject(err); + } } - function saveActivationDetails(isLoggedIn, code, startdate, enddate) { + function saveActivationDetails(code, startdate, enddate) { var tracker = $q.defer(); pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(writeLoginDoc); return tracker.promise; function getLoginDoc(res) { - res.isLoggedIn = isLoggedIn; res.activation = { code: code, startdate: startdate, @@ -80,7 +88,6 @@ function writeLoginDoc(err) { var doc = { _id: constants.pdb_local_docs.login, - isLoggedIn: isLoggedIn, activation: { code: code, startdate: startdate, @@ -99,30 +106,20 @@ } } - function saveLicenseDetails(isLoggedIn, license, cloud) { + function setLoginState(isLoggedIn) { var tracker = $q.defer(); - if ($rootScope.amGlobals == undefined) - $rootScope.amGlobals = {}; - if ($rootScope.amGlobals.validity == undefined) - $rootScope.amGlobals.validity = {}; - $rootScope.amGlobals.validity.license = license; - $rootScope.amGlobals.validity.cloud = cloud; pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(writeLoginDoc); return tracker.promise; function getLoginDoc(res) { - res.isLoggedIn = isLoggedIn - res.license = license; - res.cloud = cloud; + res.isLoggedIn = isLoggedIn; pdbLocal.save(res).then(success).catch(failure); } function writeLoginDoc(err) { var doc = { _id: constants.pdb_local_docs.login, - isLoggedIn: isLoggedIn, - license: license, - cloud: cloud + isLoggedIn: isLoggedIn } pdbLocal.save(doc).then(success).catch(failure); } @@ -136,12 +133,16 @@ } } - function saveLoginCredentials(username, password, channel) { + function saveLicense(username, password, channel, license, cloud) { var tracker = $q.defer(); if ($rootScope.amGlobals == undefined) $rootScope.amGlobals = {}; $rootScope.amGlobals.creator = username; $rootScope.amGlobals.channel = channel; + if ($rootScope.amGlobals.validity == undefined) + $rootScope.amGlobals.validity = {}; + $rootScope.amGlobals.validity.license = license; + $rootScope.amGlobals.validity.cloud = cloud; pdbLocal.get(constants.pdb_local_docs.login).then(getLoginDoc).catch(writeLoginDoc); return tracker.promise; @@ -149,6 +150,8 @@ res.username = username; res.password = password; res.channel = channel; + res.license = license; + res.cloud = cloud; pdbLocal.save(res).then(success).catch(failure); } @@ -157,7 +160,9 @@ _id: constants.pdb_local_docs.login, username : username, password: password, - channel: channel + channel: channel, + license: license, + cloud: cloud } pdbLocal.save(doc).then(success).catch(failure); } diff --git a/src/app/login/login.html b/src/app/login/login.html index a11ad56d..01ee426a 100644 --- a/src/app/login/login.html +++ b/src/app/login/login.html @@ -43,7 +43,7 @@
-
+
or
diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js new file mode 100644 index 00000000..9238c142 --- /dev/null +++ b/src/app/login/login.service.js @@ -0,0 +1,354 @@ +/** + * Angular Service for Login and Licensing Mechanism + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').service('$amLicense', LicenseService); + + LicenseService.$inject = ['$rootScope', '$q', '$http', 'utils', 'amLogin', 'pdbLocal', 'constants', 'amFactory']; + + function LicenseService($rootScope, $q, $http, utils, amLogin, pdbLocal, constants, amFactory) { + // initialize view model for Service + var vm = this; + + // temporary assignments for instance + // no such assignments + + // named assignments to view model + // no such assignments + + // function mappings to view model + vm.loadCredentials = loadCredentials; + vm.checkLogin = checkLogin; + vm.login = login; + vm.changePassword = changePassword; + + // function definitions + + function changePassword(username, newPassword) { + var tracker = $q.defer(); + console.log($rootScope.amGlobals); + if (($rootScope.amGlobals == undefined) || ($rootScope.amGlobals.channel == undefined) || ($rootScope.amGlobals.channel == '')) { + failure({ + mint_error: 401 + }); + return; + } + var adminChannels = []; + adminChannels.push($rootScope.amGlobals.channel); + $http({ + method: 'PUT', + url: amFactory.generateChangePwdUrl($rootScope.amGlobals.credentials.username), + headers: { + 'Content-Type': 'application/json' + }, + data: { + admin_channels: adminChannels, + password: newPassword + }, + timeout: 2000 + }).then(success).catch(failure); + return tracker.promise; + + function success(res) { + $rootScope.amGlobals.credentials.password = newPassword; + amLogin.savePassword(newPassword).then(proceed).catch(doLogin); + } + + function proceed(pres) { + tracker.resolve(pres); + } + + function doLogin(err) { + amLogin.setLoginState(false).then(dl1).catch(dl1); + + function dl1(dl1res) { + failure({ + mint_error: 400 + }); + } + } + + function failure(err) { + var errorCode = -1; + var errorMessage = 'Please check your internet connectivity'; + if (err.mint_error) { + switch (err.mint_error) { + case 400: + vm.errorCode = 1; + vm.errorMessage = 'Cloud not save password! Please login again with new password!'; + break; + case 401: + vm.errorCode = 1; + vm.errorMessage = 'Automint could not collect license details. Please login again!'; + break; + } + } + + tracker.reject({ + error_code: errorCode, + error_message: errorMessage + }); + } + } + + function loadCredentials(username, password) { + if (!$rootScope.amGlobals) + $rootScope.amGlobals = {}; + $rootScope.amGlobals.credentials = { + username: username, + password: password + } + } + + function login(isCodeUsed, code) { + var today = moment().format(), isLoggedIn = false, channel, isSyncableDb = false; + var tracker = $q.defer(); + if (isCodeUsed) + $http.get(amFactory.generateActivationUrl(code)).then(activate, failure); + else { + $http({ + method: 'POST', + url: amFactory.generateAuthUrl(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: $.param({ + name: $rootScope.amGlobals.credentials.username, + password: $rootScope.amGlobals.credentials.password + }), + timeout: 2000 + }).then(success, failure); + } + return tracker.promise; + + function activate(res) { + if (res.data == undefined) { + failure(); + return; + } + if (res.data.used) { + tracker.reject('The code is already in use! You cannot use it again!'); + return; + } + var startdate = moment(res.data.starts, 'YYYY-MM-DD').format(); + var enddate = moment(res.data.ends, 'YYYY-MM-DD').format(); + if ((startdate.localeCompare(today) < 0) && (enddate.localeCompare(today) > 0)) + isLoggedIn = true; + amLogin.saveActivationDetails(vm.code, res.data.starts, res.data.ends).then(proceed).catch(failure); + } + + function success(res) { + if (res.data && res.data.mint_code) { + switch (res.data.mint_code) { + case "AU100": + processLicense(res.data.userCtx, res.data.license); + break; + case "AU200": + processMintCode(200); + break; + case "AU321": + case "AU322": + processMintCode(300); + break; + case "AU330": + processMintCode(330); + break; + } + } else + failure(); + + function processLicense(userData, licenseData) { + if (userData && (userData.name != null) && userData.channels && (Object.keys(userData.channels).length > 1)) + channel = Object.keys(userData.channels)[1]; + checkLicense(); + + function checkLicense() { + if (licenseData) { + var licensestartdate = moment(licenseData.license.starts, 'YYYY-MM-DD').format(), licenseenddate; + if ((licenseData.license.ends != '0') || (licenseData.license.ends != 0)) { + licenseenddate = moment(licenseData.license.ends, 'YYYY-MM-DD').format(); + if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) + isLoggedIn = true; + } else + isLoggedIn = true; + amLogin.saveLicense($rootScope.amGlobals.credentials.username, $rootScope.amGlobals.credentials.password, channel, licenseData.license, licenseData.cloud).then(proceed).catch(failure); + } else + processMintCode(330); + } + } + } + + function proceed(res) { + if (!isLoggedIn) { + processMintCode(-1); + return; + } + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (channel ? '-' + channel : ''), + treatment: 'treatment' + (channel ? '-' + channel : ''), + inventory: 'inventory' + (channel ? '-' + channel : ''), + workshop: 'workshop' + (channel ? '-' + channel : '') + }; + + var isDbToBeChanged = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.creator != '') && ($rootScope.amGlobals.channel != '')); + + if (isDbToBeChanged) + amLogin.changeExistingDocs().then(finalize).catch(finalize); + else + finalize(); + + function finalize(fres) { + isSyncableDb = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.credentials != undefined) && ($rootScope.amGlobals.credentials.username != undefined) && ($rootScope.amGlobals.credentials.username != '') && ($rootScope.amGlobals.credentials.password != undefined) && ($rootScope.amGlobals.credentials.password != '')); + if (($rootScope.amGlobals == undefined) || ($rootScope.amGlobals.validity == undefined) || ($rootScope.amGlobals.validity.cloud == undefined) || !isSyncableDb) + isSyncableDb = false; + else { + var cloudstartdate = moment($rootScope.amGlobals.validity.cloud.starts, 'YYYY-MM-DD').format(); + var cloudenddate = moment($rootScope.amGlobals.validity.cloud.ends, 'YYYY-MM-DD').format(); + isSyncableDb = ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)); + if (!isSyncableDb) + utils.showSimpleToast('Your cloud services have expired! Please contact Automint Care!'); + } + amLogin.setLoginState(isLoggedIn).then(finallyrespond).catch(finallyrespond); + + function finallyrespond(idk) { + tracker.resolve({ + isLoggedIn: isLoggedIn, + isSyncableDb: isSyncableDb + }); + } + } + } + + function processMintCode(code) { + var message; + switch (code) { + case -1: + message = 'Your license has expired!'; + break; + case 200: + message = 'Incorrect Username or Password'; + break; + case 300: + message = 'There seems to be problem at Server! Please try again or contact Automint Care!'; + break; + case 330: + message = 'No Licensing Details Found for ' + $rootScope.amGlobals.credentials.username + '! Please Try Again or Contact Automint Care!'; + break; + } + tracker.reject(message); + } + + function failure(err) { + if (err) + console.error(err); + tracker.reject('Please check your internet connectivity!'); + } + } + + function checkLogin() { + var tracker = $q.defer(); + var today = moment().format(), isLoggedIn = false, isCloudEnabled = false, type, isCloudForceEnabled; + pdbLocal.get(constants.pdb_local_docs.login).then(checkFlags).catch(failure); + return tracker.promise; + + function checkFlags(res) { + if (!res.isLoggedIn) { + failure({ + mintSkipSave: true + }); + } + if (res.isCloudForceEnabled) + isCloudForceEnabled = res.isCloudForceEnabled; + if (res.username && res.password) { + loadCredentials(res.username, res.password); + if (res.license) { + var licensestartdate = moment(res.license.starts, 'YYYY-MM-DD').format(); + var licenseenddate = moment(res.license.ends, 'YYYY-MM-DD').format(); + if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) + isLoggedIn = true; + } + if (isLoggedIn && res.cloud) { + var cloudstartdate = moment(res.cloud.starts, 'YYYY-MM-DD').format(); + var cloudenddate = moment(res.cloud.ends, 'YYYY-MM-DD').format(); + if ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)) + isCloudEnabled = true; + } + if (isLoggedIn) { + type = "license"; + loadCredentials(res.username, res.password); + if (res.isLoggedIn != isLoggedIn) + amLogin.setLoginState(isLoggedIn).then(respond).catch(failure); + else + respond(); + return; + } + } + if (res.activation) { + if ((res.activation.startdate != undefined) && (res.activation.enddate != undefined)) { + var activationstartdate = moment(res.activation.startdate, 'YYYY-MM-DD').format(); + var activationenddate = moment(res.activation.enddate, 'YYYY-MM-DD').format(); + if ((activationstartdate.localeCompare(today) < 0) && (activationenddate.localeCompare(today) > 0)) + isLoggedIn = true; + if (isLoggedIn) { + type = "activation"; + if (res.isLoggedIn != isLoggedIn) + amLogin.setLoginState(isLoggedIn).then(respond).catch(failure); + else + respond(); + return; + } + } + } + failure(); + + function respond(saveresponse) { + $rootScope.amGlobals.creator = (res.username ? res.username : ''); + $rootScope.amGlobals.channel = (res.channel ? res.channel : ''); + $rootScope.amGlobals.configDocIds = { + settings: 'settings' + (res.channel ? '-' + res.channel : ''), + treatment: 'treatments' + (res.channel ? '-' + res.channel : ''), + inventory: 'inventory' + (res.channel ? '-' + res.channel : ''), + workshop: 'workshop' + (res.channel ? '-' + res.channel : '') + } + var obj = { + isCloudForceEnabled: isCloudForceEnabled, + isLoggedIn: isLoggedIn, + isCloudEnabled: isCloudEnabled, + type: type, + } + switch (type) { + case "license": + obj.credentials = { + username: res.username, + password: res.password + }; + if (isCloudEnabled) + obj.cloudenddate = moment(res.cloud.ends, 'YYYY-MM-DD').format(); + break; + case "activation": + obj.enddate = res.activation.enddate; + break; + } + tracker.resolve(obj); + } + } + + function failure(err) { + if (err.mintSkipSave) + ro(); + else + amLogin.setLoginState(false).then(ro).catch(ro); + + function ro(rores) { + tracker.reject(err); + } + } + } + } +})(); \ No newline at end of file diff --git a/src/app/views/initializing.html b/src/app/views/initializing.html index 9e222604..44295336 100644 --- a/src/app/views/initializing.html +++ b/src/app/views/initializing.html @@ -1 +1 @@ -
hello world
\ No newline at end of file +
\ No newline at end of file diff --git a/src/index.html b/src/index.html index d8605427..ef55006a 100644 --- a/src/index.html +++ b/src/index.html @@ -50,14 +50,15 @@ + -
+
- Restarting App. Please Wait.. + {{busyApp.message}}
From 6e2e68be5572deb489a2f13061e638abe9a105a7 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 20 Aug 2016 16:12:54 +0530 Subject: [PATCH 69/91] Bug #202 | No Vehicle Entry 202. It shows 'vehicle' in Invoice when customer name mentioned but vehicle details not mentioned. --- src/app/app.service.js | 3 +-- src/app/appbar/headerbar.controller.js | 13 ++----------- .../inventory/inventory-viewall.controller.js | 3 +-- .../components/invoices/invoices-view.controller.js | 5 +++++ src/app/components/invoices/invoices_view.html | 2 +- .../services/services-viewall.controller.js | 3 +-- src/app/components/services/services.factory.js | 1 + src/app/lock/lockscreen.controller.js | 11 +---------- 8 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index cb2dd3da..611170a9 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -19,7 +19,6 @@ var vm = this; // named assignments to rootScope - $rootScope.isAmDbLoaded = false; if (!$rootScope.amGlobals) $rootScope.amGlobals = {}; if ($rootScope.amGlobals.configDocIds == undefined) { @@ -335,7 +334,7 @@ pdbCache.save(docsToSave); function iterateRows(row) { - if (row.id == $rootScope.amGlobals.configDocIds.settings) + if (IsConfigDoc(row.id)) return; if (row.doc.user.vehicles) Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); diff --git a/src/app/appbar/headerbar.controller.js b/src/app/appbar/headerbar.controller.js index 0df47f2a..90cecaf5 100644 --- a/src/app/appbar/headerbar.controller.js +++ b/src/app/appbar/headerbar.controller.js @@ -26,10 +26,8 @@ vm.relaunch = relaunch; // default execution steps - if ($rootScope.isAmDbLoaded) - headerbarDefaults(true, false); - else - $rootScope.$watch('isAmDbLoaded', headerbarDefaults); + $rootScope.hidePreloader = true; + amAppbar.getPasscode().then(gps).catch(failure); // function definitions @@ -37,13 +35,6 @@ ipcRenderer.send('am-do-restart', true); } - function headerbarDefaults(newValue, oldValue) { - if (newValue) { - $rootScope.hidePreloader = true; - amAppbar.getPasscode().then(gps).catch(failure); - } - } - function addService() { $state.go('restricted.services.add'); } diff --git a/src/app/components/inventory/inventory-viewall.controller.js b/src/app/components/inventory/inventory-viewall.controller.js index dfff27fe..4462f3e5 100644 --- a/src/app/components/inventory/inventory-viewall.controller.js +++ b/src/app/components/inventory/inventory-viewall.controller.js @@ -86,8 +86,7 @@ } function getInventories() { - if ($rootScope.isAmDbLoaded) - vm.promise = amInventory.getInventories().then(success).catch(failure); + vm.promise = amInventory.getInventories().then(success).catch(failure); function success(res) { vm.inventories = res; diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 6b78c7f3..037d11b5 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -63,6 +63,7 @@ vm.currentPage = currentPage; vm.IsNotSinglePage = IsNotSinglePage; vm.IsCustomerNotAnonymus = IsCustomerNotAnonymus; + vm.IsVehicleNotAnonymus = IsVehicleNotAnonymus; // electron watchers eIpc.on('am-invoice-mail-sent', OnInvoiceMailSent); @@ -83,6 +84,10 @@ // function definitions + function IsVehicleNotAnonymus() { + return (vm.vehicle.reg != 'Vehicle'); + } + function IsCustomerNotAnonymus() { return (vm.user.name != 'Anonymous'); } diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index d8bbb959..c87d12c9 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -168,7 +168,7 @@

{{vm.workshop.name}}

diff --git a/src/app/components/services/services-viewall.controller.js b/src/app/components/services/services-viewall.controller.js index 1abc5aaa..0ca56d91 100644 --- a/src/app/components/services/services-viewall.controller.js +++ b/src/app/components/services/services-viewall.controller.js @@ -325,8 +325,7 @@ // fill datatable with list of services function getServices() { vm.showPaginationBar = (vm.serviceQuery == ''); - if ($rootScope.isAmDbLoaded) - vm.promise = (isPreferencesLoaded) ? amServices.getServices(vm.currentTimeSet, vm.serviceQuery).then(success).catch(failure) : undefined; + vm.promise = (isPreferencesLoaded) ? amServices.getServices(vm.currentTimeSet, vm.serviceQuery).then(success).catch(failure) : undefined; function success(res) { isDataLoaded = true; diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 76d00560..e3aa6237 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -355,6 +355,7 @@ return tracker.promise; function success(res) { + console.log(res); var result = [], currentRange = ''; Object.keys(res).forEach(iterateDateRange); tracker.resolve(result); diff --git a/src/app/lock/lockscreen.controller.js b/src/app/lock/lockscreen.controller.js index 3937ec8a..79a637b0 100644 --- a/src/app/lock/lockscreen.controller.js +++ b/src/app/lock/lockscreen.controller.js @@ -25,19 +25,10 @@ vm.changePasscode = changePasscode; // default execution steps - if ($rootScope.isAmDbLoaded) - lockDefaults(true, false); - else - $rootScope.$watch('isAmDbLoaded', lockDefaults); + amAppbar.getPasscode().then(gps).catch(unlock); // function definitions - function lockDefaults(newValue, oldValue) { - if (newValue) { - amAppbar.getPasscode().then(gps).catch(unlock); - } - } - function changePasscode() { vm.isMessage = false; } From 5bfca9cd5836362447842c18aba8f3b7293be82a Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 25 Aug 2016 20:58:38 +0530 Subject: [PATCH 70/91] Bugs In Feature #191 #171 191. Sometimes some services are not getting displayed on dashboard visuals. 171. It's not displaying all the restored services. --- src/app/app.service.js | 8 ++++++++ .../settings/settings-importdata.service.js | 19 +++++++++++++------ src/app/login/login.service.js | 5 +++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index 611170a9..a61ad67e 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -47,6 +47,7 @@ vm.initDb = initDb; vm.syncDb = syncDb; vm.dbAfterLogin = dbAfterLogin; + vm.generateCacheDocs = generateCacheDocs; // function definitions @@ -160,6 +161,8 @@ } function OnChangeMainDb(change) { + if ($rootScope.isImportingDb == true) + return; if (IsConfigDoc(change.id)) return; if (change.deleted == true) { @@ -299,6 +302,8 @@ var vdocToSave = {}; res.rows.forEach(iterateRows); vdocToSave._id = constants.pdb_cache_views.view_next_due_vehicles; + if (vvcdoc._rev != undefined) + vdocToSave._rev = vvcdoc._rev; pdbCache.save(vdocToSave); function iterateRows(row) { @@ -331,6 +336,8 @@ var docsToSave = {}, isChanged = false; res.rows.forEach(iterateRows); docsToSave._id = constants.pdb_cache_views.view_services; + if (cachedoc._rev != undefined) + docsToSave._rev = cachedoc._rev; pdbCache.save(docsToSave); function iterateRows(row) { @@ -345,6 +352,7 @@ Object.keys(vehicle.services).forEach(iterateServices); function iterateServices(sId) { + console.log(sId); var service = vehicle.services[sId]; var cd = moment(service.date).format('MMM YYYY'); var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); diff --git a/src/app/components/settings/settings-importdata.service.js b/src/app/components/settings/settings-importdata.service.js index 5f5bb12d..0a0944b0 100644 --- a/src/app/components/settings/settings-importdata.service.js +++ b/src/app/components/settings/settings-importdata.service.js @@ -11,9 +11,9 @@ angular.module('automintApp') .service('amImportdata', ImportDataService); - ImportDataService.$inject = ['pdbMain', '$filter', '$q', 'utils', '$rootScope', '$log']; + ImportDataService.$inject = ['pdbMain', '$filter', '$q', 'utils', '$rootScope', '$log', '$amRoot']; - function ImportDataService(pdbMain, $filter, $q, utils, $rootScope, $log) { + function ImportDataService(pdbMain, $filter, $q, utils, $rootScope, $log, $amRoot) { var sVm = this; // temporary named mappings @@ -62,6 +62,7 @@ } function jsonCallback(restore) { + $rootScope.isImportingDb = true; $q.all([ pdbMain.getAll(), pdbMain.get($rootScope.amGlobals.configDocIds.settings) @@ -81,10 +82,16 @@ pdbMain.saveAll(docsToSave).then(success).catch(failure); function success(res) { - tracker.resolve({ - success: true, - message: 'Backup has been restored! You may now delete the file.' - }) + setTimeout(setIsImportingDb, 1000); + + function setIsImportingDb() { + tracker.resolve({ + success: true, + message: 'Backup has been restored! You may now delete the file.' + }); + $rootScope.isImportingDb = false; + $amRoot.generateCacheDocs(true); + } } function failure(err) { diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 9238c142..ce6f9235 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -140,6 +140,8 @@ var enddate = moment(res.data.ends, 'YYYY-MM-DD').format(); if ((startdate.localeCompare(today) < 0) && (enddate.localeCompare(today) > 0)) isLoggedIn = true; + if ((res.data.code != undefined) && (res.data.code == 13)) + processMintCode(-404); amLogin.saveActivationDetails(vm.code, res.data.starts, res.data.ends).then(proceed).catch(failure); } @@ -240,6 +242,9 @@ case 330: message = 'No Licensing Details Found for ' + $rootScope.amGlobals.credentials.username + '! Please Try Again or Contact Automint Care!'; break; + case -404: + message = 'Invalid Activation Code!'; + break; } tracker.reject(message); } From 09ae973d6ddf82b38659aca12b0732c6f0fbeba6 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 26 Aug 2016 10:58:55 +0530 Subject: [PATCH 71/91] Bug Fix | Treatment Parts listing in Invoice --- .../invoices/invoices-view.controller.js | 44 +++++++++++++++++++ .../components/invoices/invoices_view.html | 36 +++++++++------ 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index 037d11b5..cac6f8ac 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -64,6 +64,7 @@ vm.IsNotSinglePage = IsNotSinglePage; vm.IsCustomerNotAnonymus = IsCustomerNotAnonymus; vm.IsVehicleNotAnonymus = IsVehicleNotAnonymus; + vm.getIndexCount = getIndexCount; // electron watchers eIpc.on('am-invoice-mail-sent', OnInvoiceMailSent); @@ -84,6 +85,14 @@ // function definitions + function getIndexCount(root, isInventory, count) { + if (isInventory && (root == 1)) { + console.log('root: ', root); + console.log('count:' , count); + } + return (root - 1); + } + function IsVehicleNotAnonymus() { return (vm.vehicle.reg != 'Vehicle'); } @@ -135,6 +144,7 @@ function doPagination() { vm.pages = []; + var membershippages = 0, packagepages = 0, treatmentpages = 0, inventorypages = 0; if (rowCount > 15) { var pageCount = Math.ceil(rowCount / 15); var mtc = membershipTreatmentCount, ptc = packageTreatmentCount, tc = treatmentCount, ic = inventoryCount; @@ -142,6 +152,7 @@ if (mtc > 15) { var ms = 0, mst = 0; Object.keys(membershipCount).forEach(iterateMemberships); + membershippages++; pushPages(ms, 0, 0, 0); mtc -= mst; continue; @@ -161,6 +172,9 @@ Object.keys(membershipCount).forEach(iterateMemberships); Object.keys(packageCount).forEach(iteratePackages); ptc = 15 - mtc; + packagepages++; + if (ms != 0) + membershippages++; pushPages(ms, ps, 0, 0); mtc = 0; continue; @@ -187,6 +201,11 @@ Object.keys(membershipCount).forEach(iterateMemberships); Object.keys(packageCount).forEach(iteratePackages); tc = 15 - mtc - ptc; + if (ms != 0) + membershippages++; + if (ps != 0) + packagepages++; + treatmentpages++; pushPages(ms, ps, tc, 0); mtc = 0; ptc = 0; @@ -214,6 +233,15 @@ Object.keys(membershipCount).forEach(iterateMemberships); Object.keys(packageCount).forEach(iteratePackages); ic = 15 - mtc - ptc - tc; + if (ic == 0) + ic = 1; + if (ms != 0) + membershippages++; + if (ps != 0) + packagepages++; + if (tc != 0) + treatmentpages++; + inventorypages++; pushPages(ms, ps, tc, ic); mtc = 0; ptc = 0; @@ -262,6 +290,16 @@ var ps = 0, ms = 0, pst = 0, mst = 0; Object.keys(membershipCount).forEach(iterateMemberships); Object.keys(packageCount).forEach(iteratePackages); + if (ms != 0) + membershippages++; + if (ps != 0) + packagepages++; + if (tc != 0) + treatmentpages++; + if (ic != 0) + inventorypages++; + + console.log(inventoryCount); pushPages(ms, ps, treatmentCount, inventoryCount); function iterateMemberships(membership) { @@ -281,10 +319,16 @@ } } + console.log(vm.pages); + function pushPages(pmtc, pptc, pagetc, pic) { var index = vm.pages.length; vm.pages.push({ index: index, + membershippages: membershippages, + packagepages: packagepages, + treatmentpages: treatmentpages, + inventorypages: inventorypages, membershipTreatmentCount: pmtc, packageTreatmentCount: pptc, treatmentCount: pagetc, diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index c87d12c9..dc43637f 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -61,6 +61,7 @@ padding: 2px 12px 2px 12px !important; font-size: 9pt; } + .am-invoice-table .m-dtl { padding: 2px 12px 12px 12px !important; font-size: 9pt; @@ -73,7 +74,7 @@ .am-invoice-table .row td { border-top: 1px solid #ECEFF1; } - + .am-invoice-table .dark-line td { border-top: 2px solid #37474F; } @@ -83,7 +84,7 @@ font-weight: 800; padding: 8px 12px 12px 12px !important; } - + .am-invoice-table .subt td { border-top: 1px solid #ECEFF1; padding: 8px 12px 8px 12px !important; @@ -106,7 +107,7 @@ } .am-invoice-w-area { - font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; width: 100%; } @@ -122,7 +123,7 @@ .am-invoice-w-area .parent .right { padding-right: 2rem; } - + a { text-decoration: none; color: #757575; @@ -132,16 +133,22 @@ display: table; transition: all 500ms ease; } - + a:hover { color: #424242; } - + .am-flex-box { height: 100%; display: flex; flex-direction: column; } + + @media print { + #am-actual-invoice-body { + page-break-after: always; + } + } @@ -183,7 +190,7 @@

{{vm.workshop.name}}

- + @@ -195,7 +202,7 @@

{{vm.workshop.name}}

- + @@ -205,17 +212,17 @@

{{vm.workshop.name}}

- + - + - + - + - + @@ -247,7 +254,7 @@

{{vm.workshop.name}}

- +
-

{{vm.user.name}}

- {{vm.vehicle.manuf + ' ' + vm.vehicle.model}}
{{vm.vehicle.reg}}
+

{{vm.user.name}}

+ {{vm.vehicle.manuf + ' ' + vm.vehicle.model}}
{{vm.vehicle.reg}}
{{service.srvc_status}} {{service.srvc_state}} - + delete
{{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}){{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}) {{vm.getServiceDate(service.srvc_date)}} {{service.srvc_cost}} {{service.srvc_status}}{{customer.name}} {{customer.mobile}} {{customer.email}}{{vehicle.manuf + ' ' + vehicle.model}} ({{vehicle.reg}})
{{vehicle.manuf + ' ' + vehicle.model}} ({{vehicle.reg}})
delete diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index b560d9e4..ef4b705f 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -1516,6 +1516,7 @@ Object.keys(res.memberships).forEach(iterateMemberships); vm.vehicle.id = $state.params.vehicleId; vm.vehicle.reg = (res.vehicle.reg == 'Vehicle') ? '' : res.vehicle.reg; + orgVehicle vm.vehicle.manuf = res.vehicle.manuf; vm.vehicle.model = res.vehicle.model; vm.vehicle.type = res.vehicle.type; diff --git a/src/app/components/services/services-viewall.controller.js b/src/app/components/services/services-viewall.controller.js index b41cd623..3d30700f 100644 --- a/src/app/components/services/services-viewall.controller.js +++ b/src/app/components/services/services-viewall.controller.js @@ -49,6 +49,7 @@ vm.IsServiceStateIv = IsServiceStateIv; vm.openTimeFilter = openTimeFilter; vm.IsCustomerAnonymous = IsCustomerAnonymous; + vm.IsVehicleAnonymous = IsVehicleAnonymous; // default execution steps $scope.$watch('vm.serviceQuery', watchServiceQuery); @@ -60,6 +61,10 @@ // function definitions + function IsVehicleAnonymous(reg) { + return (reg == 'Vehicle'); + } + function defaultExecutionSteps(newValue, oldValue) { if (newValue) { getCurrencySymbol(); diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 75af2aaa..76d00560 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -689,6 +689,8 @@ if (!res.user) res.user = {}; Object.keys(newUser).forEach(iterateUserFields); + if (res.user.type && (res.user.type == 'Lead')) + res.user.type = 'Customer'; if (!isVehicleBlank) { if (!res.user.vehicles) res.user.vehicles = {}; @@ -702,8 +704,6 @@ pdbMain.save(res).then(success).catch(failure); function iterateUserFields(ufn) { - if (ufn == 'type') - newUser[ufn] = (res.user[ufn] == 'Lead') ? 'Customer' : newUser[ufn]; res.user[ufn] = newUser[ufn]; } diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 8400723f..e946acdf 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -280,8 +280,8 @@
- Vehicle: - {{vm.vehicle.manuf}} {{vm.vehicle.model}} - {{vm.vehicle.reg}} + Vehicle: + {{vm.vehicle.manuf}} {{vm.vehicle.model}} - {{vm.vehicle.reg}}
diff --git a/src/app/components/services/services_viewAll.html b/src/app/components/services/services_viewAll.html index 58f35e1c..c068a289 100644 --- a/src/app/components/services/services_viewAll.html +++ b/src/app/components/services/services_viewAll.html @@ -42,7 +42,7 @@
{{service.cstmr_name}}{{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}){{service.vhcl_manuf + ' ' + service.vhcl_model}} ({{service.vhcl_reg}}) {{vm.getServiceDate(service.srvc_date)}} {{service.srvc_cost}} {{service.srvc_status}}

{{vm.user.name}}

- {{vm.vehicle.manuf + ' ' + vm.vehicle.model}}
{{vm.vehicle.reg}}
+ {{vm.vehicle.manuf + ' ' + vm.vehicle.model}}
{{vm.vehicle.reg}}
Qty Amount
{{key}}  
{{package.name}} - {{treatment.name}} {{vm.currencySymbol}} {{treatment.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}}{{vm.currencySymbol}} {{treatment.rate}}
{{problem.details}} {{vm.currencySymbol}} {{problem.rate | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}}{{vm.currencySymbol}} {{problem.rate}}
{{inventory.name}}{{vm.currencySymbol}} {{inventory.rate | number: (vm.vatSettings.applyTax) ? ((vm.vatSettings.inclusive) ? 2 : 0) : 0}}{{vm.currencySymbol}} {{inventory.rate}} {{inventory.qty}} {{vm.currencySymbol}} {{inventory.total}}
Total {{vm.currencySymbol}} {{vm.service.cost | number: (vm.sTaxSettings.applyTax) ? ((vm.sTaxSettings.inclusive) ? 2 : 0) : 0}}{{vm.currencySymbol}} {{vm.service.cost}}
+ diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 3c159bb3..3371c574 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -100,7 +100,6 @@ vm.label_invoice = 'Invoice'; vm.isNextDueService = false; vm.nextDueDate = new Date(); - vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); vm.problemFocusIndex = -1; vm.inventoryFocusIndex = -1; vm.discount = { @@ -199,6 +198,8 @@ vm.findVehicleByReg = findVehicleByReg; vm.IsCustomerNotAnonymus = IsCustomerNotAnonymus; vm.goToDashboard = goToDashboard; + vm.IsCustomerSelected = IsCustomerSelected; + vm.openCustomerProfile = openCustomerProfile; // default execution steps setCoverPic(); @@ -223,6 +224,18 @@ // function definitions + function openCustomerProfile() { + $state.go('restricted.customers.edit', { + id: vm.user.id + }); + } + + function IsCustomerSelected() { + if (vm.user.id == undefined) + return false; + return (vm.user.id != ''); + } + function goToDashboard() { $mdSidenav('main-nav-left').close() $state.go('restricted.dashboard'); @@ -1619,7 +1632,6 @@ } vm.isNextDueService = false; vm.nextDueDate = new Date(); - vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); autofillVehicle = false; } @@ -1721,7 +1733,6 @@ } vm.isNextDueService = false; vm.nextDueDate = new Date(); - vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); } // replace all the treatment values with updated vehicle type diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 5441d3e5..b513e31f 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -101,7 +101,6 @@ vm.label_invoice = 'Invoice'; vm.isNextDueService = false; vm.nextDueDate = new Date(); - vm.nextDueDate.setMonth(vm.nextDueDate.getMonth() + 3); vm.problemFocusIndex = -1; vm.inventoryFocusIndex = -1; vm.discount = { @@ -194,6 +193,8 @@ vm.selectUserBasedOnMobile = selectUserBasedOnMobile; vm.IsCustomerNotAnonymus = IsCustomerNotAnonymus; vm.goToDashboard = goToDashboard; + vm.IsCustomerSelected = IsCustomerSelected; + vm.openCustomerProfile = openCustomerProfile; // default execution steps setCoverPic(); @@ -209,6 +210,18 @@ // function definitions + function openCustomerProfile() { + $state.go('restricted.customers.edit', { + id: vm.user.id + }); + } + + function IsCustomerSelected() { + if (vm.user.id == undefined) + return false; + return (vm.user.id != ''); + } + function goToDashboard() { $mdSidenav('main-nav-left').close() $state.go('restricted.dashboard'); diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index 5376cf19..8400723f 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -172,7 +172,7 @@
@@ -285,6 +292,7 @@

{{vm.workshop.name}}

 
+
From 99aaa61e3420dfb48041ea7f9b21acd97b2c96fb Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 26 Aug 2016 11:44:04 +0530 Subject: [PATCH 72/91] Fixes in Features #191 #185 191. It doesn't list all the services added before. (It lists only 5) 185. ends: 0 is not implemented. --- src/app/app.service.js | 46 +++++++++++-------- .../invoices/invoices-view.controller.js | 9 +--- .../components/invoices/invoices_view.html | 2 +- .../components/services/services.factory.js | 1 - .../settings/settings-importdata.service.js | 17 ++++--- src/app/login/login.controller.js | 5 +- src/app/login/login.service.js | 26 +++++++---- 7 files changed, 62 insertions(+), 44 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index a61ad67e..2c90957c 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -12,9 +12,9 @@ angular.module('automintApp').service('$amRoot', AutomintService); - AutomintService.$inject = ['$rootScope', '$state', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory']; + AutomintService.$inject = ['$rootScope', '$state', '$q', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory']; - function AutomintService($rootScope, $state, constants, pdbMain, pdbCache, pdbLocal, amFactory) { + function AutomintService($rootScope, $state, $q, constants, pdbMain, pdbCache, pdbLocal, amFactory) { // set up service view model var vm = this; @@ -115,9 +115,6 @@ } function dbAfterLogin(wait) { - // handle database migration if any and generate cache docs after the process - // no such migrations right now - generateCacheDocs(); // setup database change listeners pdbMain.OnDbChanged({ @@ -129,9 +126,21 @@ $rootScope.checkAutomintValidity = setInterval(checkAutomintValidity, 1000*60*60*24); if (wait) - setTimeout(transit, 2000); - else - $state.go('restricted.dashboard'); + setTimeout(prepraeTransit, 2000); + else { + // handle database migration if any and generate cache docs after the process + // no such migrations right now + generateCacheDocs().then(transit).catch(transit); + } + + function prepraeTransit() { + $rootScope.isOnChangeMainDbBlocked = false; + // handle database migration if any and generate cache docs after the process + // no such migrations right now + generateCacheDocs(true).then(transit).catch(transit); + + + } function transit() { $state.go('restricted.dashboard'); @@ -161,7 +170,7 @@ } function OnChangeMainDb(change) { - if ($rootScope.isImportingDb == true) + if ($rootScope.isOnChangeMainDbBlocked == true) return; if (IsConfigDoc(change.id)) return; @@ -290,21 +299,27 @@ } function generateCacheDocs(force) { + var tracker = $q.defer(); pdbMain.getAll().then(success).catch(failure); + return tracker.promise; function success(res) { - var serviceCacheCallback = (force == true) ? generateServicesCache : ignore; - var nextDueCacheCallback = (force == true) ? generateNextDueCache : ignore; + var serviceCacheCallback = (force == true) ? generateServicesCache : quitRoutine; + var nextDueCacheCallback = (force == true) ? generateNextDueCache : quitRoutine; pdbCache.get(constants.pdb_cache_views.view_services).then(serviceCacheCallback).catch(generateServicesCache); pdbCache.get(constants.pdb_cache_views.view_next_due_vehicles).then(nextDueCacheCallback).catch(generateNextDueCache); + function quitRoutine() { + tracker.resolve(true); + } + function generateNextDueCache(vvcdoc) { var vdocToSave = {}; res.rows.forEach(iterateRows); vdocToSave._id = constants.pdb_cache_views.view_next_due_vehicles; if (vvcdoc._rev != undefined) vdocToSave._rev = vvcdoc._rev; - pdbCache.save(vdocToSave); + pdbCache.save(vdocToSave).then(quitRoutine).catch(quitRoutine); function iterateRows(row) { if (IsConfigDoc(row.id)) @@ -338,7 +353,7 @@ docsToSave._id = constants.pdb_cache_views.view_services; if (cachedoc._rev != undefined) docsToSave._rev = cachedoc._rev; - pdbCache.save(docsToSave); + pdbCache.save(docsToSave).then(quitRoutine).catch(quitRoutine); function iterateRows(row) { if (IsConfigDoc(row.id)) @@ -352,7 +367,6 @@ Object.keys(vehicle.services).forEach(iterateServices); function iterateServices(sId) { - console.log(sId); var service = vehicle.services[sId]; var cd = moment(service.date).format('MMM YYYY'); var payreceived = (service.partialpayment) ? service.partialpayment.total : ((service.status == "paid") ? service.cost : 0); @@ -383,10 +397,6 @@ } } } - - function ignore(res) { - // do nothing - } } function failure(err) { diff --git a/src/app/components/invoices/invoices-view.controller.js b/src/app/components/invoices/invoices-view.controller.js index cac6f8ac..14c71dc9 100644 --- a/src/app/components/invoices/invoices-view.controller.js +++ b/src/app/components/invoices/invoices-view.controller.js @@ -85,11 +85,7 @@ // function definitions - function getIndexCount(root, isInventory, count) { - if (isInventory && (root == 1)) { - console.log('root: ', root); - console.log('count:' , count); - } + function getIndexCount(root) { return (root - 1); } @@ -299,7 +295,6 @@ if (ic != 0) inventorypages++; - console.log(inventoryCount); pushPages(ms, ps, treatmentCount, inventoryCount); function iterateMemberships(membership) { @@ -319,8 +314,6 @@ } } - console.log(vm.pages); - function pushPages(pmtc, pptc, pagetc, pic) { var index = vm.pages.length; vm.pages.push({ diff --git a/src/app/components/invoices/invoices_view.html b/src/app/components/invoices/invoices_view.html index dc43637f..7bdef89d 100644 --- a/src/app/components/invoices/invoices_view.html +++ b/src/app/components/invoices/invoices_view.html @@ -220,7 +220,7 @@

{{vm.workshop.name}}

{{vm.currencySymbol}} {{problem.rate}} - + {{inventory.name}} {{vm.currencySymbol}} {{inventory.rate}} {{inventory.qty}} diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index e3aa6237..76d00560 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -355,7 +355,6 @@ return tracker.promise; function success(res) { - console.log(res); var result = [], currentRange = ''; Object.keys(res).forEach(iterateDateRange); tracker.resolve(result); diff --git a/src/app/components/settings/settings-importdata.service.js b/src/app/components/settings/settings-importdata.service.js index 0a0944b0..7d96b07c 100644 --- a/src/app/components/settings/settings-importdata.service.js +++ b/src/app/components/settings/settings-importdata.service.js @@ -62,7 +62,7 @@ } function jsonCallback(restore) { - $rootScope.isImportingDb = true; + $rootScope.isOnChangeMainDbBlocked = true; $q.all([ pdbMain.getAll(), pdbMain.get($rootScope.amGlobals.configDocIds.settings) @@ -85,12 +85,15 @@ setTimeout(setIsImportingDb, 1000); function setIsImportingDb() { - tracker.resolve({ - success: true, - message: 'Backup has been restored! You may now delete the file.' - }); - $rootScope.isImportingDb = false; - $amRoot.generateCacheDocs(true); + $rootScope.isOnChangeMainDbBlocked = false; + $amRoot.generateCacheDocs(true).then(respond).catch(respond); + + function respond() { + tracker.resolve({ + success: true, + message: 'Backup has been restored! You may now delete the file.' + }); + } } } diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index 4ecc59f1..37e12aa8 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -98,8 +98,11 @@ function success(res) { if (res.isLoggedIn) { - if (res.isSyncableDb) + if (res.isSyncableDb) { + $rootScope.isOnChangeMainDbBlocked = true; $amRoot.syncDb(); + } + vm.message = "Preparing Dashboard.."; $amRoot.dbAfterLogin(true); } else failure('Your license has expired!'); diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index ce6f9235..051e20d9 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -32,7 +32,6 @@ function changePassword(username, newPassword) { var tracker = $q.defer(); - console.log($rootScope.amGlobals); if (($rootScope.amGlobals == undefined) || ($rootScope.amGlobals.channel == undefined) || ($rootScope.amGlobals.channel == '')) { failure({ mint_error: 401 @@ -138,7 +137,9 @@ } var startdate = moment(res.data.starts, 'YYYY-MM-DD').format(); var enddate = moment(res.data.ends, 'YYYY-MM-DD').format(); - if ((startdate.localeCompare(today) < 0) && (enddate.localeCompare(today) > 0)) + if ((res.data.ends == 0) || (res.data.ends == '0')) + isLoggedIn = true; + else if ((startdate.localeCompare(today) < 0) && (enddate.localeCompare(today) > 0)) isLoggedIn = true; if ((res.data.code != undefined) && (res.data.code == 13)) processMintCode(-404); @@ -173,12 +174,13 @@ function checkLicense() { if (licenseData) { var licensestartdate = moment(licenseData.license.starts, 'YYYY-MM-DD').format(), licenseenddate; - if ((licenseData.license.ends != '0') || (licenseData.license.ends != 0)) { + if ((licenseData.license.ends == '0') || (licenseData.license.ends == 0)) + isLoggedIn = true; + else { licenseenddate = moment(licenseData.license.ends, 'YYYY-MM-DD').format(); if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) isLoggedIn = true; - } else - isLoggedIn = true; + } amLogin.saveLicense($rootScope.amGlobals.credentials.username, $rootScope.amGlobals.credentials.password, channel, licenseData.license, licenseData.cloud).then(proceed).catch(failure); } else processMintCode(330); @@ -213,6 +215,8 @@ var cloudstartdate = moment($rootScope.amGlobals.validity.cloud.starts, 'YYYY-MM-DD').format(); var cloudenddate = moment($rootScope.amGlobals.validity.cloud.ends, 'YYYY-MM-DD').format(); isSyncableDb = ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)); + if (($rootScope.amGlobals.validity.cloud.ends == 0) || ($rootScope.amGlobals.validity.cloud.ends == '0')) + isSyncableDb = true; if (!isSyncableDb) utils.showSimpleToast('Your cloud services have expired! Please contact Automint Care!'); } @@ -275,13 +279,17 @@ if (res.license) { var licensestartdate = moment(res.license.starts, 'YYYY-MM-DD').format(); var licenseenddate = moment(res.license.ends, 'YYYY-MM-DD').format(); - if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) + if ((res.license.ends == 0) || (res.license.ends == '0')) + isLoggedIn = true; + else if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) isLoggedIn = true; } if (isLoggedIn && res.cloud) { var cloudstartdate = moment(res.cloud.starts, 'YYYY-MM-DD').format(); var cloudenddate = moment(res.cloud.ends, 'YYYY-MM-DD').format(); - if ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)) + if ((res.cloud.ends == 0) || (res.cloud.ends == '0')) + isCloudEnabled = true; + else if ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)) isCloudEnabled = true; } if (isLoggedIn) { @@ -298,7 +306,9 @@ if ((res.activation.startdate != undefined) && (res.activation.enddate != undefined)) { var activationstartdate = moment(res.activation.startdate, 'YYYY-MM-DD').format(); var activationenddate = moment(res.activation.enddate, 'YYYY-MM-DD').format(); - if ((activationstartdate.localeCompare(today) < 0) && (activationenddate.localeCompare(today) > 0)) + if ((res.activation.enddate == 0) || (res.activation.enddate == '0')) + isLoggedIn = true; + else if ((activationstartdate.localeCompare(today) < 0) && (activationenddate.localeCompare(today) > 0)) isLoggedIn = true; if (isLoggedIn) { type = "activation"; From 2c67a9285a5d5552e29d3986051202a1a7845f8b Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 26 Aug 2016 13:41:13 +0530 Subject: [PATCH 73/91] Bug Fix for Syncing --- src/app/login/login.service.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 051e20d9..923c7dd8 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -139,7 +139,7 @@ var enddate = moment(res.data.ends, 'YYYY-MM-DD').format(); if ((res.data.ends == 0) || (res.data.ends == '0')) isLoggedIn = true; - else if ((startdate.localeCompare(today) < 0) && (enddate.localeCompare(today) > 0)) + if ((startdate.localeCompare(today) < 0) && (enddate.localeCompare(today) > 0)) isLoggedIn = true; if ((res.data.code != undefined) && (res.data.code == 13)) processMintCode(-404); @@ -281,7 +281,7 @@ var licenseenddate = moment(res.license.ends, 'YYYY-MM-DD').format(); if ((res.license.ends == 0) || (res.license.ends == '0')) isLoggedIn = true; - else if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) + if ((licensestartdate.localeCompare(today) < 0) && (licenseenddate.localeCompare(today) > 0)) isLoggedIn = true; } if (isLoggedIn && res.cloud) { @@ -289,7 +289,7 @@ var cloudenddate = moment(res.cloud.ends, 'YYYY-MM-DD').format(); if ((res.cloud.ends == 0) || (res.cloud.ends == '0')) isCloudEnabled = true; - else if ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)) + if ((cloudstartdate.localeCompare(today) < 0) && (cloudenddate.localeCompare(today) > 0)) isCloudEnabled = true; } if (isLoggedIn) { @@ -308,7 +308,7 @@ var activationenddate = moment(res.activation.enddate, 'YYYY-MM-DD').format(); if ((res.activation.enddate == 0) || (res.activation.enddate == '0')) isLoggedIn = true; - else if ((activationstartdate.localeCompare(today) < 0) && (activationenddate.localeCompare(today) > 0)) + if ((activationstartdate.localeCompare(today) < 0) && (activationenddate.localeCompare(today) > 0)) isLoggedIn = true; if (isLoggedIn) { type = "activation"; From 6dd8ded7c6b2add809d3f57c837b518da39670cb Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 27 Aug 2016 12:20:01 +0530 Subject: [PATCH 74/91] Feature #179 | Part/Treatment Details in Services - Purchase/Cost against the Part/Treatment to get the Profit and Loss --- src/app/app.states.js | 4 ++ .../services/services-add.controller.js | 43 +++++++++++++- .../services/services-edit.controller.js | 58 ++++++++++++++++++- src/app/components/services/services_add.html | 34 ++++++++++- .../services/tmpl2/dialog-id.controller.js | 55 ++++++++++++++++++ .../components/services/tmpl2/dialog-id.html | 50 ++++++++++++++++ .../services/tmpl2/dialog-td.controller.js | 47 +++++++++++++++ .../components/services/tmpl2/dialog-td.html | 37 ++++++++++++ .../treatments/treatments_master.html | 4 +- 9 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 src/app/components/services/tmpl2/dialog-id.controller.js create mode 100644 src/app/components/services/tmpl2/dialog-id.html create mode 100644 src/app/components/services/tmpl2/dialog-td.controller.js create mode 100644 src/app/components/services/tmpl2/dialog-td.html diff --git a/src/app/app.states.js b/src/app/app.states.js index c639888e..530b99ac 100644 --- a/src/app/app.states.js +++ b/src/app/app.states.js @@ -520,6 +520,8 @@ 'app/components/services/tmpl/dialog_membership.edit.controller.js', 'app/components/services/tmpl/dialog_discount.controller.js', 'app/components/services/tmpl/dialog_partialpayment.controller.js', + 'app/components/services/tmpl2/dialog-td.controller.js', + 'app/components/services/tmpl2/dialog-id.controller.js', 'app/components/services/services-add.controller.js' ]) } @@ -536,6 +538,8 @@ 'app/components/services/tmpl/dialog_membership.edit.controller.js', 'app/components/services/tmpl/dialog_discount.controller.js', 'app/components/services/tmpl/dialog_partialpayment.controller.js', + 'app/components/services/tmpl2/dialog-td.controller.js', + 'app/components/services/tmpl2/dialog-id.controller.js', 'app/components/services/services-edit.controller.js' ]) } diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index a660f158..7f28dcce 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -200,6 +200,8 @@ vm.goToDashboard = goToDashboard; vm.IsCustomerSelected = IsCustomerSelected; vm.openCustomerProfile = openCustomerProfile; + vm.openTd = openTd; + vm.openId = openId; // watchers $(window).on('resize', OnWindowResize); @@ -225,6 +227,44 @@ // function definitions + function openId(event, inventory) { + $mdDialog.show({ + controller: 'amCtrlSeId', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl2/dialog-id.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + inventory: inventory + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + // do nothing + } + } + + function openTd(event, problem) { + $mdDialog.show({ + controller: 'amCtrlSeTd', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl2/dialog-td.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + problem: problem + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (!res) + return; + problem.orgcost = res.orgcost; + } + } + function openCustomerProfile() { $state.go('restricted.customers.edit', { id: vm.user.id @@ -666,7 +706,8 @@ } function IsPackageEnabled() { - return (vm.packages && (vm.packages.length > 0)); + return false; + // return (vm.packages && (vm.packages.length > 0)); } function convertVehicleTypeToAF() { diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 565e104a..3e17edc6 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -34,6 +34,7 @@ var dTreatmentTax, dInventoryTax, dTreatment, dInventory; var taxSettingsSnap = [], lastServiceState; var orgVehicle = {}, userMobile = undefined; + var isPackageAvailInService = false; // vm assignments to keep track of UI related elements vm.vehicleTypeList = []; @@ -195,6 +196,8 @@ vm.goToDashboard = goToDashboard; vm.IsCustomerSelected = IsCustomerSelected; vm.openCustomerProfile = openCustomerProfile; + vm.openTd = openTd; + vm.openId = openId; // watchers $(window).on('resize', OnWindowResize); @@ -210,6 +213,44 @@ // function definitions + function openId(event, inventory) { + $mdDialog.show({ + controller: 'amCtrlSeId', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl2/dialog-id.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + inventory: inventory + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + // do nothing + } + } + + function openTd(event, problem) { + $mdDialog.show({ + controller: 'amCtrlSeTd', + controllerAs: 'vm', + templateUrl: 'app/components/services/tmpl2/dialog-td.html', + parent: angular.element(document.body), + targetEvent: event, + locals: { + problem: problem + }, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + if (!res) + return; + problem.orgcost = res.orgcost; + } + } + function openCustomerProfile() { $state.go('restricted.customers.edit', { id: vm.user.id @@ -683,7 +724,8 @@ } function IsPackageEnabled() { - return (vm.packages && (vm.packages.length > 0)); + return isPackageAvailInService; + // return (vm.packages && (vm.packages.length > 0)); } function convertVehicleTypeToAF() { @@ -981,6 +1023,9 @@ found[0].rate = inventory.rate; found[0].amount = (inventory.amount) ? inventory.amount : inventory.rate; found[0].qty = inventory.qty; + found[0].orgcost = inventory.orgcost; + found[0].vendor = inventory.vendor; + found[0].purchasedate = inventory.purchasedate; vm.selectedInventories.push(found[0]); } else { vm.inventories.push({ @@ -988,7 +1033,10 @@ rate: inventory.rate, amount: (inventory.amount) ? inventory.amount : inventory.rate, qty: inventory.qty, - checked: true + checked: true, + orgcost: inventory.orgcost, + vendor: inventory.vendor, + purchasedate: inventory.purchasedate }); vm.selectedInventories.push(vm.inventories[vm.inventories.length - 1]); } @@ -1513,6 +1561,8 @@ vm.vehicle.type = res.vehicle.type; if (res.vehicle.service.packages) { vm.serviceType = vm.serviceTypeList[1]; + if (Object.keys(res.vehicle.service.packages).length > 0) + isPackageAvailInService = true; Object.keys(res.vehicle.service.packages).forEach(iteratePackages); } if (res.vehicle.service.memberships) { @@ -1818,6 +1868,7 @@ found[0].rate = problem.rate; found[0].amount = (problem.amount) ? problem.amount : Math.round(problem.rate); found[0].tax = problem.tax; + found[0].orgcost = problem.orgcost; vm.selectedProblems.push(found[0]); } else { vm.service.problems.push({ @@ -1825,7 +1876,8 @@ rate: problem.rate, amount: (problem.amount) ? problem.amount : Math.round(problem.rate), tax: problem.tax, - checked: true + checked: true, + orgcost: problem.orgcost }); vm.selectedProblems.push(vm.service.problems[vm.service.problems.length - 1]); } diff --git a/src/app/components/services/services_add.html b/src/app/components/services/services_add.html index e946acdf..3fe3d06a 100644 --- a/src/app/components/services/services_add.html +++ b/src/app/components/services/services_add.html @@ -409,12 +409,13 @@ Treatments - ( - Show AllShow All Treatments - ) + ( + Show AllShow All Treatments + ) Amount +   @@ -423,6 +424,11 @@ + + + more + + @@ -435,6 +441,7 @@ +   @@ -453,6 +460,7 @@ Amount +   @@ -461,6 +469,11 @@ + + + more + + @@ -473,6 +486,7 @@ +   @@ -492,6 +506,7 @@ Rate Qty Amount +   @@ -506,6 +521,11 @@ + + + more + + @@ -524,6 +544,7 @@ +   @@ -543,6 +564,7 @@ Rate Qty Amount +   @@ -557,6 +579,11 @@ + + + more + + @@ -575,6 +602,7 @@ +   diff --git a/src/app/components/services/tmpl2/dialog-id.controller.js b/src/app/components/services/tmpl2/dialog-id.controller.js new file mode 100644 index 00000000..bfb54263 --- /dev/null +++ b/src/app/components/services/tmpl2/dialog-id.controller.js @@ -0,0 +1,55 @@ +/** + * Controller to handle Inventory Details (td) dialog box + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlSeId', IdController); + + IdController.$inject = ['$mdDialog', 'inventory']; + + function IdController($mdDialog, inventory) { + // intialize view model + var vm = this; + + // temporary assignments for controller instance + // no such assignments + + // named assignments for view model + vm.inventory = inventory; + + // function mappings to view model + vm.OnKeyDown = OnKeyDown; + vm.submit = submit; + vm.getDate = getDate; + + // default execution steps + setTimeout(focusOriginalCost, 800); + if (vm.inventory.purchasedate) + vm.inventory.purchasedate = new Date(vm.inventory.purchasedate); + + // function definitions + + function getDate(date) { + return moment(date).format('DD MMM YYYY'); + } + + function focusOriginalCost() { + $('#ami-did-orgcost').focus(); + } + + function OnKeyDown(event) { + if (event.keyCode == 13) + submit(); + } + + function submit() { + vm.inventory.purchasedate = moment(vm.inventory.purchasedate).format(); + $mdDialog.hide(vm.inventory); + } + } +})(); \ No newline at end of file diff --git a/src/app/components/services/tmpl2/dialog-id.html b/src/app/components/services/tmpl2/dialog-id.html new file mode 100644 index 00000000..9033244f --- /dev/null +++ b/src/app/components/services/tmpl2/dialog-id.html @@ -0,0 +1,50 @@ + + + +
+ {{vm.inventory.name}} +
+
+ + +
+
+ + +
+
+ + + {{vm.getDate(vm.inventory.purchasedate)}} +
+
+ + + done + Done + + +
\ No newline at end of file diff --git a/src/app/components/services/tmpl2/dialog-td.controller.js b/src/app/components/services/tmpl2/dialog-td.controller.js new file mode 100644 index 00000000..869ad4bf --- /dev/null +++ b/src/app/components/services/tmpl2/dialog-td.controller.js @@ -0,0 +1,47 @@ +/** + * Controller to handle Treatment Details (td) dialog box + * @author ndkcha + * @since 0.7.0 + * @version 0.7.0 + */ + +/// + +(function() { + angular.module('automintApp').controller('amCtrlSeTd', TdController); + + TdController.$inject = ['$mdDialog', 'problem']; + + function TdController($mdDialog, problem) { + // intialize view model + var vm = this; + + // temporary assignments for controller instance + // no such assignments + + // named assignments for view model + vm.problem = problem; + + // function mappings to view model + vm.OnKeyDown = OnKeyDown; + vm.submit = submit; + + // default execution steps + setTimeout(focusOriginalCost, 800); + + // function definitions + + function focusOriginalCost() { + $('#ami-dtd-orgcost').focus(); + } + + function OnKeyDown(event) { + if (event.keyCode == 13) + submit(); + } + + function submit() { + $mdDialog.hide(vm.problem); + } + } +})(); \ No newline at end of file diff --git a/src/app/components/services/tmpl2/dialog-td.html b/src/app/components/services/tmpl2/dialog-td.html new file mode 100644 index 00000000..813dfe3d --- /dev/null +++ b/src/app/components/services/tmpl2/dialog-td.html @@ -0,0 +1,37 @@ + + + +
+ {{vm.problem.details}} +
+
+ + +
+
+ + + done + Done + + +
\ No newline at end of file diff --git a/src/app/components/treatments/treatments_master.html b/src/app/components/treatments/treatments_master.html index 67ea7d02..6aabf4fe 100644 --- a/src/app/components/treatments/treatments_master.html +++ b/src/app/components/treatments/treatments_master.html @@ -53,7 +53,7 @@ - + From b0db4928e64c394159409adf4ca0aaea35929b0b Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sat, 27 Aug 2016 16:56:16 +0530 Subject: [PATCH 75/91] FIx: Refresh Dashboard --- src/app/appbar/headerbar.controller.js | 21 ++++++++++++++++++--- src/app/appbar/headerbar.html | 2 +- src/assets/css/automint.css | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/app/appbar/headerbar.controller.js b/src/app/appbar/headerbar.controller.js index 90cecaf5..33c7dc67 100644 --- a/src/app/appbar/headerbar.controller.js +++ b/src/app/appbar/headerbar.controller.js @@ -13,9 +13,9 @@ angular.module('automintApp').controller('amCtrlHeaderbar', HeaderBarController); - HeaderBarController.$inject = ['$rootScope', '$scope', '$state', '$timeout', '$mdSidenav', 'amAppbar']; + HeaderBarController.$inject = ['$rootScope', '$scope', '$state', '$timeout', '$mdSidenav', '$amRoot', 'amAppbar']; - function HeaderBarController($rootScope, $scope, $state, $timeout, $mdSidenav, amAppbar) { + function HeaderBarController($rootScope, $scope, $state, $timeout, $mdSidenav, $amRoot, amAppbar) { var vm = this; // map functions to view model @@ -31,8 +31,23 @@ // function definitions - function relaunch() { + /*function relaunch() { ipcRenderer.send('am-do-restart', true); + }*/ + + function relaunch() { + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = "Refreshing Dashboard.."; + $amRoot.generateCacheDocs(true).then(fakeWait).catch(fakeWait); + + function fakeWait(res) { + setTimeout(proceed, 1000); + } + + function proceed() { + $rootScope.busyApp.show = false; + $state.go('home'); + } } function addService() { diff --git a/src/app/appbar/headerbar.html b/src/app/appbar/headerbar.html index efaaac4e..15fcc156 100644 --- a/src/app/appbar/headerbar.html +++ b/src/app/appbar/headerbar.html @@ -9,7 +9,7 @@
refresh - Relaunch App + Refresh Dashboard add diff --git a/src/assets/css/automint.css b/src/assets/css/automint.css index 02f72532..bd724968 100644 --- a/src/assets/css/automint.css +++ b/src/assets/css/automint.css @@ -22,7 +22,7 @@ #page_preloader { width: 100%; height: 100%; - z-index: 999; + z-index: 997; background: white; } From e977898fad0c1415e633eff275b57de04ad5114b Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Sun, 28 Aug 2016 10:25:50 +0530 Subject: [PATCH 76/91] Bugs #207 #208 207. User not able to login, it continuously show processing bar #208. Treatments not showing, after sync --- src/app/app.service.js | 8 ++++++ src/app/appbar/headerbar.controller.js | 17 +----------- src/app/appbar/headerbar.html | 2 +- .../dashboard/dashboard.controller.js | 26 ++++++++++++++++--- src/app/components/dashboard/dashboard.html | 24 ++++++++++++++++- .../treatments/treatments.factory.js | 2 ++ src/app/login/login.service.js | 4 ++- 7 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index 2c90957c..22bf503c 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -204,6 +204,8 @@ _id: constants.pdb_cache_views.view_services } } + if (curdoc.user == undefined) + return; if (curdoc.user.vehicles) Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); pdbCache.save(cachedoc); @@ -250,6 +252,8 @@ } } + if (curdoc.user == undefined) + return; if (curdoc.user.vehicles) Object.keys(curdoc.user.vehicles).forEach(iterateVehicles); pdbCache.save(cachedoc); @@ -324,6 +328,8 @@ function iterateRows(row) { if (IsConfigDoc(row.id)) return; + if (row.doc.user == undefined) + return; if (row.doc.user.vehicles) Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); @@ -358,6 +364,8 @@ function iterateRows(row) { if (IsConfigDoc(row.id)) return; + if (row.doc.user == undefined) + return; if (row.doc.user.vehicles) Object.keys(row.doc.user.vehicles).forEach(iterateVehicles); diff --git a/src/app/appbar/headerbar.controller.js b/src/app/appbar/headerbar.controller.js index 33c7dc67..c3d90ba5 100644 --- a/src/app/appbar/headerbar.controller.js +++ b/src/app/appbar/headerbar.controller.js @@ -31,23 +31,8 @@ // function definitions - /*function relaunch() { - ipcRenderer.send('am-do-restart', true); - }*/ - function relaunch() { - $rootScope.busyApp.show = true; - $rootScope.busyApp.message = "Refreshing Dashboard.."; - $amRoot.generateCacheDocs(true).then(fakeWait).catch(fakeWait); - - function fakeWait(res) { - setTimeout(proceed, 1000); - } - - function proceed() { - $rootScope.busyApp.show = false; - $state.go('home'); - } + ipcRenderer.send('am-do-restart', true); } function addService() { diff --git a/src/app/appbar/headerbar.html b/src/app/appbar/headerbar.html index 15fcc156..51ea7d2a 100644 --- a/src/app/appbar/headerbar.html +++ b/src/app/appbar/headerbar.html @@ -9,7 +9,7 @@
refresh - Refresh Dashboard + Relaunch Automint add diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index 6ea43057..ec79b994 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -12,9 +12,9 @@ angular.module('automintApp').controller('dashboardCtrl', DashboardController); - DashboardController.$inject = ['$rootScope', '$state', '$filter', '$log', '$mdDialog', 'utils', 'amDashboard']; + DashboardController.$inject = ['$rootScope', '$state', '$filter', '$log', '$mdDialog', '$amRoot', 'utils', 'amDashboard']; - function DashboardController($rootScope, $state, $filter, $log, $mdDialog, utils, amDashboard) { + function DashboardController($rootScope, $state, $filter, $log, $mdDialog, $amRoot, utils, amDashboard) { // initialize view model var vm = this; var ubServices = [], nwCustomers = [], filterRange = [], isNextDueServicesOpened = false; @@ -98,7 +98,7 @@ vm.openNextDueServices = openNextDueServices; vm.IsNoNextDueReminders = IsNoNextDueReminders; vm.IsReminderInPast = IsReminderInPast; - + vm.refreshDashboard = refreshDashboard; // default execution steps $rootScope.hidePreloader = true; @@ -109,6 +109,26 @@ // function definitions + function refreshDashboard() { + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Refreshing Dashboard....'; + $rootScope.isOnChangeMainDbBlocked = true; + $amRoot.generateCacheDocs(true).then(fakeWait).catch(fakeWait); + + function fakeWait() { + $rootScope.isOnChangeMainDbBlocked = false; + setTimeout(reloadData, 1000); + } + + function reloadData() { + $rootScope.busyApp.show = false; + initCurrentTimeSet(); + getFilterMonths(); + processPreferences(); + getCurrencySymbol(); + } + } + function openCurrencyDialog() { $mdDialog.show({ controller: 'amCtrlDashCurrency', diff --git a/src/app/components/dashboard/dashboard.html b/src/app/components/dashboard/dashboard.html index 5dd90a20..fa231541 100644 --- a/src/app/components/dashboard/dashboard.html +++ b/src/app/components/dashboard/dashboard.html @@ -75,9 +75,31 @@ .am-dashboard-table-nxtdue tr:hover td { color: #212121; } + + .amb-refresh { + outline: none; + cursor: pointer; + transition: background 300ms ease; + padding-right: 14px; + } + + .amb-refresh:hover { + background: rgba(0, 0, 0, 0.1); + } + + .amb-refresh:focus { + background: rgba(0, 0, 0, 0.1); + } - + +
+ + refresh + + Refresh Dashboard +
+
Showing data for : {{vm.ddTimeSet}} date_range diff --git a/src/app/components/treatments/treatments.factory.js b/src/app/components/treatments/treatments.factory.js index 094ff8f6..1a6a1906 100644 --- a/src/app/components/treatments/treatments.factory.js +++ b/src/app/components/treatments/treatments.factory.js @@ -142,6 +142,7 @@ treatments: [], total: 0 }; + console.log($rootScope.amGlobals.configDocIds.treatment); pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(failure); return tracker.promise; @@ -161,6 +162,7 @@ } function failure(err) { + console.log(err); tracker.reject(response); } } diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 923c7dd8..1f0107c2 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -193,12 +193,14 @@ processMintCode(-1); return; } + console.log('channel: ' + channel); $rootScope.amGlobals.configDocIds = { settings: 'settings' + (channel ? '-' + channel : ''), - treatment: 'treatment' + (channel ? '-' + channel : ''), + treatment: 'treatments' + (channel ? '-' + channel : ''), inventory: 'inventory' + (channel ? '-' + channel : ''), workshop: 'workshop' + (channel ? '-' + channel : '') }; + console.log($rootScope.amGlobals.configDocIds); var isDbToBeChanged = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.creator != '') && ($rootScope.amGlobals.channel != '')); From b734db234e80c5e5b02b5b84d3803785ed49ef16 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Mon, 29 Aug 2016 11:02:15 +0530 Subject: [PATCH 77/91] Feature #179 | CtS from Origin - Add Treatment related fields while Adding Treatment to database --- .../services/services-add.controller.js | 9 ++++-- .../services/services-edit.controller.js | 9 ++++-- .../components/services/services.factory.js | 7 +++-- .../regular/treatments-add.controller.js | 16 +++++++++++ .../regular/treatments-edit.controller.js | 28 +++++++++++++++++++ .../treatments/regular/treatments_add.html | 11 +++++++- .../treatments/treatments.factory.js | 6 ++-- src/assets/css/automint.css | 11 ++++++-- 8 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/app/components/services/services-add.controller.js b/src/app/components/services/services-add.controller.js index 7f28dcce..6d1b1932 100644 --- a/src/app/components/services/services-add.controller.js +++ b/src/app/components/services/services-add.controller.js @@ -1797,6 +1797,8 @@ problem.tax = {}; vm.taxSettings.forEach(iterateTaxes); problem.rate = parseFloat(taxable); + if (found[0].orgcost && found[0].orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]) + problem.orgcost = found[0].orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; function iterateTaxes(tax) { if (!tax.isForTreatments) @@ -1846,12 +1848,15 @@ getTreatmentsTax(); function iterateTreatment(treatment) { - vm.service.problems.push({ + var p = { details: treatment.name, rate: treatment.rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')], amount: treatment.rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')], checked: false - }); + }; + if (treatment.orgcost && treatment.orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]) + p.orgcost = treatment.orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; + vm.service.problems.push(p); } } diff --git a/src/app/components/services/services-edit.controller.js b/src/app/components/services/services-edit.controller.js index 3e17edc6..366d656d 100644 --- a/src/app/components/services/services-edit.controller.js +++ b/src/app/components/services/services-edit.controller.js @@ -1810,6 +1810,8 @@ problem.tax = {}; vm.taxSettings.forEach(iterateTaxes); problem.rate = parseFloat(taxable); + if (found[0].orgcost && found[0].orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]) + problem.orgcost = found[0].orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; function iterateTaxes(tax) { if (!tax.isForTreatments) @@ -1884,12 +1886,15 @@ } function iterateTreatment(treatment) { - vm.service.problems.push({ + var p = { details: treatment.name, rate: treatment.rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')], amount: (treatment.amount) ? treatment.amount : treatment.rate[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')], checked: false - }); + }; + if (treatment.orgcost && treatment.orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]) + p.orgcost = treatment.orgcost[angular.lowercase(vm.vehicle.type).replace(/\s/g, '-')]; + vm.service.problems.push(p); } } diff --git a/src/app/components/services/services.factory.js b/src/app/components/services/services.factory.js index 76d00560..ee6dc7b0 100644 --- a/src/app/components/services/services.factory.js +++ b/src/app/components/services/services.factory.js @@ -479,10 +479,13 @@ function iterateTreatments(name) { if (!res.regular[name]._deleted) { - treatments.push({ + var t = { name: name, rate: res.regular[name].rate - }) + }; + if (res.regular[name].orgcost) + t.orgcost = res.regular[name].orgcost; + treatments.push(t); } } } diff --git a/src/app/components/treatments/regular/treatments-add.controller.js b/src/app/components/treatments/regular/treatments-add.controller.js index 45d0a43e..1d129c28 100644 --- a/src/app/components/treatments/regular/treatments-add.controller.js +++ b/src/app/components/treatments/regular/treatments-add.controller.js @@ -63,6 +63,7 @@ vm.rates.push({ type: utils.convertToTitleCase(type.replace(/-/g, ' ')), value: '', + orgcost: '', fromDb: true, focusIndex: vm.rates.length }); @@ -80,6 +81,7 @@ vm.rates.push({ type: '', value: '', + orgcost: '', fromDb: false, focusIndex: fIndex }); @@ -113,9 +115,23 @@ } return result; } + + function generateOrgCost() { + var result = {}; + vm.rates.forEach(iterateRate); + + function iterateRate(rate) { + rate.type = rate.type.trim(); + if (rate.type && rate.orgcost != '') + result[angular.lowercase(rate.type.replace(/\s/g, '-'))] = rate.orgcost; + } + return result; + } function save() { vm.treatment.rate = generateRate(); + if (vm.isOrgCostEnabled) + vm.treatment.orgcost = generateOrgCost(); amTreatments.saveVehicleTypes(Object.keys(vm.treatment.rate)); amTreatments.saveTreatment(vm.treatment, vm.operationMode).then(success).catch(failure); diff --git a/src/app/components/treatments/regular/treatments-edit.controller.js b/src/app/components/treatments/regular/treatments-edit.controller.js index 8ebcb60f..f750c7d9 100644 --- a/src/app/components/treatments/regular/treatments-edit.controller.js +++ b/src/app/components/treatments/regular/treatments-edit.controller.js @@ -71,6 +71,18 @@ vm.treatment.name = res.name; changeNameLabel(); Object.keys(res.rate).forEach(iterateRate); + if (res.orgcost) { + vm.isOrgCostEnabled = true; + Object.keys(res.orgcost).forEach(iterateOrgCost); + } + + function iterateOrgCost(rk) { + var found = $filter('filter')(vm.rates, { + type: utils.convertToTitleCase(rk.replace(/-/g, ' ')) + }, true); + if (found.length == 1) + found[0].orgcost = res.orgcost[rk]; + } function iterateRate(rk) { var found = $filter('filter')(vm.rates, { @@ -97,6 +109,7 @@ vm.rates.push({ type: utils.convertToTitleCase(type.replace(/-/g, ' ')), value: '', + orgcost: '', fromDb: true, focusIndex: vm.rates.length }); @@ -116,6 +129,7 @@ type: '', value: '', fromDb: false, + orgcost: '', focusIndex: fIndex }); if (focus) { @@ -144,9 +158,23 @@ } return result; } + + function generateOrgCost() { + var result = {}; + vm.rates.forEach(iterateRate); + + function iterateRate(rate) { + rate.type = rate.type.trim(); + if (rate.type && rate.orgcost != '') + result[angular.lowercase(rate.type.replace(/\s/g, '-'))] = rate.orgcost; + } + return result; + } function save() { vm.treatment.rate = generateRate(); + if (vm.isOrgCostEnabled) + vm.treatment.orgcost = generateOrgCost(); amTreatments.saveVehicleTypes(Object.keys(vm.treatment.rate)); amTreatments.saveTreatment(vm.treatment, vm.operationMode).then(success).catch(failure); diff --git a/src/app/components/treatments/regular/treatments_add.html b/src/app/components/treatments/regular/treatments_add.html index 4c64ca47..5f6e4896 100644 --- a/src/app/components/treatments/regular/treatments_add.html +++ b/src/app/components/treatments/regular/treatments_add.html @@ -4,19 +4,25 @@
- + spa
+
+ + Original Cost to Center + +
+ @@ -27,6 +33,9 @@ +
Vehicle Type RateOriginal Cost to Center
+ +
diff --git a/src/app/components/treatments/treatments.factory.js b/src/app/components/treatments/treatments.factory.js index 1a6a1906..dfeca7e5 100644 --- a/src/app/components/treatments/treatments.factory.js +++ b/src/app/components/treatments/treatments.factory.js @@ -127,6 +127,8 @@ name: treatmentName, rate: res.regular[treatmentName].rate } + if (res.regular[treatmentName].orgcost) + treatment.orgcost = res.regular[treatmentName].orgcost; tracker.resolve(treatment); } @@ -142,7 +144,6 @@ treatments: [], total: 0 }; - console.log($rootScope.amGlobals.configDocIds.treatment); pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(failure); return tracker.promise; @@ -162,7 +163,6 @@ } function failure(err) { - console.log(err); tracker.reject(response); } } @@ -193,6 +193,8 @@ var i = { rate: treatment.rate }; + if (treatment.orgcost) + i.orgcost = treatment.orgcost; pdbMain.get($rootScope.amGlobals.configDocIds.treatment).then(treatmentDocFound).catch(noTreatmentDoc); return tracker.promise; diff --git a/src/assets/css/automint.css b/src/assets/css/automint.css index bd724968..c6b1d36a 100644 --- a/src/assets/css/automint.css +++ b/src/assets/css/automint.css @@ -150,17 +150,22 @@ input[type=number]::-webkit-outer-spin-button { -webkit-transition: color 1s ease 0s; -moz-transition: color 1s ease 0s; transition: color 1s ease 0s; - box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0.3); + box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0.2); } .am-table-input:focus { color: rgba(30, 30, 30, 0.9); - box-shadow: 1px 1px 3px 1px rgba(30, 30, 30, 0.3); + box-shadow: 1px 1px 3px 1px rgba(30, 30, 30, 0.2); } .am-table-input.search-box { font-size: small; - box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0.3); + box-shadow: 0px 1px 3px 1px rgba(30, 30, 30, 0.2); +} + +.am-table-input:disabled { + background: white; + box-shadow: none; } .am-checkbox .md-icon { From fac701259d683497a24def888e4db25261159f2a Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 30 Aug 2016 14:51:24 +0530 Subject: [PATCH 78/91] Enhancement #209 | Remove Button in Id - Services - 'Remove' button along with 'Done' button in the 'purchase/cost against part/treatment' dialogue box --- .../services/tmpl2/dialog-id.controller.js | 6 ++++++ src/app/components/services/tmpl2/dialog-id.html | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/app/components/services/tmpl2/dialog-id.controller.js b/src/app/components/services/tmpl2/dialog-id.controller.js index bfb54263..94f3baec 100644 --- a/src/app/components/services/tmpl2/dialog-id.controller.js +++ b/src/app/components/services/tmpl2/dialog-id.controller.js @@ -26,6 +26,7 @@ vm.OnKeyDown = OnKeyDown; vm.submit = submit; vm.getDate = getDate; + vm.clearall = clearall; // default execution steps setTimeout(focusOriginalCost, 800); @@ -34,6 +35,11 @@ // function definitions + function clearall() { + delete vm.inventory.orgcost; + delete vm.inventory.vendor; + } + function getDate(date) { return moment(date).format('DD MMM YYYY'); } diff --git a/src/app/components/services/tmpl2/dialog-id.html b/src/app/components/services/tmpl2/dialog-id.html index 9033244f..655e239f 100644 --- a/src/app/components/services/tmpl2/dialog-id.html +++ b/src/app/components/services/tmpl2/dialog-id.html @@ -21,6 +21,14 @@ .am-purdate-datepicker .md-datepicker-button { margin: 0; } + + .am-dd-cancel { + color: #F44336; + } + + .am-dd-cancel md-icon { + color: #F44336; + } @@ -42,6 +50,10 @@
+ + close + Clear All + done Done From 07210b364352d02a6479a989366fb8207d9a6f86 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Tue, 30 Aug 2016 16:01:41 +0530 Subject: [PATCH 79/91] Enhancement in Feature #185 | On Password Change --- src/app/app.controller.js | 65 +++++++++++++++- src/app/app.db.js | 2 +- src/app/app.service.js | 68 +++++++++++++---- .../settings/settings.controller.js | 19 ++--- src/app/login/login.controller.js | 12 ++- src/app/login/login.service.js | 75 ++++++++++++++++--- src/app/views/updatepassword.tmpl.html | 51 +++++++++++++ src/assets/css/automint.css | 4 + src/index.html | 5 +- 9 files changed, 254 insertions(+), 47 deletions(-) create mode 100644 src/app/views/updatepassword.tmpl.html diff --git a/src/app/app.controller.js b/src/app/app.controller.js index 0ddf4050..2d11c1d8 100644 --- a/src/app/app.controller.js +++ b/src/app/app.controller.js @@ -9,17 +9,18 @@ (function() { - angular.module('automintApp').controller('amCtrl', HomeController); + angular.module('automintApp').controller('amCtrl', HomeController).controller('amUpdatePwdCtrl', UpdatePasswordController); HomeController.$inject = ['$rootScope', '$state', '$amRoot', '$amLicense', 'utils']; + UpdatePasswordController.$inject = ['$mdDialog', '$rootScope', '$amLicense', '$amRoot']; - function HomeController($rootScope, $state, $amRoot, $amLicense, utils, amLogin) { + function HomeController($rootScope, $state, $amRoot, $amLicense, utils) { // initialize view model var vm = this; // default execution steps $rootScope.isFirstLoad = true; - $amLicense.checkLogin().then(success).catch(failure); + $amLicense.checkLogin(true).then(success).catch(failure); // function definitions @@ -35,8 +36,66 @@ } function failure(err) { + if (err != undefined) + utils.showSimpleToast(err); $rootScope.hidePreloader = true; $state.go('login'); } } + + function UpdatePasswordController($mdDialog, $rootScope, $amLicense, $amRoot) { + // initialize view model + var vm = this; + + // temporary assignments for the instance + // no such assignments for now + + // named assignments to view model + vm.password = ''; + + // function mappings to view model + vm.OnKeyDown = OnKeyDown; + vm.submit = submit; + + // default execution steps + setTimeout(focusPassword, 300); + + // function definitions + + function focusPassword() { + $('#ami-password').focus(); + } + + function OnKeyDown(event) { + if (event.keyCode == 13) + submit(); + } + + function submit() { + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = 'Loging In. Please Wait..'; + $amLicense.loadCredentials($rootScope.amGlobals.credentials.username, vm.password); + $amLicense.login(false).then(success).catch(failure); + + function success(res) { + $rootScope.busyApp.show = false; + if (res.isLoggedIn) { + if (res.isSyncableDb) { + if ($rootScope.amDbSync) + $rootScope.amDbSync.cancel(); + $amRoot.syncDb(); + } + $mdDialog.hide(); + } else { + utils.showSimpleToast('Your license has expired!'); + $state.go('login'); + } + } + + function failure(err) { + $rootScope.busyApp.show = false; + vm.message = err.message; + } + } + } })(); \ No newline at end of file diff --git a/src/app/app.db.js b/src/app/app.db.js index ca543f6c..191fb98b 100644 --- a/src/app/app.db.js +++ b/src/app/app.db.js @@ -174,7 +174,7 @@ if (!syncOptions) { syncOptions = { live: true, - retry: true + retry: false }; } return database.sync(remoteDb, syncOptions); diff --git a/src/app/app.service.js b/src/app/app.service.js index 22bf503c..33120c9d 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -12,9 +12,9 @@ angular.module('automintApp').service('$amRoot', AutomintService); - AutomintService.$inject = ['$rootScope', '$state', '$q', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory']; + AutomintService.$inject = ['$rootScope', '$state', '$q', '$mdDialog', 'constants', 'pdbMain', 'pdbCache', 'pdbLocal', 'amFactory']; - function AutomintService($rootScope, $state, $q, constants, pdbMain, pdbCache, pdbLocal, amFactory) { + function AutomintService($rootScope, $state, $q, $mdDialog, constants, pdbMain, pdbCache, pdbLocal, amFactory) { // set up service view model var vm = this; @@ -82,16 +82,23 @@ console.error('Username/Password/Database name missing from url'); return; } - $rootScope.amDbSync = pdbMain.sync(url).on('change', onChangedDb).on('paused', onPausedDb).on('active', onActiveDb).on('denied', onDeniedDb).on('complete', onCompleteDb).on('error', onErrorDb); + + $rootScope.amDbSync = pdbMain.sync(url); + $rootScope.amDbSync.on('complete', onCompleteDb); + $rootScope.amDbSync.on('paused', onPausedDb); + $rootScope.amDbSync.on('error', onErrorDb); + $rootScope.amDbSync.on('denied', onDeniedDb); + $rootScope.amDbSync.on('change', onChangedDb); + $rootScope.amDbSync.on('active', onActiveDb); function onChangedDb(info) { // listen to on change event - console.info('pdbSync', info); + console.info('pdbSync change', info); } function onPausedDb(err) { // listen to on pause event\ - console.warn('pdbSync', err); + console.warn('pdbSync pause', err); } function onActiveDb() { @@ -100,17 +107,45 @@ function onDeniedDb(err) { // listen to on denied event - console.error('pdbSync', err); + if (err.status && (err.status == 401)) { + $mdDialog.show({ + controller: 'amUpdatePwdCtrl', + controllerAs: 'vm', + templateUrl: 'app/views/updatepassword.tmpl.html', + parent: angular.element(document.body), + targetEvent: event, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + // do nothing + } + } + console.error('pdbSync deny', err); } function onCompleteDb(info) { // listen to on complete event - console.info('pdbSync', info); + console.info('pdbSync complete', info); } function onErrorDb(err) { // listen to on error event - console.error('pdbSync', err); + if (err.status && (err.status == 401)) { + $mdDialog.show({ + controller: 'amUpdatePwdCtrl', + controllerAs: 'vm', + templateUrl: 'app/views/updatepassword.tmpl.html', + parent: angular.element(document.body), + targetEvent: event, + clickOutsideToClose: true + }).then(success).catch(success); + + function success(res) { + // do nothing + } + } + console.error('pdbSync error', err); } } @@ -147,7 +182,7 @@ } function checkAutomintValidity() { - $amLicense.checkLogin().then(success).catch(failure); + $amLicense.checkLogin(true).then(success).catch(failure); function success(res) { if (res.isLoggedIn) { @@ -155,15 +190,18 @@ if ($rootScope.amDbSync) $rootScope.amDbSync.cancel(); } - } else { - $rootScope.busyApp.show = true; - $rootScope.busyApp.message = "Your license has expired! Try Loging Again or Contact Automint Care!"; - setTimeout(failure, 1000); - } + } else + failure("Something went wrong. Login Again"); } function failure(err) { - $rootScope.busyApp.message = "Your license has expired! Restarting App. Please Wait..."; + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = err; + $rootScope.busyApp.isRaEnabled = true; + setTimeout(restartApp, 1000); + } + + function restartApp(err) { ipcRenderer.send('am-do-restart', true); } } diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index 47a457d0..dcd0a51b 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -225,7 +225,7 @@ } function getLoginState() { - $amLicense.checkLogin().then(success).catch(failure); + $amLicense.checkLogin(false).then(success).catch(failure); function success(res) { vm.cloudSettings.enable = ((res.isCloudEnabled != undefined) ? res.isCloudEnabled : false); @@ -249,19 +249,12 @@ } function failure(err) { - $mdDialog.show( - $mdDialog.alert() - .parent(document.body) - .clickOutsideToClose(false) - .title('License Error!') - .textContent('Automint cannot identify your license. Please Login Again or Contact Automint Care!') - .ariaLabel('License Error') - .ok('Got it!') - ).then(restartApp).catch(restartApp); + $rootScope.busyApp.show = true; + $rootScope.busyApp.message = err; + $rootScope.busyApp.isRaEnabled = true; + setTimeout(restartApp, 1000); function restartApp(res) { - $rootScope.busyApp.show = true; - $rootScope.busyApp.message = 'Restarting App due to License Error. Please Wait..'; ipcRenderer.send('am-do-restart', true); } console.error(err); @@ -307,7 +300,7 @@ function failure(err) { $rootScope.busyApp.show = false; - vm.cloudSettings.message = err; + vm.cloudSettings.message = err.message; } } diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index 37e12aa8..f60f9f99 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -79,14 +79,17 @@ vm.message = undefined; if ((vm.username == '') && (vm.password == '') && (vm.code == '')) { vm.message = 'Please Enter Username and Password, or Code!'; + setTimeout(focusUsername, 300); return; } if (((vm.username != '') && (vm.password == '')) && (vm.code == '')) { vm.message = 'Please Enter Password!'; + setTimeout(focusPassword, 300); return; } if (((vm.password != '') && (vm.username == '')) && (vm.code == '')) { vm.message = 'Please Enter Username!'; + setTimeout(focusUsername, 300); return; } vm.isLogingIn = true; @@ -104,13 +107,16 @@ } vm.message = "Preparing Dashboard.."; $amRoot.dbAfterLogin(true); - } else - failure('Your license has expired!'); + } else { + failure({ + message: 'Your license has expired!' + }); + } } function failure(err) { vm.isLogingIn = false; - vm.message = err; + vm.message = err.message; } } } diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 1f0107c2..1ddb8839 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -105,7 +105,7 @@ } } - function login(isCodeUsed, code) { + function login(isCodeUsed, code, ignoreCodeUsedError) { var today = moment().format(), isLoggedIn = false, channel, isSyncableDb = false; var tracker = $q.defer(); if (isCodeUsed) @@ -131,7 +131,7 @@ failure(); return; } - if (res.data.used) { + if ((ignoreCodeUsedError != true) && res.data.used) { tracker.reject('The code is already in use! You cannot use it again!'); return; } @@ -193,14 +193,12 @@ processMintCode(-1); return; } - console.log('channel: ' + channel); $rootScope.amGlobals.configDocIds = { settings: 'settings' + (channel ? '-' + channel : ''), treatment: 'treatments' + (channel ? '-' + channel : ''), inventory: 'inventory' + (channel ? '-' + channel : ''), workshop: 'workshop' + (channel ? '-' + channel : '') }; - console.log($rootScope.amGlobals.configDocIds); var isDbToBeChanged = (($rootScope.amGlobals != undefined) && ($rootScope.amGlobals.creator != '') && ($rootScope.amGlobals.channel != '')); @@ -252,22 +250,77 @@ message = 'Invalid Activation Code!'; break; } - tracker.reject(message); + tracker.reject({ + code: code, + message: message + }); } function failure(err) { if (err) console.error(err); - tracker.reject('Please check your internet connectivity!'); + tracker.reject({ + message: 'Please check your internet connectivity!' + }); } - } + } - function checkLogin() { + function checkLogin(isLdtbSynced) { var tracker = $q.defer(); + var errm = undefined; var today = moment().format(), isLoggedIn = false, isCloudEnabled = false, type, isCloudForceEnabled; - pdbLocal.get(constants.pdb_local_docs.login).then(checkFlags).catch(failure); + if (isLdtbSynced) + pdbLocal.get(constants.pdb_local_docs.login).then(performLogin).catch(failure); + else + pdbLocal.get(constants.pdb_local_docs.login).then(checkFlags).catch(failure); return tracker.promise; + function performLogin(extLd) { + if (extLd.isLoggedIn) { + if (extLd.username && extLd.password) { + loadCredentials(extLd.username, extLd.password); + login(false).then(furtherCheck).catch(handleError); + } else if (extLd.activation && extLd.activation.code) + login(true, extLd.activation.code, true).then(furtherCheck).catch(handleError); + else { + failure(); + } + } else { + failure({ + mintSkipSave: true + }); + } + + function handleError(err) { + if (err.code == undefined) { + furtherCheck(); + return + } + switch (err.code) { + case -404: + errm = 'License Error: Invalid Activation Code!'; + break; + case -1: + errm = 'Your license has expired!'; + break; + case 200: + errm = 'Your password has been changed. Please Login Again'; + break; + case 300: + errm = 'There seems to be problem at Server! Please try again or contact Automint Care!'; + break; + case 330: + errm = 'No Licensing Details Found for ' + $rootScope.amGlobals.credentials.username + '! Please Try Again or Contact Automint Care!'; + break; + } + failure(err); + } + + function furtherCheck(res) { + pdbLocal.get(constants.pdb_local_docs.login).then(checkFlags).catch(failure); + } + } + function checkFlags(res) { if (!res.isLoggedIn) { failure({ @@ -322,7 +375,7 @@ } } } - failure(); + failure(res); function respond(saveresponse) { $rootScope.amGlobals.creator = (res.username ? res.username : ''); @@ -363,7 +416,7 @@ amLogin.setLoginState(false).then(ro).catch(ro); function ro(rores) { - tracker.reject(err); + tracker.reject(errm); } } } diff --git a/src/app/views/updatepassword.tmpl.html b/src/app/views/updatepassword.tmpl.html new file mode 100644 index 00000000..b784dfbe --- /dev/null +++ b/src/app/views/updatepassword.tmpl.html @@ -0,0 +1,51 @@ + + + +
+ Your password has been changed. Please provide your new password +
+ +
+ + {{$root.amGlobals.credentials.username}} +
+
+ + +
+
+ {{vm.message}} + + Submit + send + +
+
+
\ No newline at end of file diff --git a/src/assets/css/automint.css b/src/assets/css/automint.css index c6b1d36a..24978463 100644 --- a/src/assets/css/automint.css +++ b/src/assets/css/automint.css @@ -5,6 +5,10 @@ * @version 0.7.0 */ + .am-restart-app .message { + margin-bottom: 4px; + } + .am-restart-app { position: fixed; z-index: 998; diff --git a/src/index.html b/src/index.html index ef55006a..2af251c4 100644 --- a/src/index.html +++ b/src/index.html @@ -58,7 +58,10 @@
- {{busyApp.message}} +
+ {{busyApp.message}} + Restrating App.. +
From 0cddcb6868d6d836cf7b5042c85c119d5394f409 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Wed, 31 Aug 2016 12:54:22 +0530 Subject: [PATCH 80/91] Url change in Change Password Call --- .../tmpl/changepassword.controller.js | 4 ++ src/app/login/login.service.js | 39 ++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/app/components/settings/tmpl/changepassword.controller.js b/src/app/components/settings/tmpl/changepassword.controller.js index 5739b0df..f6f655ad 100644 --- a/src/app/components/settings/tmpl/changepassword.controller.js +++ b/src/app/components/settings/tmpl/changepassword.controller.js @@ -70,15 +70,19 @@ } $rootScope.busyApp.show = true; $rootScope.busyApp.message = 'Changing Password. Please Wait..'; + if ($rootScope.amDbSync) + $rootScope.amDbSync.cancel(); $amLicense.changePassword($rootScope.amGlobals.credentials.username, vm.newPassword).then(success).catch(failure); function success(res) { + $amRoot.syncDb(); $rootScope.busyApp.show = false; $mdDialog.hide(true); } function failure(err) { $rootScope.busyApp.show = false; + $amRoot.syncDb(); if (err.error_code == 1) { doLogin(err.error_message); return; diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 1ddb8839..9ff1e4b1 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -41,22 +41,31 @@ var adminChannels = []; adminChannels.push($rootScope.amGlobals.channel); $http({ - method: 'PUT', - url: amFactory.generateChangePwdUrl($rootScope.amGlobals.credentials.username), - headers: { - 'Content-Type': 'application/json' - }, - data: { - admin_channels: adminChannels, - password: newPassword - }, - timeout: 2000 - }).then(success).catch(failure); + method: 'PUT', + url: amFactory.generateChangePwdUrl(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: $.param({ + name: $rootScope.amGlobals.credentials.username, + oldpass: $rootScope.amGlobals.credentials.password, + newpass: newPassword + }), + timeout: 2000 + }).then(success, failure); return tracker.promise; function success(res) { - $rootScope.amGlobals.credentials.password = newPassword; - amLogin.savePassword(newPassword).then(proceed).catch(doLogin); + if (res.data && res.data.mint_code) { + if (res.data.mint_code == 'PA100') { + $rootScope.amGlobals.credentials.password = newPassword; + amLogin.savePassword(newPassword).then(proceed).catch(doLogin); + return; + } + } + failure({ + mint_error: 300 + }); } function proceed(pres) { @@ -86,6 +95,10 @@ vm.errorCode = 1; vm.errorMessage = 'Automint could not collect license details. Please login again!'; break; + case 300: + vm.errorCode = -1; + vm.errorMessage = 'Could not change password. Please try again!'; + break; } } From dd3e665813b174ece03f0069f56647d422d0f27d Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 1 Sep 2016 12:48:32 +0530 Subject: [PATCH 81/91] Building App for OS X --- pkg/package.json | 22 +++++++++++++++++++++- src/automint_modules/am-preferences.js | 3 ++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/pkg/package.json b/pkg/package.json index 363219a3..b4cfc054 100644 --- a/pkg/package.json +++ b/pkg/package.json @@ -10,14 +10,34 @@ "dist:win32-ia32": "./node_modules/.bin/build --platform win32 --arch ia32", "dist:win32-x64": "./node_modules/.bin/build --platform win32 --arch x64", "dist:win32": "./node_modules/.bin/build --platform win32 --arch all", - "release": "gulp && npm run dist:win32-ia32" + "dist:osx": "./node_modules/.bin/build --platform darwin", + "release": "gulp && npm run dist:win32-ia32", + "releaseosx": "gulp && npm run dist:osx" }, "build": { + "appId": "in.automint.mac", + "catagory": "public.app-category.productivity", "iconUrl": "http://cruzer.io/automint.ico", "win": { "authors": "Automint Pvt. Ltd.", "loadingGif": "build/installer.gif", "remoteReleases": "http://updates.automint.in/releases/win32/ia32/" + }, + "dmg": { + "icon": "build/icon.icns", + "contents": [ + { + "x": 410, + "y": 150, + "type": "link", + "path": "/Applications" + }, + { + "x": 130, + "y": 150, + "type": "file" + } + ] } }, "devDependencies": { diff --git a/src/automint_modules/am-preferences.js b/src/automint_modules/am-preferences.js index a6fcf4b7..9738a859 100644 --- a/src/automint_modules/am-preferences.js +++ b/src/automint_modules/am-preferences.js @@ -13,7 +13,8 @@ var Promise = require('promise'); // named assignments - var PREF_DIR = __dirname + "/../../app.asar.unpacked/"; + // var PREF_DIR = __dirname + "/../../app.asar.unpacked/"; + var PREF_DIR = process.resourcesPath + "/app.asar.unpacked/"; var PREF_FILE = PREF_DIR + 'automint-preferences.json'; var e404 = { success: false, From f72ffc540fad84b0122d7cbbc6a3da089518fdf1 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Thu, 1 Sep 2016 14:34:07 +0530 Subject: [PATCH 82/91] Handled Self Signed Certificate Error --- src/main.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main.js b/src/main.js index abf2c164..88685114 100644 --- a/src/main.js +++ b/src/main.js @@ -17,10 +17,10 @@ // Module for IPC const ipcMain = electron.ipcMain; // Importing Eelctron Squirrel Startup - if(require('electron-squirrel-startup')) return; + if (require('electron-squirrel-startup')) return; // Module to Auto Update app - var autoUpdater = electron.autoUpdater; - var os = require('os'); + var autoUpdater = electron.autoUpdater; + var os = require('os'); // var feedURL = 'http://updates.automint.in/releases/' + (os.platform()) + '/' + (os.arch()); var feedURL = 'http://updates.automint.in/releases/win32/ia32'; // Module to check preferences @@ -35,7 +35,7 @@ autoUpdater.setFeedURL(feedURL); if (process.argv[1] == '--squirrel-firstrun') { - setTimeout(()=> { + setTimeout(() => { autoUpdater.checkForUpdates(); }, 180000) } else { @@ -60,7 +60,7 @@ fs.accessSync(amUserDataPath); app.setPath('userData', amUserDataPath); isUserDataPathExists = true; - } catch(e) { + } catch (e) { isUserDataPathExists = false; } } @@ -87,12 +87,20 @@ } }); + app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { + // Verification logic. + event.preventDefault() + callback(true); + }) + ipcMain.on('am-quit-update', updateAndRestartApp); ipcMain.on('am-do-restart', restartApp); function restartApp(event, args) { - app.relaunch({args: process.argv.slice(1).concat('--relaunch')}); + app.relaunch({ + args: process.argv.slice(1).concat('--relaunch') + }); app.quit(); } @@ -120,7 +128,9 @@ function openCorrectFileUrl(buttonIndex) { if (buttonIndex == 0) { - var newPath = dialog.showOpenDialog({properties: ['openDirectory']}); + var newPath = dialog.showOpenDialog({ + properties: ['openDirectory'] + }); if (newPath) { ammPreferences.storePreference('automint.userDataPath', newPath[0]); restartApp(); From b75510ee3fca3ca8ba31e484d27d53ab18143a9c Mon Sep 17 00:00:00 2001 From: Viral Kacha Date: Thu, 1 Sep 2016 22:45:35 +0530 Subject: [PATCH 83/91] Removed time outs from http requests --- src/app/login/login.service.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 9ff1e4b1..fedbdb7f 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -50,8 +50,7 @@ name: $rootScope.amGlobals.credentials.username, oldpass: $rootScope.amGlobals.credentials.password, newpass: newPassword - }), - timeout: 2000 + }) }).then(success, failure); return tracker.promise; @@ -133,8 +132,7 @@ data: $.param({ name: $rootScope.amGlobals.credentials.username, password: $rootScope.amGlobals.credentials.password - }), - timeout: 2000 + }) }).then(success, failure); } return tracker.promise; From ec6b08cb618d7119c8103c8c4b3f518698cefa57 Mon Sep 17 00:00:00 2001 From: Viral Kacha Date: Fri, 2 Sep 2016 07:19:51 +0530 Subject: [PATCH 84/91] timeout value increased --- src/app/app.service.js | 2 +- src/app/components/settings/settings.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index 33120c9d..5f9157a8 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -161,7 +161,7 @@ $rootScope.checkAutomintValidity = setInterval(checkAutomintValidity, 1000*60*60*24); if (wait) - setTimeout(prepraeTransit, 2000); + setTimeout(prepraeTransit, 60000); else { // handle database migration if any and generate cache docs after the process // no such migrations right now diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index dcd0a51b..d6e57cf3 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -286,7 +286,7 @@ if (res.isSyncableDb) { $amRoot.syncDb(); $rootScope.busyApp.message = 'Syncing'; - setTimeout(syncDone, 2000); + setTimeout(syncDone, 60000); } else $rootScope.busyApp.show = false; } else From 0cf237ac6c59110bf3b39a571c785405d71eae04 Mon Sep 17 00:00:00 2001 From: Viral Kacha Date: Fri, 2 Sep 2016 07:57:20 +0530 Subject: [PATCH 85/91] sync bugs solved --- src/app/app.service.js | 47 +++++++++++++------ .../settings/settings.controller.js | 9 ++-- src/app/login/login.controller.js | 7 ++- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index 5f9157a8..b8cc9c16 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -99,6 +99,32 @@ function onPausedDb(err) { // listen to on pause event\ console.warn('pdbSync pause', err); + + if ($rootScope.isSyncCalledFromSettings == true) { + setTimeout(loadSb, 1000); + $rootScope.isSyncCalledFromSettings = false; + + function loadSb() { + generateCacheDocs(true).then(tr).catch(tr); + + function tr() { + $rootScope.loadSettingsBlock(); + $rootScope.busyApp.show = false; + } + } + } + + if ($rootScope.isSyncCalledManually == true) { + setTimeout(prepraeTransit, 1000); + $rootScope.isSyncCalledManually = false; + + function prepraeTransit() { + $rootScope.isOnChangeMainDbBlocked = false; + // handle database migration if any and generate cache docs after the process + // no such migrations right now + generateCacheDocs(true).then(transitToDashboard).catch(transitToDashboard); + } + } } function onActiveDb() { @@ -149,6 +175,10 @@ } } + function transitToDashboard() { + $state.go('restricted.dashboard'); + } + function dbAfterLogin(wait) { // setup database change listeners @@ -161,24 +191,11 @@ $rootScope.checkAutomintValidity = setInterval(checkAutomintValidity, 1000*60*60*24); if (wait) - setTimeout(prepraeTransit, 60000); + $rootScope.isSyncCalledManually = true; else { // handle database migration if any and generate cache docs after the process // no such migrations right now - generateCacheDocs().then(transit).catch(transit); - } - - function prepraeTransit() { - $rootScope.isOnChangeMainDbBlocked = false; - // handle database migration if any and generate cache docs after the process - // no such migrations right now - generateCacheDocs(true).then(transit).catch(transit); - - - } - - function transit() { - $state.go('restricted.dashboard'); + generateCacheDocs().then(transitToDashboard).catch(transitToDashboard); } function checkAutomintValidity() { diff --git a/src/app/components/settings/settings.controller.js b/src/app/components/settings/settings.controller.js index d6e57cf3..618eea02 100644 --- a/src/app/components/settings/settings.controller.js +++ b/src/app/components/settings/settings.controller.js @@ -146,6 +146,8 @@ vm.cloudSettings.submit = csSubmit; vm.cloudSettings.changeAvailability = csChangeAvailability; vm.cloudSettings.changePassword = csChangePassword; + + $rootScope.loadSettingsBlock = loadBlock; // function maps [END] // default execution steps [BEGIN] @@ -284,18 +286,13 @@ if (res.isLoggedIn) { vm.cloudSettings.isLicensed = (res.isLoggedIn == true); if (res.isSyncableDb) { + $rootScope.isSyncCalledFromSettings = true; $amRoot.syncDb(); $rootScope.busyApp.message = 'Syncing'; - setTimeout(syncDone, 60000); } else $rootScope.busyApp.show = false; } else getLoginState(); - - function syncDone() { - loadBlock(); - $rootScope.busyApp.show = false; - } } function failure(err) { diff --git a/src/app/login/login.controller.js b/src/app/login/login.controller.js index f60f9f99..8c78f815 100644 --- a/src/app/login/login.controller.js +++ b/src/app/login/login.controller.js @@ -104,9 +104,12 @@ if (res.isSyncableDb) { $rootScope.isOnChangeMainDbBlocked = true; $amRoot.syncDb(); + vm.message = "Preparing Automint"; + $amRoot.dbAfterLogin(true); + } else { + vm.message = "Preparing Dashboard.."; + $amRoot.dbAfterLogin(false); } - vm.message = "Preparing Dashboard.."; - $amRoot.dbAfterLogin(true); } else { failure({ message: 'Your license has expired!' From d3e1624963b634fc37083d0dc5d86cabcb5f6dcb Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 2 Sep 2016 09:43:27 +0530 Subject: [PATCH 86/91] Token Directory changes in Automint Modules --- src/automint_modules/googleoauth/google-auth.js | 3 ++- src/automint_modules/print/am-save-html.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/automint_modules/googleoauth/google-auth.js b/src/automint_modules/googleoauth/google-auth.js index 6610da57..fcf4697e 100644 --- a/src/automint_modules/googleoauth/google-auth.js +++ b/src/automint_modules/googleoauth/google-auth.js @@ -20,7 +20,8 @@ // initialize essential params for Google && Gmail API var SCOPE = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send']; - var TOKEN_DIR = __dirname + "/../../../app.asar.unpacked/"; + var TOKEN_DIR = process.resourcesPath + "/app.asar.unpacked/"; + // var TOKEN_DIR = __dirname + "/../../../app.asar.unpacked/"; var TOKEN_PATH = TOKEN_DIR + 'google-cred.json'; // declare callback function and boolean for requesting new token diff --git a/src/automint_modules/print/am-save-html.js b/src/automint_modules/print/am-save-html.js index 22fc2a4a..78a009f5 100644 --- a/src/automint_modules/print/am-save-html.js +++ b/src/automint_modules/print/am-save-html.js @@ -12,7 +12,8 @@ var fs = require('fs'); // constants used for the module (but not exported) - var PREVIEW_DIR = __dirname + "/../../../app.asar.unpacked/"; + var PREVIEW_DIR = process.resourcesPath + "/app.asar.unpacked/"; + // var PREVIEW_DIR = __dirname + "/../../../app.asar.unpacked/"; var PREVIEW_PATH = PREVIEW_DIR + "print-preview.html"; // export relevant function as indevidual module From d45a12b3f141ab36b1fa6e416011530019a86ba1 Mon Sep 17 00:00:00 2001 From: Viral Kacha Date: Fri, 2 Sep 2016 10:43:35 +0530 Subject: [PATCH 87/91] dashboard refresh bug solved --- src/app/components/dashboard/dashboard.controller.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/components/dashboard/dashboard.controller.js b/src/app/components/dashboard/dashboard.controller.js index ec79b994..fbb55a23 100644 --- a/src/app/components/dashboard/dashboard.controller.js +++ b/src/app/components/dashboard/dashboard.controller.js @@ -122,10 +122,7 @@ function reloadData() { $rootScope.busyApp.show = false; - initCurrentTimeSet(); - getFilterMonths(); - processPreferences(); - getCurrencySymbol(); + $state.go($state.current, {}, {reload: true}); } } From 1b2d95deabfea4a441c90795d5ba3e38c7247fb2 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 2 Sep 2016 11:46:02 +0530 Subject: [PATCH 88/91] Handled Activation Code Error --- src/app/login/login.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index fedbdb7f..8dd6b0c8 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -143,7 +143,9 @@ return; } if ((ignoreCodeUsedError != true) && res.data.used) { - tracker.reject('The code is already in use! You cannot use it again!'); + tracker.reject({ + message: 'The code is already in use! You cannot use it again!' + }); return; } var startdate = moment(res.data.starts, 'YYYY-MM-DD').format(); From 8fcd2c17e9b5fc0fac8854b0ee8bfa832d5d621b Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 2 Sep 2016 12:27:14 +0530 Subject: [PATCH 89/91] Cloud Enabled Flag --- src/app/login/login.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/login/login.service.js b/src/app/login/login.service.js index 8dd6b0c8..e1956b0c 100644 --- a/src/app/login/login.service.js +++ b/src/app/login/login.service.js @@ -340,7 +340,7 @@ mintSkipSave: true }); } - if (res.isCloudForceEnabled) + if (res.isCloudForceEnabled != undefined) isCloudForceEnabled = res.isCloudForceEnabled; if (res.username && res.password) { loadCredentials(res.username, res.password); From f756b5d82a340e4c2d62c3b8699ad0aee08b5ba2 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 2 Sep 2016 12:56:56 +0530 Subject: [PATCH 90/91] Customer Lead Problem --- src/app/components/customers/customers-edit.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index c9560cd2..57563bb8 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -567,6 +567,8 @@ vm.serviceQuery.total = vm.services.length; vm.possibleVehicleList = pvl; changeVehicle(undefined); + if (vm.user.type == vm.customerTypeList[0] && (vm.services.length > 0)) + vm.user.type = vm.customerTypeList[1]; $scope.$apply(); function iterateVehicle(vId) { From adcb4b48c22c833ccfe834311e376decf4bc7123 Mon Sep 17 00:00:00 2001 From: Anand Kacha Date: Fri, 2 Sep 2016 13:08:27 +0530 Subject: [PATCH 91/91] Cache problem and Customer name change on syncing --- src/app/app.service.js | 2 +- src/app/components/customers/customers-edit.controller.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/app.service.js b/src/app/app.service.js index b8cc9c16..2297f7f1 100644 --- a/src/app/app.service.js +++ b/src/app/app.service.js @@ -230,7 +230,7 @@ if (IsConfigDoc(change.id)) return; if (change.deleted == true) { - generateCacheDocs(force); + generateCacheDocs(true); return; } var curdoc; diff --git a/src/app/components/customers/customers-edit.controller.js b/src/app/components/customers/customers-edit.controller.js index 57563bb8..542504c6 100644 --- a/src/app/components/customers/customers-edit.controller.js +++ b/src/app/components/customers/customers-edit.controller.js @@ -688,7 +688,6 @@ if (vm.user.name == '') vm.user.name = "Anonymous"; userDbInstance.user.mobile = vm.user.mobile; - userDbInstance.user.name = vm.user.name; userDbInstance.user.email = vm.user.email; userDbInstance.user.address = vm.user.address; userDbInstance.user.type = vm.user.type; @@ -701,6 +700,8 @@ delete userDbInstance._deleted; delete userDbInstance._rev; } + + userDbInstance.user.name = vm.user.name; if (vm.membershipChips != undefined) { var smArray = $.extend([], vm.membershipChips);