From 6f46be2b4bcfdc1d2b70c20dce45e3b9ae09a412 Mon Sep 17 00:00:00 2001 From: Jonathan Niles Date: Mon, 23 May 2016 12:28:07 +0100 Subject: [PATCH] feat(Services): implement PrototypeApiService This commit implements PrototypeApiService to remove boilerplate code from our services and improve code documentation. To demonstrate how the PrototypeApiService works, the FiscalService has been created to inherit directly from it. The CashPayments service also is updated to inherit from the PrototypeApiService. This also increments mocha's versioning since the recent bug fix. Finally, a bug with the bhCurrencySelect has been fixed. Closes #214. --- client/src/js/components/bhCurrencySelect.js | 6 +- client/src/js/services/CashService.js | 149 +++++----------- client/src/js/services/FiscalYearService.js | 22 +++ client/src/js/services/PeriodService.js | 14 ++ client/src/js/services/PrototypeApiService.js | 164 ++++++++++++++++++ client/src/partials/cash/cash.html | 1 - 6 files changed, 247 insertions(+), 109 deletions(-) create mode 100644 client/src/js/services/FiscalYearService.js create mode 100644 client/src/js/services/PeriodService.js create mode 100644 client/src/js/services/PrototypeApiService.js diff --git a/client/src/js/components/bhCurrencySelect.js b/client/src/js/components/bhCurrencySelect.js index b997bcb9fa..99c201a03e 100644 --- a/client/src/js/components/bhCurrencySelect.js +++ b/client/src/js/components/bhCurrencySelect.js @@ -87,7 +87,7 @@ function bhCurrencySelect($scope, Currencies) { $scope.$watchCollection(function () { return $ctrl.disableIds; }, function (array) { - array = array || []; + if (!array) { return; } // loop through the currencies, disabling the currencies with ids in the // disabledIds array. @@ -97,8 +97,6 @@ function bhCurrencySelect($scope, Currencies) { // if the two array lengths are equal, it means every currency is disabled $ctrl.allDisabled = ($ctrl.currencies.length === array.length); - if ($ctrl.allDisabled) { - $ctrl.form.$setValidity('currencies', false); - } + $ctrl.form.$setValidity('currencies', $ctrl.allDisabled); }); } diff --git a/client/src/js/services/CashService.js b/client/src/js/services/CashService.js index eae9e444b6..d15a07be04 100644 --- a/client/src/js/services/CashService.js +++ b/client/src/js/services/CashService.js @@ -1,49 +1,30 @@ -/** - * CashService - * - * This service interacts with the server-side /cash API. - * - * @module services/CashService - */ - angular.module('bhima.services') .service('CashService', CashService); -CashService.$inject = [ '$http', 'util', 'ExchangeRateService', 'uuid', 'SessionService' ]; +CashService.$inject = [ + 'PrototypeApiService', 'ExchangeRateService', 'SessionService' +]; /** - * A service to interact with the server-side /cash API. + * @class CashService * - * @constructor CashService + * @description + * A service to interact with the server-side /cash API. */ -function CashService($http, util, Exchange, uuid, sessionService ) { +function CashService(PrototypeApiService, Exchange, Session) { var service = this; - var baseUrl = '/cash/'; - service.read = read; + // inherit prototype API methods + angular.extend(service, PrototypeApiService); + + // bind the base url + service.url = '/cash/'; + + // custom methods service.create = create; - service.update = update; - service.delete = remove; service.reference = reference; service.getTransferRecord = getTransferRecord; - /** - * Fetchs cash payments from the server. If an uuid is specified, will read a - * single JSON out of the service, otherwise, fetches every cash payment in the - * database. - * - * @method read - * @param {string} uuid (optional) - a cash payment UUID - * @param {object} options - parameters to be passed as HTTP query strings - * @returns {object|array} payments One or more cash payments. - */ - function read(uuid, options) { - var target = baseUrl.concat(uuid || ''); - - return $http.get(target, options) - .then(util.unwrapHttpResponse); - } - /** * Cash Payments can be made to multiple invoices. This function loops * though the invoices in selected order, allocating the global amount to each @@ -83,9 +64,11 @@ function CashService($http, util, Exchange, uuid, sessionService ) { } /** + * @method create + * + * @description * Creates a cash payment from a JSON passed from a form. * - * @method create * @param {object} data A JSON object containing the cash payment record defn * @returns {object} payment A promise resolved with the database uuid. */ @@ -109,101 +92,59 @@ function CashService($http, util, Exchange, uuid, sessionService ) { // remove data.invoices property before submission to the server delete data.invoices; - return $http.post(baseUrl, { payment : data }) - .then(util.unwrapHttpResponse); - } - - /** - * Fetchs cash payments from the server. If an id is specified, will read a single - * JSON out of the service, otherwise, fetches every cash payment in the database. - * - * @method update - * @param {string} uuid A cash payment UUID - * @returns {object} payments A promise containing the entire cash payment record - */ - function update(uuid, data) { - var target = baseUrl.concat(uuid); - return $http.put(target, data) - .then(util.unwrapHttpResponse); - } - - /** - * Deletes cash payments from the database based on the id passed in. - * - * @method delete - * @param {string} uuid A cash payment UUID - * @returns {promise} promise - a resolved or rejected empty promise - */ - function remove(uuid) { - var target = baseUrl.concat(uuid); - - // Technically, we are not returning any body, so unwrappHttpResponse does - // not do anything. However, to keep uniformity with the API, I've included - // it. - return $http.delete(target) - .then(util.unwrapHttpResponse); + // call the prototype create method with the formatted data + return PrototypeApiService.create.call(service, { payment : data }); } /** * Searches for a cash payment by its reference. * * @method reference - * @param {string} reference - * @returns {promise} promise - a resolved or rejected promise with the + * @param {String} reference + * @returns {Promise} promise - a resolved or rejected promise with the * result sent from the server. */ function reference(ref) { - var target = baseUrl + 'references/' + ref; - - return $http.get(target) - .then(util.unwrapHttpResponse); + var target = service.url.concat('references/', ref); + return this.$http.get(target) + .then(this.util.unwrapHttpResponse); } /** - * This methode is responsible to create a voucher object and it back - **/ - function getTransferRecord (cashAccountCurrency, amount, currency_id){ + * This method is responsible to create a voucher object and it back + */ + function getTransferRecord(cashAccountCurrency, amount, currencyId) { /** * The date field is set at the server side * @todo the date in timestamp type in the database */ var voucher = { - uuid : uuid(), - project_id : sessionService.project.id, - currency_id : currency_id, + project_id : Session.project.id, + currency_id : currencyId, amount : amount, description : generateTransferDescription(), - user_id : sessionService.user.id, - items : [] - }; - - var cashVoucherLine = { - uuid : uuid (), - account_id : cashAccountCurrency.account_id, - debit : 0, - credit : amount, - voucher_uuid : voucher.uuid + user_id : Session.user.id, + + // two lines (debit and credit) to be recorded in the database + items : [{ + account_id : cashAccountCurrency.account_id, + debit : 0, + credit : amount, + }, { + account_id : cashAccountCurrency.transfer_account_id, + debit : amount, + credit : 0, + }] }; - var transferVoucherLine = { - uuid : uuid (), - account_id : cashAccountCurrency.transfer_account_id, - debit : amount, - credit : 0, - voucher_uuid : voucher.uuid - }; - - voucher.items.push(cashVoucherLine); - voucher.items.push(transferVoucherLine); - return { voucher : voucher }; } /** - * This methode is responsible to generate a description for the transfer operation - * @private - **/ + * This method is responsible to generate a description for the transfer operation. + * @private + */ function generateTransferDescription (){ - return ['Transfer voucher', new Date().toISOString().slice(0, 10), sessionService.user.id].join('/'); + return 'Transfer Voucher/'.concat(new Date().toISOString().slice(0, 10), '/', Session.user.id); } } diff --git a/client/src/js/services/FiscalYearService.js b/client/src/js/services/FiscalYearService.js new file mode 100644 index 0000000000..96b138230b --- /dev/null +++ b/client/src/js/services/FiscalYearService.js @@ -0,0 +1,22 @@ +angular.module('bhima.services') +.service('FiscalService', FiscalService); + +FiscalService.$inject = ['PrototypeApiService']; + +/** + * Fiscal Service + * + * This service is responsible for loading the Fiscal Years and Periods, as well + * as providing metadata like period totals, opening balances and such. + */ +function FiscalService(PrototypeApiService) { + var service = this; + + // inherit from the PrototypeApiService + angular.extend(service, PrototypeApiService); + + // the service URL + service.url = '/fiscal/'; + + return service; +} diff --git a/client/src/js/services/PeriodService.js b/client/src/js/services/PeriodService.js new file mode 100644 index 0000000000..bcde448c60 --- /dev/null +++ b/client/src/js/services/PeriodService.js @@ -0,0 +1,14 @@ +angular.module('bhima.services') +.service('PeriodService', PeriodService); + +PeriodService.$inject = ['PrototypeApiService']; + +function PeriodService(PrototypeApiService) { + var service = this; + + // inherit methods from the + angular.extend(service, PrototypeApiService); + + + return service; +} diff --git a/client/src/js/services/PrototypeApiService.js b/client/src/js/services/PrototypeApiService.js new file mode 100644 index 0000000000..461d67b5da --- /dev/null +++ b/client/src/js/services/PrototypeApiService.js @@ -0,0 +1,164 @@ +angular.module('bhima.services') +.service('PrototypeApiService', PrototypeApiService); + +PrototypeApiService.$inject = ['$http', 'util']; + +/** + * @class PrototypeApiService + * + * @description + * This service is the parent/prototype of all API services throughout the + * application. It defines the basic methods to be implemented and parameters + * that are required for each. Full CRUD is implemented in this service, + * extending from a base url. + * + * Child services are expected to use angular.extend() to inherit the basic + * methods and properties from this service. + * + * @requires $http + * @requires util + */ +function PrototypeApiService($http, util) { + + /** bind the required $http and util services */ + this.$http = $http; + this.$util = util; + + // basic API methods + this.create = create; + this.read = read; + this.update = update; + this.delete = remove; + + /** + * @method read + * + * @description + * Sends an HTTP GET request to the url "/route" or "route/:id". If an id is + * provided, the id is appended to the base url before sending the request. + * Otherwise, the request is made against the base url. + * + * Optional parameters may be provided as the second parameter to be passed as + * query string parameters to $http. + * + * @param {Number|String|Null} id - the optional identifier of the URL route. + * @param {Object|Null} params - optional parameters to be passed to $http + * @returns {Promise} - the promise with the requested data + * + * @example + * // GETting data from the base /route/ + * service.read().then(function (data) { + * // data is an array of values + * }) + * + * // GETting data from the /route/:id + * service.read(id).then(function (data) { + * // data is typically an object here + * }); + * + * // GETting data with query string params + * // /route?limit=10&detailed=1 + * service.read(null, { limit : 10, detailed : 1 }) + * .then(function (data) { + * // data is typically an array here + * }); + */ + function read(id, parameters) { + + // default to empty object for paramters + parameters = parameters || {}; + + // append the id to the target + var target = this.url.concat(id || ''); + + // send the GET request + return this.$http.get(target, { params : parameters }) + .then(util.unwrapHttpResponse); + } + + /** + * @method update + * + * @description + * Sends an HTTP PUT request to the url `/route/:id` with properties to update in + * the database. The method removes any identifiers (id, uuid) if they exist + * on the object to avoid changing references in the database. + * + * @param {Number|String|Null} id - the optional identifier of the URL route. + * @param {Object|Null} data - the changed data to be updated in the database + * @returns {Promise} - the promise with the full changed object + * + * @example + * // PUT data to the url "/route/1" + * service.update(1, { name : "Hope" }).then(function (data) { + * // data is a JSON with the full record's properties + * }); + */ + function update(id, data) { + + // remove identifers before update command + delete data.id; + delete data.uuid; + + // append the id to the base url + var target = this.url.concat(id); + + // send the PUT request + return this.$http.put(target, data) + .then(util.unwrapHttpResponse); + } + + /** + * @method create + * + * @description + * Sends an HTTP POST request to the url `/route` with the record properties + * in the HTTP body. + * + * @param {Object|Null} data - the record data to be create in the database + * @returns {Promise} - the promise with the identifier from the database + * resolving to the created record identifier + * + * @example + * // POST data to the url "/route" + * service.create({ text : "Hello World!" }).then(function (data) { + * // data an object containing the identifier. Usually "id" or "uuid" + * }); + */ + function create(data) { + + // the target is the base URL + var target = this.url; + + // send the POST request + return this.$http.post(target, data) + .then(util.unwrapHttpResponse); + } + + /** + * @method delete + * + * @description + * Sends an HTTP DELETE request to the url "/route/:id" to delete an object + * from the database. The expected response is a `204 No Content` HTTP status + * code. + * + * @param {Number|String|Null} id - the identifier of the URL route. + * @returns {Promise} - the promise with the identifier from the database + * + * @example + * // POST data to the url "/route" + * service.create({ text : "Hello World!" }).then(function (data) { + * // data an object containing the identifier. Usually "id" or "uuid" + * }); + */ + function remove(id) { + + // append the id to the base url + var target = this.url.concat(id); + + // send the DELETE request + return this.$http.delete(target) + .then(util.unwrapHttpResponse); + } +} diff --git a/client/src/partials/cash/cash.html b/client/src/partials/cash/cash.html index 04468f867f..995e555bed 100644 --- a/client/src/partials/cash/cash.html +++ b/client/src/partials/cash/cash.html @@ -74,7 +74,6 @@ -