Guest User

Untitled

a guest
Jul 8th, 2018
318
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 485.24 KB | None | 0 0
  1. (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  2. (function (factory) {
  3. if (typeof define === 'function' && define.amd) {
  4. define(['jquery', 'moment'], factory);
  5. } else {
  6. factory($, moment);
  7. }
  8. }(function (jquery, moment) {/**
  9. * Provides the classes needed to communicate with the CHECKROOM API
  10. * @module api
  11. * @namespace api
  12. * @copyright CHECKROOM NV 2015
  13. */
  14. var api, settings, common_code, common_order, common_reservation, common_item, common_conflicts, common_keyValues, common_image, common_attachment, common_inflection, common_validation, common_utils, common_slimdown, common_kit, common_contact, common_user, common_template, common_clientStorage, common_document, common_transaction, common_queue, common_pubsub, common, colorLabel, document, Availability, Attachment, comment, attachment, field, Base, Category, Comment, Conflict, base, user, helper, Contact, DateHelper, Document, Group, Item, Kit, Location, location, dateHelper, transaction, conflict, Order, PermissionHandler, Reservation, Template, Transaction, User, UserSync, WebHook, OrderTransfer, ColorLabel, Field, core;
  15. api = function ($, moment) {
  16. var MAX_QUERYSTRING_LENGTH = 2048;
  17. //TODO change this
  18. //system.log fallback
  19. var system = {
  20. log: function () {
  21. }
  22. };
  23. // Disable caching AJAX requests in IE
  24. // http://stackoverflow.com/questions/5502002/jquery-ajax-producing-304-responses-when-it-shouldnt
  25. $.ajaxSetup({ cache: false });
  26. var api = {};
  27. //*************
  28. // ApiErrors
  29. //*************
  30. // Network
  31. api.NetworkNotConnected = function (msg, opt) {
  32. this.code = 999;
  33. this.message = msg || 'Connection interrupted';
  34. this.opt = opt;
  35. };
  36. api.NetworkNotConnected.prototype = new Error();
  37. api.NetworkTimeout = function (msg, opt) {
  38. this.code = 408;
  39. this.message = msg || 'Could not reach the server in time';
  40. this.opt = opt;
  41. };
  42. api.NetworkTimeout.prototype = new Error();
  43. // Api
  44. api.ApiError = function (msg, opt) {
  45. this.code = 500;
  46. this.message = msg || 'Something went wrong on the server';
  47. this.opt = opt;
  48. };
  49. api.ApiError.prototype = new Error();
  50. api.ApiNotFound = function (msg, opt) {
  51. this.code = 404;
  52. this.message = msg || 'Could not find what you\'re looking for';
  53. this.opt = opt;
  54. };
  55. api.ApiNotFound.prototype = new Error();
  56. api.ApiBadRequest = function (msg, opt) {
  57. this.code = 400;
  58. this.message = msg || 'The server did not understand your request';
  59. this.opt = opt;
  60. };
  61. api.ApiBadRequest.prototype = new Error();
  62. api.ApiUnauthorized = function (msg, opt) {
  63. this.code = 401;
  64. this.message = msg || 'Your session has expired';
  65. this.opt = opt;
  66. };
  67. api.ApiUnauthorized.prototype = new Error();
  68. api.ApiForbidden = function (msg, opt) {
  69. this.code = 403;
  70. this.message = msg || 'You don\'t have sufficient rights';
  71. this.opt = opt;
  72. };
  73. api.ApiForbidden.prototype = new Error();
  74. api.ApiUnprocessableEntity = function (msg, opt) {
  75. this.code = 422;
  76. this.message = msg || 'Some data is invalid';
  77. this.opt = opt;
  78. };
  79. api.ApiUnprocessableEntity.prototype = new Error();
  80. api.ApiSubscriptionLimit = function (msg, opt) {
  81. this.code = 422;
  82. this.message = msg || 'You have reached your subscription limit';
  83. this.opt = opt;
  84. };
  85. api.ApiSubscriptionLimit.prototype = new Error();
  86. api.ApiPaymentRequired = function (msg, opt) {
  87. this.code = 402;
  88. this.message = msg || 'Your subscription has expired';
  89. this.opt = opt;
  90. };
  91. api.ApiPaymentRequired.prototype = new Error();
  92. api.ApiServerCapicity = function (msg, opt) {
  93. this.code = 503;
  94. this.message = msg || 'Back-end server is at capacity';
  95. this.opt = opt;
  96. };
  97. api.ApiServerCapicity.prototype = new Error();
  98. //*************
  99. // ApiAjax
  100. //*************
  101. /**
  102. * The ajax communication object which makes the request to the API
  103. * @name ApiAjax
  104. * @param {object} spec
  105. * @constructor
  106. * @memberof api
  107. */
  108. api.ApiAjax = function (spec) {
  109. spec = spec || {};
  110. this.timeOut = spec.timeOut || 10000;
  111. this.responseInTz = true;
  112. };
  113. api.ApiAjax.prototype.get = function (url, timeOut) {
  114. system.log('ApiAjax: get ' + url);
  115. return this._getAjax(url, timeOut);
  116. };
  117. api.ApiAjax.prototype.post = function (url, data, timeOut) {
  118. system.log('ApiAjax: post ' + url);
  119. return this._postAjax(url, data, timeOut);
  120. };
  121. // Implementation
  122. // ----
  123. api.ApiAjax.prototype._handleAjaxSuccess = function (dfd, data, opt) {
  124. if (this.responseInTz) {
  125. data = this._fixDates(data);
  126. }
  127. return dfd.resolve(data);
  128. };
  129. api.ApiAjax.prototype._handleAjaxError = function (dfd, x, t, m, opt) {
  130. // ajax call was aborted
  131. if (t == 'abort')
  132. return;
  133. var msg = '';
  134. if (m === 'timeout') {
  135. dfd.reject(new api.NetworkTimeout(msg, opt));
  136. } else {
  137. if (x) {
  138. if (x.statusText && x.statusText.indexOf('Notify user:') > -1) {
  139. msg = x.statusText.slice(x.statusText.indexOf('Notify user:') + 13);
  140. }
  141. if (x.status == 422 && x.responseText && x.responseText.match(/HTTPError: \(.+\)/g).length > 0) {
  142. opt = { detail: x.responseText.match(/HTTPError: \(.+\)/g)[0] };
  143. }
  144. }
  145. switch (x.status) {
  146. case 400:
  147. dfd.reject(new api.ApiBadRequest(msg, opt));
  148. break;
  149. case 401:
  150. dfd.reject(new api.ApiUnauthorized(msg, opt));
  151. break;
  152. case 402:
  153. dfd.reject(new api.ApiPaymentRequired(msg, opt));
  154. break;
  155. case 403:
  156. dfd.reject(new api.ApiForbidden(msg, opt));
  157. break;
  158. case 404:
  159. dfd.reject(new api.ApiNotFound(msg, opt));
  160. break;
  161. case 408:
  162. dfd.reject(new api.NetworkTimeout(msg, opt));
  163. break;
  164. case 422:
  165. // 422 Notify user: Cannot create item, max limit 50 items reached
  166. if (msg && msg.indexOf('limit') >= 0 && msg.indexOf('reach') >= 0) {
  167. dfd.reject(new api.ApiSubscriptionLimit(msg, opt));
  168. } else {
  169. dfd.reject(new api.ApiUnprocessableEntity(msg, opt));
  170. }
  171. break;
  172. case 503:
  173. dfd.reject(new api.ApiServerCapicity(msg, opt));
  174. break;
  175. case 500:
  176. default:
  177. dfd.reject(new api.ApiError(msg, opt));
  178. break;
  179. }
  180. }
  181. };
  182. api.ApiAjax.prototype._postAjax = function (url, data, timeOut, opt) {
  183. var dfd = $.Deferred();
  184. var that = this;
  185. var xhr = $.ajax({
  186. type: 'POST',
  187. url: url,
  188. data: JSON.stringify(this._prepareDict(data)),
  189. contentType: 'application/json; charset=utf-8',
  190. timeout: timeOut || this.timeOut,
  191. success: function (data) {
  192. return that._handleAjaxSuccess(dfd, data, opt);
  193. },
  194. error: function (x, t, m) {
  195. return that._handleAjaxError(dfd, x, t, m, opt);
  196. }
  197. });
  198. // Extend promise with abort method
  199. // to abort xhr request if needed
  200. // http://stackoverflow.com/questions/21766428/chained-jquery-promises-with-abort
  201. var promise = dfd.promise();
  202. promise.abort = function () {
  203. xhr.abort();
  204. };
  205. return promise;
  206. };
  207. api.ApiAjax.prototype._getAjax = function (url, timeOut, opt) {
  208. var dfd = $.Deferred();
  209. var that = this;
  210. var xhr = $.ajax({
  211. url: url,
  212. timeout: timeOut || this.timeOut,
  213. success: function (data) {
  214. return that._handleAjaxSuccess(dfd, data, opt);
  215. },
  216. error: function (x, t, m) {
  217. return that._handleAjaxError(dfd, x, t, m, opt);
  218. }
  219. });
  220. // Extend promise with abort method
  221. // to abort xhr request if needed
  222. // http://stackoverflow.com/questions/21766428/chained-jquery-promises-with-abort
  223. var promise = dfd.promise();
  224. promise.abort = function () {
  225. xhr.abort();
  226. };
  227. return promise;
  228. };
  229. api.ApiAjax.prototype._prepareDict = function (data) {
  230. // Makes sure all values from the dict are serializable and understandable for json
  231. if (!data) {
  232. return {};
  233. }
  234. $.each(data, function (key, value) {
  235. if (moment.isMoment(value)) {
  236. data[key] = value.toJSONDate();
  237. }
  238. });
  239. return data;
  240. };
  241. /**
  242. * Turns all strings that look like datetimes into moment objects recursively
  243. *
  244. * @name DateHelper#fixDates
  245. * @method
  246. * @private
  247. *
  248. * @param data
  249. * @returns {*}
  250. */
  251. api.ApiAjax.prototype._fixDates = function (data) {
  252. if (typeof data == 'string' || data instanceof String) {
  253. // "2014-04-03T12:15:00+00:00" (length 25)
  254. // "2014-04-03T09:32:43.841000+00:00" (length 32)
  255. if (data.endsWith('+00:00')) {
  256. var len = data.length;
  257. if (len == 25) {
  258. return moment(data.substring(0, len - 6));
  259. } else if (len == 32) {
  260. return moment(data.substring(0, len - 6).split('.')[0]);
  261. }
  262. }
  263. } else if (data instanceof Object || $.isArray(data)) {
  264. var that = this;
  265. $.each(data, function (k, v) {
  266. data[k] = that._fixDates(v);
  267. });
  268. }
  269. return data;
  270. };
  271. //*************
  272. // ApiUser
  273. //*************
  274. /**
  275. * @name ApiUser
  276. * @param {object} spec
  277. * @param {string} spec.userId - the users primary key
  278. * @param {string} spec.userToken - the users token
  279. * @param {string} spec.tokenType - the token type (empty for now)
  280. * @constructor
  281. * @memberof api
  282. */
  283. api.ApiUser = function (spec) {
  284. spec = spec || {};
  285. this.userId = spec.userId || '';
  286. this.userToken = spec.userToken || '';
  287. this.tokenType = spec.tokenType || '';
  288. };
  289. api.ApiUser.prototype.fromStorage = function () {
  290. this.userId = window.localStorage.getItem('userId') || '';
  291. this.userToken = window.localStorage.getItem('userToken') || '';
  292. this.tokenType = window.localStorage.getItem('tokenType') || '';
  293. };
  294. api.ApiUser.prototype.toStorage = function () {
  295. window.localStorage.setItem('userId', this.userId);
  296. window.localStorage.setItem('userToken', this.userToken);
  297. window.localStorage.setItem('tokenType', this.tokenType);
  298. };
  299. api.ApiUser.prototype.removeFromStorage = function () {
  300. window.localStorage.removeItem('userId');
  301. window.localStorage.removeItem('userToken');
  302. window.localStorage.removeItem('tokenType');
  303. };
  304. api.ApiUser.prototype.clearToken = function () {
  305. window.localStorage.setItem('userToken', null);
  306. window.localStorage.setItem('tokenType', null);
  307. };
  308. api.ApiUser.prototype.isValid = function () {
  309. system.log('ApiUser: isValid');
  310. return this.userId != null && this.userId.length > 0 && (this.userToken != null && this.userToken.length > 0);
  311. };
  312. api.ApiUser.prototype._reset = function () {
  313. this.userId = '';
  314. this.userToken = '';
  315. this.tokenType = '';
  316. };
  317. //*************
  318. // ApiAuth
  319. //*************
  320. api.ApiAuth = function (spec) {
  321. spec = spec || {};
  322. this.urlAuth = spec.urlAuth || '';
  323. this.ajax = spec.ajax;
  324. this.version = spec.version;
  325. this.platform = spec.platform;
  326. this.device = spec.device;
  327. this.allowAccountOwner = spec.allowAccountOwner !== undefined ? spec.allowAccountOwner : true;
  328. };
  329. api.ApiAuth.prototype.authenticate = function (userId, password) {
  330. system.log('ApiAuth: authenticate ' + userId);
  331. var that = this;
  332. var params = {
  333. user: userId,
  334. password: password,
  335. _v: this.version
  336. };
  337. if (this.platform) {
  338. params.platform = this.platform;
  339. }
  340. if (this.device) {
  341. params.device = this.device;
  342. }
  343. var url = this.urlAuth + '?' + $.param(params);
  344. var dfd = $.Deferred();
  345. this.ajax.get(url, 30000).done(function (resp) {
  346. // Check if login is ok AND if login is ok but account is expired, check if we allow login or not (allowAccountOwner)
  347. //
  348. // REMARK
  349. // - web app allows owners to still login on expired/cancelled account
  350. // - mobile doesn't allow expired logins also not for owners
  351. if (resp.status == 'OK' && ([
  352. 'expired',
  353. 'cancelled_expired',
  354. 'archived'
  355. ].indexOf(resp.subscription) != -1 ? that.allowAccountOwner : true)) {
  356. dfd.resolve(resp.data);
  357. } else {
  358. dfd.reject(resp);
  359. }
  360. }).fail(function (err) {
  361. dfd.reject(err);
  362. });
  363. return dfd.promise();
  364. };
  365. // Deprecated ApiAuthV2, use ApiAuth
  366. api.ApiAuthV2 = api.ApiAuth;
  367. //*************
  368. // ApiAnonymous
  369. // Communicates with the API without having token authentication
  370. //*************
  371. /**
  372. * @name ApiAnonymous
  373. * @param {object} spec
  374. * @param {ApiAjax} spec.ajax
  375. * @param {string} spec.urlApi
  376. * @constructor
  377. * @memberof api
  378. */
  379. api.ApiAnonymous = function (spec) {
  380. spec = spec || {};
  381. this.ajax = spec.ajax;
  382. this.urlApi = spec.urlApi || '';
  383. this.version = spec.version;
  384. };
  385. /**
  386. * Makes a call to the API which doesn't require a token
  387. * @method
  388. * @name ApiAnonymous#call
  389. * @param method
  390. * @param params
  391. * @param timeOut
  392. * @param opt
  393. * @returns {*}
  394. */
  395. api.ApiAnonymous.prototype.call = function (method, params, timeOut, opt) {
  396. system.log('ApiAnonymous: call ' + method);
  397. if (this.version) {
  398. params = params || {};
  399. params['_v'] = this.version;
  400. }
  401. var url = this.urlApi + '/' + method + '?' + $.param(this.ajax._prepareDict(params));
  402. return this.ajax.get(url, timeOut, opt);
  403. };
  404. /**
  405. * Makes a long call (timeout 60s) to the API which doesn't require a token
  406. * @method
  407. * @name ApiAnonymous#longCall
  408. * @param method
  409. * @param params
  410. * @param opt
  411. * @returns {*}
  412. */
  413. api.ApiAnonymous.prototype.longCall = function (method, params, opt) {
  414. system.log('ApiAnonymous: longCall ' + method);
  415. return this.call(method, params, 60000, opt);
  416. };
  417. //*************
  418. // ApiDataSource
  419. // Communicates with the API using an ApiUser
  420. //*************
  421. /**
  422. * @name ApiDataSource
  423. * @param {object} spec
  424. * @param {string} spec.collection - the collection this datasource uses, e.g. "items"
  425. * @param {string} spec.urlApi - the api url to use
  426. * @param {ApiUser} spec.user - the user auth object
  427. * @param {ApiAjax} spec.ajax - the ajax api object to use
  428. * @constructor
  429. * @memberof api
  430. */
  431. api.ApiDataSource = function (spec) {
  432. spec = spec || {};
  433. this.collection = spec.collection || '';
  434. this.urlApi = spec.urlApi || '';
  435. this.user = spec.user;
  436. this.ajax = spec.ajax;
  437. this.version = spec.version;
  438. };
  439. /**
  440. * Checks if a certain document exists
  441. * @method
  442. * @name ApiDataSource#exists
  443. * @param pk
  444. * @param fields
  445. * @returns {*}
  446. */
  447. api.ApiDataSource.prototype.exists = function (pk, fields) {
  448. system.log('ApiDataSource: ' + this.collection + ': exists ' + pk);
  449. var cmd = 'exists';
  450. var dfd = $.Deferred();
  451. var that = this;
  452. // We're actually doing a API get
  453. // and resolve to an object,
  454. // so we also pass the fields
  455. var url = this.getBaseUrl() + pk;
  456. var p = this.getParams(fields);
  457. if (!$.isEmptyObject(p)) {
  458. url += '?' + this.getParams(p);
  459. }
  460. this._ajaxGet(cmd, url).done(function (data) {
  461. dfd.resolve(data);
  462. }).fail(function (error) {
  463. if (error instanceof api.ApiNotFound) {
  464. dfd.resolve(null);
  465. } else {
  466. dfd.reject(error);
  467. }
  468. });
  469. return dfd.promise();
  470. };
  471. /**
  472. * Gets a certain document by its primary key
  473. * @method
  474. * @name ApiDataSource#get
  475. * @param pk
  476. * @param fields
  477. * @returns {promise}
  478. */
  479. api.ApiDataSource.prototype.get = function (pk, fields) {
  480. system.log('ApiDataSource: ' + this.collection + ': get ' + pk);
  481. var cmd = 'get';
  482. var url = this.getBaseUrl() + pk;
  483. var p = this.getParamsDict(fields);
  484. if (!$.isEmptyObject(p)) {
  485. url += '?' + this.getParams(p);
  486. }
  487. return this._ajaxGet(cmd, url);
  488. };
  489. /**
  490. * Gets a certain document by its primary key, but returns null if not found
  491. * instead of a rejected promise
  492. * @method
  493. * @name ApiDataSource#getIgnore404
  494. * @param pk
  495. * @param fields
  496. * @returns {promise}
  497. */
  498. api.ApiDataSource.prototype.getIgnore404 = function (pk, fields) {
  499. system.log('ApiDataSource: ' + this.collection + ': getIgnore404 ' + pk);
  500. var that = this;
  501. var dfd = $.Deferred();
  502. this.get(pk, fields).done(function (data) {
  503. dfd.resolve(data);
  504. }).fail(function (err) {
  505. if (err instanceof api.ApiNotFound) {
  506. dfd.resolve(null);
  507. } else {
  508. dfd.reject(err);
  509. }
  510. });
  511. return dfd.promise();
  512. };
  513. /**
  514. * Get multiple document by primary keys in a single query
  515. * @method
  516. * @name ApiDataSource#getMultiple
  517. * @param {array} pks
  518. * @param fields
  519. * @returns {promise}
  520. */
  521. api.ApiDataSource.prototype.getMultiple = function (pks, fields) {
  522. system.log('ApiDataSource: ' + this.collection + ': getMultiple ' + pks);
  523. var cmd = 'getMultiple';
  524. var url = this.getBaseUrl() + pks.join(',') + ',';
  525. var p = this.getParamsDict(fields);
  526. if (!$.isEmptyObject(p)) {
  527. url += '?' + this.getParams(p);
  528. }
  529. return this._ajaxGet(cmd, url);
  530. };
  531. /**
  532. * Deletes a document by its primary key
  533. * @method
  534. * @name ApiDataSource#delete
  535. * @param pk
  536. * @returns {promise}
  537. */
  538. api.ApiDataSource.prototype.delete = function (pk) {
  539. system.log('ApiDataSource: ' + this.collection + ': delete ' + pk);
  540. var cmd = 'delete';
  541. var url = this.getBaseUrl() + pk + '/delete';
  542. return this._ajaxGet(cmd, url);
  543. };
  544. /**
  545. * Deletes documents by their primary key
  546. * @method
  547. * @name ApiDataSource#deleteMultiple
  548. * @param pks
  549. * @returns {promise}
  550. */
  551. api.ApiDataSource.prototype.deleteMultiple = function (pks, usePost) {
  552. system.log('ApiDataSource: ' + this.collection + ': deleteMultiple ' + pks);
  553. var cmd = 'deleteMultiple';
  554. var url = this.getBaseUrl() + 'delete';
  555. var p = { pk: pks };
  556. var geturl = url + '?' + this.getParams(p);
  557. if (usePost || geturl.length >= MAX_QUERYSTRING_LENGTH) {
  558. return this._ajaxPost(cmd, url, p);
  559. } else {
  560. return this._ajaxGet(cmd, geturl);
  561. }
  562. };
  563. /**
  564. * Updates a document by its primary key and a params objects
  565. * @method
  566. * @name ApiDataSource#update
  567. * @param pk
  568. * @param params
  569. * @param fields
  570. * @param timeOut
  571. * @param usePost
  572. * @returns {promise}
  573. */
  574. api.ApiDataSource.prototype.update = function (pk, params, fields, timeOut, usePost) {
  575. system.log('ApiDataSource: ' + this.collection + ': update ' + pk);
  576. var cmd = 'update';
  577. var url = this.getBaseUrl() + pk + '/update';
  578. var p = $.extend({}, params);
  579. if (fields != null && fields.length > 0) {
  580. p['_fields'] = $.isArray(fields) ? fields.join(',') : fields;
  581. }
  582. var geturl = url + '?' + this.getParams(p);
  583. if (usePost || geturl.length >= MAX_QUERYSTRING_LENGTH) {
  584. return this._ajaxPost(cmd, url, p, timeOut);
  585. } else {
  586. return this._ajaxGet(cmd, geturl, timeOut);
  587. }
  588. };
  589. /**
  590. * Creates a document with some data in an object
  591. * @method
  592. * @name ApiDataSource#create
  593. * @param params
  594. * @param fields
  595. * @param timeOut
  596. * @param usePost
  597. * @returns {promise}
  598. */
  599. api.ApiDataSource.prototype.create = function (params, fields, timeOut, usePost) {
  600. system.log('ApiDataSource: ' + this.collection + ': create');
  601. var cmd = 'create';
  602. var url = this.getBaseUrl() + 'create';
  603. var p = $.extend({}, params);
  604. if (fields != null && fields.length > 0) {
  605. p['_fields'] = $.isArray(fields) ? fields.join(',') : fields;
  606. }
  607. var geturl = url + '?' + this.getParams(p);
  608. if (usePost || geturl.length >= MAX_QUERYSTRING_LENGTH) {
  609. return this._ajaxPost(cmd, url, p, timeOut);
  610. } else {
  611. return this._ajaxGet(cmd, geturl, timeOut);
  612. }
  613. };
  614. /**
  615. * Creates multiple objects in one go
  616. * @method
  617. * @name ApiDataSource#createMultiple
  618. * @param objects
  619. * @param fields
  620. * @returns {promise}
  621. */
  622. api.ApiDataSource.prototype.createMultiple = function (objects, fields) {
  623. system.log('ApiDataSource: ' + this.collection + ': createMultiple (' + objects.length + ')');
  624. var dfd = $.Deferred();
  625. var that = this;
  626. var todoObjs = objects.slice(0);
  627. var doneIds = [];
  628. // Trigger the creates sequentially
  629. var createRecurse = function (todoObjs) {
  630. if (todoObjs.length > 0) {
  631. var obj = todoObjs.pop();
  632. that.create(obj, fields).done(function (resp) {
  633. doneIds.push(resp._id);
  634. return createRecurse(todoObjs);
  635. }).fail(function (error) {
  636. dfd.reject(error);
  637. });
  638. } else {
  639. dfd.resolve(doneIds);
  640. }
  641. };
  642. createRecurse(todoObjs);
  643. return dfd.promise();
  644. };
  645. /**
  646. * Get a list of objects from the collection
  647. * @method
  648. * @name ApiDataSource#list
  649. * @param name
  650. * @param fields
  651. * @param limit
  652. * @param skip
  653. * @param sort
  654. * @returns {promise}
  655. */
  656. api.ApiDataSource.prototype.list = function (name, fields, limit, skip, sort) {
  657. name = name || '';
  658. system.log('ApiDataSource: ' + this.collection + ': list ' + name);
  659. var cmd = 'list.' + name;
  660. var url = this.getBaseUrl();
  661. if (name != null && name.length > 0) {
  662. url += 'list/' + name + '/';
  663. }
  664. var p = this.getParamsDict(fields, limit, skip, sort);
  665. if (!$.isEmptyObject(p)) {
  666. url += '?' + this.getParams(p);
  667. }
  668. return this._ajaxGet(cmd, url);
  669. };
  670. /**
  671. * Searches for objects in the collection
  672. * @method
  673. * @name ApiDataSource#search
  674. * @param params
  675. * @param fields
  676. * @param limit
  677. * @param skip
  678. * @param sort
  679. * @param mimeType
  680. * @returns {promise}
  681. */
  682. api.ApiDataSource.prototype.search = function (params, fields, limit, skip, sort, mimeType) {
  683. system.log('ApiDataSource: ' + this.collection + ': search ' + params);
  684. var cmd = 'search';
  685. var url = this.searchUrl(params, fields, limit, skip, sort, mimeType);
  686. return this._ajaxGet(cmd, url);
  687. };
  688. api.ApiDataSource.prototype.searchUrl = function (params, fields, limit, skip, sort, mimeType) {
  689. var url = this.getBaseUrl() + 'search';
  690. var p = $.extend(this.getParamsDict(fields, limit, skip, sort), params);
  691. if (mimeType != null && mimeType.length > 0) {
  692. p['mimeType'] = mimeType;
  693. }
  694. url += '?' + this.getParams(p);
  695. return url;
  696. };
  697. /**
  698. * Calls a certain method on an object or on the entire collection
  699. * @method
  700. * @name ApiDataSource#call
  701. * @param pk
  702. * @param method
  703. * @param params
  704. * @param fields
  705. * @param timeOut
  706. * @param usePost
  707. * @returns {promise}
  708. */
  709. api.ApiDataSource.prototype.call = function (pk, method, params, fields, timeOut, usePost) {
  710. system.log('ApiDataSource: ' + this.collection + ': call ' + method);
  711. var cmd = 'call.' + method;
  712. var url = pk != null && pk.length > 0 ? this.getBaseUrl() + pk + '/call/' + method : this.getBaseUrl() + 'call/' + method;
  713. var p = $.extend({}, this.getParamsDict(fields, null, null, null), params);
  714. var getUrl = url + '?' + this.getParams(p);
  715. if (usePost || getUrl.length >= MAX_QUERYSTRING_LENGTH) {
  716. return this._ajaxPost(cmd, url, p, timeOut);
  717. } else {
  718. return this._ajaxGet(cmd, getUrl, timeOut);
  719. }
  720. };
  721. /**
  722. * Calls a certain method on one or more objects in a collection
  723. * @method
  724. * @name ApiDataSource#callMultiple
  725. * @param pks
  726. * @param method
  727. * @param params
  728. * @param fields
  729. * @param timeOut
  730. * @param usePost
  731. * @returns {promise}
  732. */
  733. api.ApiDataSource.prototype.callMultiple = function (pks, method, params, fields, timeOut, usePost) {
  734. system.log('ApiDataSource: ' + this.collection + ': call ' + method);
  735. var cmd = 'call.' + method;
  736. var url = this.getBaseUrl() + pks.join(',') + '/call/' + method;
  737. var p = $.extend({}, this.getParamsDict(fields, null, null, null), params);
  738. var getUrl = url + '?' + this.getParams(p);
  739. if (usePost || getUrl.length >= MAX_QUERYSTRING_LENGTH) {
  740. return this._ajaxPost(cmd, url, p, timeOut);
  741. } else {
  742. return this._ajaxGet(cmd, getUrl, timeOut);
  743. }
  744. };
  745. /**
  746. * Makes a long call (timeout 60s) to a certain method on an object or on the entire collection
  747. * @method
  748. * @name ApiDataSource#longCall
  749. * @param pk
  750. * @param method
  751. * @param params
  752. * @param fields
  753. * @param usePost
  754. * @returns {promise}
  755. */
  756. api.ApiDataSource.prototype.longCall = function (pk, method, params, fields, usePost) {
  757. return this.call(pk, method, params, fields, 60000, usePost);
  758. };
  759. /**
  760. * Gets the base url for all calls to this collection
  761. * @method
  762. * @name ApiDataSource#getBaseUrl
  763. * @returns {string}
  764. */
  765. api.ApiDataSource.prototype.getBaseUrl = function () {
  766. var tokenType = this.user.tokenType != null && this.user.tokenType.length > 0 ? this.user.tokenType : 'null';
  767. //Don't use cached version of this because when user session gets expired
  768. //a new token is generated
  769. return this.urlApi + '/' + this.user.userId + '/' + this.user.userToken + '/' + tokenType + '/' + this.collection + '/';
  770. };
  771. /**
  772. * Prepare some parameters so we can use them during a request
  773. * @method
  774. * @name ApiDataSource#getParams
  775. * @param data
  776. * @returns {object}
  777. */
  778. api.ApiDataSource.prototype.getParams = function (data) {
  779. return $.param(this.ajax._prepareDict(data));
  780. };
  781. /**
  782. * Gets a dictionary of parameters
  783. * @method
  784. * @name ApiDataSource#getParamsDict
  785. * @param fields
  786. * @param limit
  787. * @param skip
  788. * @param sort
  789. * @returns {{}}
  790. */
  791. api.ApiDataSource.prototype.getParamsDict = function (fields, limit, skip, sort) {
  792. var p = {};
  793. if (fields) {
  794. p['_fields'] = $.isArray(fields) ? fields.join(',') : fields.replace(/\s/g, '');
  795. }
  796. if (limit) {
  797. p['_limit'] = limit;
  798. }
  799. if (skip) {
  800. p['_skip'] = skip;
  801. }
  802. if (sort) {
  803. p['_sort'] = sort;
  804. }
  805. if (this.version) {
  806. p['_v'] = this.version;
  807. }
  808. return p;
  809. };
  810. /**
  811. * Does an ajax GET call using the api.ApiAjax object
  812. * @param cmd
  813. * @param url
  814. * @param timeout
  815. * @returns {promise}
  816. * @private
  817. */
  818. api.ApiDataSource.prototype._ajaxGet = function (cmd, url, timeout) {
  819. return this.ajax.get(url, timeout, {
  820. coll: this.collection,
  821. cmd: cmd
  822. });
  823. };
  824. /**
  825. * Does an ajax POST call using the api.ApiAjax object
  826. * @param cmd
  827. * @param url
  828. * @param data
  829. * @param timeout
  830. * @returns {promise}
  831. * @private
  832. */
  833. api.ApiDataSource.prototype._ajaxPost = function (cmd, url, data, timeout) {
  834. return this.ajax.post(url, data, timeout, {
  835. coll: this.collection,
  836. cmd: cmd
  837. });
  838. };
  839. return api;
  840. }(jquery, moment);
  841. settings = { amazonBucket: 'app' };
  842. common_code = {
  843. /**
  844. * isCodeValid
  845. *
  846. * @memberOf common
  847. * @name common#isCodeValid
  848. * @method
  849. *
  850. * @param codeId
  851. * @return {Boolean}
  852. */
  853. isCodeValid: function (codeId) {
  854. // Checks if a code is syntactically valid
  855. // This does not mean that it is an official code issued by CHEQROOM
  856. return codeId.trim().match(/^[a-z0-9]{8}$/i) != null;
  857. },
  858. /**
  859. * isCodeFromScanner
  860. *
  861. * @memberOf common
  862. * @name common#isCodeFromScanner
  863. * @method
  864. *
  865. * @param urlPart
  866. * @return {Boolean}
  867. */
  868. isCodeFromScanner: function (urlPart) {
  869. // If no urlPart is given or is empty, return false
  870. if (!urlPart || urlPart.length == 0)
  871. return false;
  872. var prefix = urlPart.substring(0, 23);
  873. var index = 'http://cheqroom.com/qr/'.indexOf(prefix);
  874. return index == 0;
  875. },
  876. /**
  877. * isBarcodeValid
  878. *
  879. * @memberOf common
  880. * @name common#isValidBarcode
  881. * @method
  882. *
  883. * @param {string} barCode
  884. * @return {Boolean}
  885. */
  886. isValidBarcode: function (barCode) {
  887. return barCode && barCode.match(/^[A-Z0-9\-]{4,22}$/i) != null;
  888. },
  889. /**
  890. * isValidQRCode
  891. *
  892. * @memberOf common
  893. * @name common#isValidQRCode
  894. * @method
  895. *
  896. * @param {string} qrCode
  897. * @return {Boolean}
  898. */
  899. isValidQRCode: function (qrCode) {
  900. return this.isValidItemQRCode(qrCode) || this.isValidTransferQRCode(qrCode);
  901. },
  902. /**
  903. * isValidTransferQRCode
  904. * For example: http://cheqroom.com/ordertransfer/tTfZXW6eTianQU3UQVELdn
  905. *
  906. * @memberOf common
  907. * @name common#isValidTransferQRCode
  908. * @method
  909. *
  910. * @param {string} qrCode
  911. * @return {Boolean}
  912. */
  913. isValidTransferQRCode: function (qrCode) {
  914. return qrCode.match(/^http:\/\/cheqroom\.com\/ordertransfer\/[a-zA-Z0-9]{22}$/i) != null;
  915. },
  916. /**
  917. * isValidDocQRCode
  918. * For example: http://cheqroom.com/qr/eeaa37ed
  919. *
  920. * @memberOf common
  921. * @name common#isValidDocQRCode
  922. * @method
  923. *
  924. * @param {string} qrCode
  925. * @return {Boolean}
  926. */
  927. isValidDocQRCode: function (qrCode) {
  928. return qrCode && (qrCode.match(/^http:\/\/cheqroom\.com\/qr\/[a-z0-9]{8}$/i) != null || qrCode.match(/^[a-z0-9]{8}$/i) != null);
  929. },
  930. /**
  931. * isValidItemQRCode
  932. *
  933. * @memberOf common
  934. * @name common#isValidItemQRCode
  935. * @method
  936. *
  937. * @param {string} qrCode
  938. * @return {Boolean}
  939. */
  940. isValidItemQRCode: function (qrCode) {
  941. return this.isValidDocQRCode(qrCode);
  942. },
  943. /**
  944. * isValidKitQRCode
  945. *
  946. * @memberOf common
  947. * @name common#isValidKitQRCode
  948. * @method
  949. *
  950. * @param {string} qrCode
  951. * @return {Boolean}
  952. */
  953. isValidKitQRCode: function (qrCode) {
  954. return this.isValidDocQRCode(qrCode);
  955. },
  956. /**
  957. * getCheqRoomRedirectUrl
  958. *
  959. * @memberOf common
  960. * @name common#getCheqRoomRedirectUrl
  961. * @method
  962. *
  963. * @param codeId
  964. * @return {string}
  965. */
  966. getCheqRoomRedirectUrl: function (codeId) {
  967. return this.isCodeValid(codeId) ? 'http://cheqroom.com/qr/' + codeId.trim() : '';
  968. },
  969. /**
  970. * getCheqRoomRedirectUrlQR
  971. *
  972. * @memberOf common
  973. * @name common#getCheqRoomRedirectUrlQR
  974. * @method
  975. *
  976. * @param codeId
  977. * @param size
  978. * @return {string}
  979. */
  980. getCheqRoomRedirectUrlQR: function (codeId, size) {
  981. if (this.isCodeValid(codeId)) {
  982. //https://chart.googleapis.com/chart?chs=200x200&cht=qr&choe=UTF-8&chld=L|0&chl=http://cheqroom.com/qr/c4ab3a6a
  983. var url = encodeURI(this.getCheqRoomRedirectUrl(codeId));
  984. return 'https://chart.googleapis.com/chart?chs=' + size + 'x' + size + '&cht=qr&choe=UTF-8&chld=L|0&chl=' + url;
  985. } else {
  986. return '';
  987. }
  988. },
  989. /**
  990. * getQRCodeUrl
  991. *
  992. * @memberOf common
  993. * @name common#getCheqRoomRedirectUrlQR
  994. * @method
  995. *
  996. * @param {string} urlApi
  997. * @param {string} code
  998. * @param {number} size
  999. * @return {string}
  1000. */
  1001. getQRCodeUrl: function (urlApi, code, size) {
  1002. return urlApi + '/qrcode?code=' + code + '&size=' + size;
  1003. },
  1004. /**
  1005. * getBarcodeUrl
  1006. *
  1007. * @memberOf common
  1008. * @name common#getCheqRoomRedirectUrlQR
  1009. * @method
  1010. *
  1011. * @param {string} urlApi
  1012. * @param {string} code
  1013. * @param {number} size
  1014. * @return {string}
  1015. */
  1016. getBarcodeUrl: function (urlApi, code, width, height) {
  1017. return urlApi + '/barcode?code=' + code + '&width=' + width + (height ? '&height=' + height : '');
  1018. }
  1019. };
  1020. common_order = function (moment) {
  1021. return {
  1022. /**
  1023. * getFriendlyOrderStatus
  1024. *
  1025. * @memberOf common
  1026. * @name common#getFriendlyOrderStatus
  1027. * @method
  1028. *
  1029. * @param {string} status
  1030. * @return {string}
  1031. */
  1032. getFriendlyOrderStatus: function (status) {
  1033. // ORDER_STATUS = ('creating', 'open', 'closed')
  1034. switch (status) {
  1035. case 'creating':
  1036. return 'Draft';
  1037. case 'open':
  1038. return 'Open';
  1039. case 'closed':
  1040. return 'Completed';
  1041. default:
  1042. return 'Unknown';
  1043. }
  1044. },
  1045. /**
  1046. * getFriendlyOrderCss
  1047. *
  1048. * @memberOf common
  1049. * @name common#getFriendlyOrderCss
  1050. * @method
  1051. *
  1052. * @param {string} status
  1053. * @return {string}
  1054. */
  1055. getFriendlyOrderCss: function (status) {
  1056. switch (status) {
  1057. case 'creating':
  1058. return 'label-creating';
  1059. case 'open':
  1060. return 'label-open';
  1061. case 'closed':
  1062. return 'label-closed';
  1063. default:
  1064. return '';
  1065. }
  1066. },
  1067. /**
  1068. * getFriendlyOrderSize
  1069. *
  1070. * @memberOf common
  1071. * @name common#getFriendlyOrderSize
  1072. * @method
  1073. *
  1074. * @param {object} order
  1075. * @return {string}
  1076. */
  1077. getFriendlyOrderSize: function (order) {
  1078. if (order.items && order.items.length > 0) {
  1079. var str = order.items.length + ' item';
  1080. if (order.items.length > 1) {
  1081. str += 's';
  1082. }
  1083. return str;
  1084. } else {
  1085. return 'No items';
  1086. }
  1087. },
  1088. /**
  1089. * isOrderOverdue
  1090. *
  1091. * @memberOf common
  1092. * @name common#isOrderOverdue
  1093. * @method
  1094. *
  1095. * @param {object} order
  1096. * @param {moment} now
  1097. * @return {Boolean}
  1098. */
  1099. isOrderOverdue: function (order, now) {
  1100. now = now || moment();
  1101. return order.status == 'open' && now.isAfter(order.due);
  1102. },
  1103. /**
  1104. * isOrderArchived
  1105. *
  1106. * @memberOf common
  1107. * @name common#isOrderArchived
  1108. * @method
  1109. *
  1110. * @param {object} order
  1111. * @return {Boolean}
  1112. */
  1113. isOrderArchived: function (order) {
  1114. return order && order.archived != null;
  1115. },
  1116. /**
  1117. * getOrderStatus
  1118. *
  1119. * @memberOf common
  1120. * @name common#getOrderStatus
  1121. * @method
  1122. *
  1123. * @param {object} order
  1124. * @param {moment} now
  1125. * @return {string}
  1126. */
  1127. getOrderStatus: function (order, now) {
  1128. now = now || moment();
  1129. if (this.isOrderOverdue(order, now)) {
  1130. return 'Overdue';
  1131. } else if (this.isOrderArchived(order)) {
  1132. return 'Archived';
  1133. } else {
  1134. return this.getFriendlyOrderStatus(order.status);
  1135. }
  1136. },
  1137. /**
  1138. * getOrderCss
  1139. *
  1140. * @memberOf common
  1141. * @name common#getOrderCss
  1142. * @method
  1143. *
  1144. * @param {object} order
  1145. * @param {moment} now
  1146. * @return {string}
  1147. */
  1148. getOrderCss: function (order, now) {
  1149. now = now || moment();
  1150. if (this.isOrderOverdue(order, now)) {
  1151. return 'label-overdue';
  1152. } else if (this.isOrderArchived(order)) {
  1153. return this.getFriendlyOrderCss(order.status) + ' label-striped';
  1154. } else {
  1155. return this.getFriendlyOrderCss(order.status);
  1156. }
  1157. }
  1158. };
  1159. }(moment);
  1160. common_reservation = {
  1161. /**
  1162. * getFriendlyReservationCss
  1163. *
  1164. * @memberOf common
  1165. * @name common#getFriendlyReservationCss
  1166. * @method
  1167. *
  1168. * @param {string} status
  1169. * @return {string}
  1170. */
  1171. getFriendlyReservationCss: function (status) {
  1172. switch (status) {
  1173. case 'creating':
  1174. return 'label-creating';
  1175. case 'open':
  1176. return 'label-open';
  1177. case 'closed':
  1178. return 'label-closed';
  1179. case 'cancelled':
  1180. return 'label-cancelled';
  1181. default:
  1182. return '';
  1183. }
  1184. },
  1185. /**
  1186. * getFriendlyReservationStatus
  1187. *
  1188. * @memberOf common
  1189. * @name common#getFriendlyReservationStatus
  1190. * @method
  1191. *
  1192. * @param {string} status
  1193. * @return {string}
  1194. */
  1195. getFriendlyReservationStatus: function (status) {
  1196. switch (status) {
  1197. case 'creating':
  1198. return 'Draft';
  1199. case 'open':
  1200. return 'Booked';
  1201. case 'closed':
  1202. return 'Completed';
  1203. case 'cancelled':
  1204. return 'Cancelled';
  1205. default:
  1206. return 'Unknown';
  1207. }
  1208. },
  1209. /**
  1210. * getFriendlyReservationFrequency
  1211. *
  1212. * @memberOf common
  1213. * @name common#getFriendlyReservationFrequency
  1214. * @method
  1215. *
  1216. * @param {string} frequency
  1217. * @return {string}
  1218. */
  1219. getFriendlyReservationFrequency: function (frequency) {
  1220. switch (frequency) {
  1221. case 'every_day':
  1222. return 'Repeats every day';
  1223. case 'every_weekday':
  1224. return 'Repeats every weekday';
  1225. case 'every_week':
  1226. return 'Repeats every week';
  1227. case 'every_2_weeks':
  1228. return 'Repeats every 2 weeks';
  1229. case 'every_month':
  1230. return 'Repeats every month';
  1231. case 'every_2_months':
  1232. return 'Repeats every 2 months';
  1233. case 'every_3_months':
  1234. return 'Repeats every 3 months';
  1235. case 'every_6_months':
  1236. return 'Repeats every 6 months';
  1237. default:
  1238. return 'Repeating reservation';
  1239. }
  1240. },
  1241. /**
  1242. * isReservationOverdue
  1243. *
  1244. * @memberOf common
  1245. * @name common#isReservationOverdue
  1246. * @method
  1247. *
  1248. * @param {object} reservation
  1249. * @param {moment} now
  1250. * @return {Boolean}
  1251. */
  1252. isReservationOverdue: function (reservation, now) {
  1253. now = now || moment();
  1254. return reservation.status == 'open' && now.isAfter(reservation.fromDate || reservation.from);
  1255. },
  1256. /**
  1257. * isReservationInThePast
  1258. *
  1259. * @memberOf common
  1260. * @name common#isReservationInThePast
  1261. * @method
  1262. *
  1263. * @param {object} reservation
  1264. * @param {moment} now
  1265. * @return {Boolean}
  1266. */
  1267. isReservationInThePast: function (reservation, now) {
  1268. now = now || moment();
  1269. return reservation.status == 'open' && now.isAfter(reservation.fromDate) && now.isAfter(reservation.toDate);
  1270. },
  1271. /**
  1272. * isReservationArchived
  1273. *
  1274. * @memberOf common
  1275. * @name common#isReservationArchived
  1276. * @method
  1277. *
  1278. * @param {object} reservation
  1279. * @return {Boolean}
  1280. */
  1281. isReservationArchived: function (reservation) {
  1282. return reservation && reservation.archived != null;
  1283. },
  1284. /**
  1285. * getReservationCss
  1286. *
  1287. * @memberOf common
  1288. * @name common#getReservationCss
  1289. * @method
  1290. *
  1291. * @param {object} reservation
  1292. * @return {string}
  1293. */
  1294. getReservationCss: function (reservation) {
  1295. if (this.isOrderArchived(reservation)) {
  1296. return this.getFriendlyReservationCss(reservation.status) + ' label-striped';
  1297. } else {
  1298. return this.getFriendlyReservationCss(reservation.status);
  1299. }
  1300. }
  1301. };
  1302. common_item = function () {
  1303. var that = {};
  1304. that.itemCanTakeCustody = function (item) {
  1305. return item.status == 'available';
  1306. };
  1307. that.itemCanReleaseCustody = function (item) {
  1308. return item.status == 'in_custody';
  1309. };
  1310. that.itemCanTransferCustody = function (item) {
  1311. return item.status == 'in_custody';
  1312. };
  1313. that.itemCanReserve = function (item) {
  1314. return item.status != 'expired' && item.status != 'in_custody';
  1315. };
  1316. that.itemCanCheckout = function (item) {
  1317. return item.status == 'available';
  1318. };
  1319. that.itemCanGoToCheckout = function (item) {
  1320. return item.status == 'checkedout' || item.status == 'await_checkout';
  1321. };
  1322. that.itemCanCheckin = function (item) {
  1323. return item.status == 'checkedout';
  1324. };
  1325. that.itemCanExpire = function (item) {
  1326. return item.status == 'available';
  1327. };
  1328. that.itemCanUndoExpire = function (item) {
  1329. return item.status == 'expired';
  1330. };
  1331. that.itemCanDelete = function (item) {
  1332. return item.status == 'available' || item.status == 'expired';
  1333. };
  1334. /**
  1335. * getFriendlyItemStatus
  1336. *
  1337. * @memberOf common
  1338. * @name common#getFriendlyItemStatus
  1339. * @method
  1340. *
  1341. * @param status
  1342. * @return {string}
  1343. */
  1344. that.getFriendlyItemStatus = function (status) {
  1345. // ITEM_STATUS = ('available', 'checkedout', 'await_checkout', 'in_transit', 'maintenance', 'repair', 'inspection', 'expired')
  1346. switch (status) {
  1347. case 'available':
  1348. return 'Available';
  1349. case 'checkedout':
  1350. return 'Checked out';
  1351. case 'await_checkout':
  1352. return 'Checking out';
  1353. case 'in_transit':
  1354. return 'In transit';
  1355. case 'in_custody':
  1356. return 'In custody';
  1357. case 'maintenance':
  1358. return 'Maintenance';
  1359. case 'repair':
  1360. return 'Repair';
  1361. case 'inspection':
  1362. return 'Inspection';
  1363. case 'expired':
  1364. return 'Expired';
  1365. default:
  1366. return 'Unknown';
  1367. }
  1368. };
  1369. /**
  1370. * getItemStatusCss
  1371. *
  1372. * @memberOf common
  1373. * @name common#getItemStatusCss
  1374. * @method
  1375. *
  1376. * @param status
  1377. * @return {string}
  1378. */
  1379. that.getItemStatusCss = function (status) {
  1380. switch (status) {
  1381. case 'available':
  1382. return 'label-available';
  1383. case 'checkedout':
  1384. return 'label-checkedout';
  1385. case 'await_checkout':
  1386. return 'label-awaitcheckout';
  1387. case 'in_transit':
  1388. return 'label-transit';
  1389. case 'in_custody':
  1390. return 'label-custody';
  1391. case 'maintenance':
  1392. return 'label-maintenance';
  1393. case 'repair':
  1394. return 'label-repair';
  1395. case 'inspection':
  1396. return 'label-inspection';
  1397. case 'expired':
  1398. return 'label-expired';
  1399. default:
  1400. return '';
  1401. }
  1402. };
  1403. /**
  1404. * getItemStatusIcon
  1405. *
  1406. * @memberOf common
  1407. * @name common#getItemStatusIcon
  1408. * @method
  1409. *
  1410. * @param status
  1411. * @return {string}
  1412. */
  1413. that.getItemStatusIcon = function (status) {
  1414. switch (status) {
  1415. case 'available':
  1416. return 'fa fa-check-circle';
  1417. case 'checkedout':
  1418. return 'fa fa-times-circle';
  1419. case 'await_checkout':
  1420. return 'fa fa-ellipsis-h';
  1421. case 'in_transit':
  1422. return 'fa fa-truck';
  1423. case 'in_custody':
  1424. return 'fa fa-exchange';
  1425. case 'maintenance':
  1426. return 'fa fa-wrench';
  1427. case 'repair':
  1428. return 'fa fa-wrench';
  1429. case 'inspection':
  1430. return 'fa fa-stethoscope';
  1431. case 'expired':
  1432. return 'fa fa-bug';
  1433. default:
  1434. return '';
  1435. }
  1436. };
  1437. /**
  1438. * getItemsByStatus
  1439. *
  1440. * @memberOf common
  1441. * @name common#getItemsByStatus
  1442. * @method
  1443. *
  1444. * @param {Array} items
  1445. * @param {string|function} comparator
  1446. * @return {Array}
  1447. */
  1448. that.getItemsByStatus = function (items, comparator) {
  1449. if (!items)
  1450. return [];
  1451. return items.filter(function (item) {
  1452. if (typeof comparator == 'string') {
  1453. //filter items on status
  1454. return item.status == comparator;
  1455. } else {
  1456. //use custom comparator to filter items
  1457. return comparator(item);
  1458. }
  1459. });
  1460. };
  1461. /**
  1462. * getAvailableItems
  1463. *
  1464. * @memberOf common
  1465. * @name common#getAvailableItems
  1466. * @method
  1467. *
  1468. * @param {Array} items
  1469. * @return {Array}
  1470. */
  1471. that.getAvailableItems = function (items) {
  1472. return this.getItemsByStatus(items, 'available');
  1473. };
  1474. /**
  1475. * getActiveItems
  1476. *
  1477. * @memberOf common
  1478. * @name common#getActiveItems
  1479. * @method
  1480. *
  1481. * @param {Array} items
  1482. * @return {Array}
  1483. */
  1484. that.getActiveItems = function (items) {
  1485. return this.getItemsByStatus(items, function (item) {
  1486. return item.status != 'expired' && item.status != 'in_custody';
  1487. });
  1488. };
  1489. /**
  1490. * getItemIds
  1491. *
  1492. * @memberOf common
  1493. * @name common#getItemIds
  1494. * @method
  1495. *
  1496. * @param items
  1497. * @return {array}
  1498. */
  1499. that.getItemIds = function (items) {
  1500. return items.map(function (item) {
  1501. return typeof item === 'string' ? item : item._id;
  1502. });
  1503. };
  1504. return that;
  1505. }();
  1506. common_conflicts = {
  1507. /**
  1508. * getFriendlyConflictKind
  1509. *
  1510. * @memberOf common
  1511. * @name common#getFriendlyConflictKind
  1512. * @method
  1513. *
  1514. * @param kind
  1515. * @return {string}
  1516. */
  1517. getFriendlyConflictKind: function (kind) {
  1518. switch (kind) {
  1519. case 'location':
  1520. return 'At wrong location';
  1521. case 'order':
  1522. return 'Checked out';
  1523. case 'reservation':
  1524. return 'Already reserved';
  1525. case 'expired':
  1526. return 'Item is expired';
  1527. case 'custody':
  1528. return 'Item is in custody';
  1529. default:
  1530. return '';
  1531. }
  1532. }
  1533. };
  1534. common_keyValues = function () {
  1535. var _getCategoryName = function (obj) {
  1536. return typeof obj === 'string' ? obj : obj['name'];
  1537. };
  1538. return {
  1539. /**
  1540. * Creates a category key from a friendly name
  1541. *
  1542. * @memberOf common
  1543. * @name common#getCategoryKeyFromName
  1544. * @method
  1545. *
  1546. * @param {string} name
  1547. * @return {string}
  1548. */
  1549. getCategoryKeyFromName: function (name) {
  1550. return 'cheqroom.types.item.' + name.split(' ').join('_').split('.').join('').toLowerCase();
  1551. },
  1552. /**
  1553. * Creates a name from a category key
  1554. *
  1555. * @memberOf common
  1556. * @name common#getCategoryNameFromKey
  1557. * @method
  1558. *
  1559. * @param {string} key
  1560. * @return {string}
  1561. */
  1562. getCategoryNameFromKey: function (key) {
  1563. var re = new RegExp('_', 'g');
  1564. return key.split('.').pop().replace(re, ' ');
  1565. },
  1566. /**
  1567. * getCategorySummary
  1568. *
  1569. * @memberOf common
  1570. * @name common#getCategorySummary
  1571. * @method
  1572. *
  1573. * @param {array} items
  1574. * @return {string}
  1575. */
  1576. getCategorySummary: function (items) {
  1577. items = items || [];
  1578. if (items.length == 0) {
  1579. return 'No items';
  1580. }
  1581. var item = null, key = null, catName = null, catSummary = {}, firstKey = '', firstKeyCount = 0;
  1582. for (var i = 0, len = items.length; i < len; i++) {
  1583. item = items[i];
  1584. catName = item.category ? _getCategoryName(item.category) : '';
  1585. key = catName ? this.getCategoryNameFromKey(catName) : '';
  1586. //console.log(item.category, catName, key);
  1587. if (!catSummary[key]) {
  1588. catSummary[key] = 1;
  1589. } else {
  1590. catSummary[key] += 1;
  1591. }
  1592. // first key should be category with largest number of items
  1593. if (catSummary[key] > firstKeyCount) {
  1594. firstKey = key;
  1595. firstKeyCount = catSummary[key];
  1596. }
  1597. }
  1598. var summ = catSummary[firstKey] + ' ';
  1599. if (firstKeyCount == 1 && String.prototype.singularize) {
  1600. summ += firstKey.singularize();
  1601. } else {
  1602. summ += firstKey;
  1603. }
  1604. if (items.length > catSummary[firstKey]) {
  1605. var other = items.length - catSummary[firstKey];
  1606. summ += ' +' + other + ' other';
  1607. }
  1608. return summ;
  1609. },
  1610. /**
  1611. * getItemSummary
  1612. *
  1613. * Works much like getCategorySummary but prefers making summaries with kit names in it
  1614. *
  1615. * @memberOf common
  1616. * @name common#getItemSummary
  1617. * @method
  1618. *
  1619. * @param {array} items
  1620. * @return {string}
  1621. */
  1622. getItemSummary: function (items) {
  1623. items = items || [];
  1624. if (items.length == 0) {
  1625. return 'No items';
  1626. }
  1627. var sep = ', ', item = null, numKits = 0, kitItems = {}, unkittedItems = [];
  1628. // Do a first loop to extract all items for which we have a kit name
  1629. // If we don't have the kit.name field, we'll treat the item as if
  1630. // the item was not in a kit, and put it in unkittedItems
  1631. for (var i = 0, len = items.length; i < len; i++) {
  1632. item = items[i];
  1633. if (item.kit && item.kit.name) {
  1634. if (kitItems[item.kit.name]) {
  1635. kitItems[item.kit.name].push(item);
  1636. } else {
  1637. kitItems[item.kit.name] = [item];
  1638. numKits += 1;
  1639. }
  1640. } else {
  1641. unkittedItems.push(item);
  1642. }
  1643. }
  1644. // If we have no kits (or no kit names),
  1645. // we'll just use getCategorySummary
  1646. // which works pretty well for that
  1647. if (numKits == 0) {
  1648. return this.getCategorySummary(items);
  1649. } else {
  1650. // Get all the kit names as an array
  1651. var names = $.map(kitItems, function (val, key) {
  1652. return key;
  1653. });
  1654. // We only have kits and not unkitted items
  1655. // We can try to make a very short summary of the kit names
  1656. // If we can't fit multiple kit names into a single string
  1657. // we'll take 1 (or more) and then add "+3 other kits"
  1658. if (unkittedItems.length == 0) {
  1659. var maxKitNamesLength = 30;
  1660. return names.joinOther(maxKitNamesLength, sep, 'other kits');
  1661. } else {
  1662. // We have a mix of kits an unkitted items
  1663. // If we only have one kit, we'll use its name
  1664. // and just paste getCategorySummary after it
  1665. if (numKits == 1) {
  1666. return names[0] + sep + this.getCategorySummary(unkittedItems);
  1667. } else {
  1668. // We have multiple kits, so we'll put
  1669. // 3 kits, 5 pumps +2 other
  1670. return len(names) + ' kits' + sep + this.getCategorySummary(unkittedItems);
  1671. }
  1672. }
  1673. }
  1674. }
  1675. };
  1676. }();
  1677. common_image = function ($) {
  1678. return {
  1679. /**
  1680. * Returns an avatar image with the initials of the user
  1681. * source: http://codepen.io/leecrossley/pen/CBHca
  1682. *
  1683. * @memberOf common
  1684. * @name common#getAvatarInitial
  1685. * @method
  1686. *
  1687. * @param {string} name name for which to display the initials
  1688. * @param {string} size Possible values XS,S,M,L,XL
  1689. * @return {string} base64 image url
  1690. */
  1691. getAvatarInitial: function (name, size) {
  1692. name = name || 'Unknown';
  1693. var sizes = {
  1694. 'XS': 32,
  1695. 'S': 64,
  1696. 'M': 128,
  1697. 'L': 256,
  1698. 'XL': 512
  1699. };
  1700. var colours = [
  1701. '#1abc9c',
  1702. '#2ecc71',
  1703. '#3498db',
  1704. '#9b59b6',
  1705. '#34495e',
  1706. '#16a085',
  1707. '#27ae60',
  1708. '#2980b9',
  1709. '#8e44ad',
  1710. '#2c3e50',
  1711. '#f1c40f',
  1712. '#e67e22',
  1713. '#e74c3c',
  1714. '#95a5a6',
  1715. '#f39c12',
  1716. '#d35400',
  1717. '#c0392b',
  1718. '#bdc3c7'
  1719. ];
  1720. var nameSplit = name.split(' '), initials = nameSplit.length == 2 ? nameSplit[0].charAt(0).toUpperCase() + nameSplit[1].charAt(0).toUpperCase() : nameSplit[0].charAt(0).toUpperCase();
  1721. var charIndex = initials.charCodeAt(0) - 65, colourIndex = charIndex % colours.length;
  1722. var canvasWidth = sizes[size], canvasHeight = sizes[size], canvasCssWidth = canvasWidth, canvasCssHeight = canvasHeight;
  1723. var $canvas = $('<canvas />').attr({
  1724. width: canvasWidth,
  1725. height: canvasHeight
  1726. });
  1727. var context = $canvas.get(0).getContext('2d');
  1728. if (window.devicePixelRatio) {
  1729. $canvas.attr('width', canvasWidth * window.devicePixelRatio);
  1730. $canvas.attr('height', canvasHeight * window.devicePixelRatio);
  1731. $canvas.css('width', canvasCssWidth);
  1732. $canvas.css('height', canvasCssHeight);
  1733. context.scale(window.devicePixelRatio, window.devicePixelRatio);
  1734. }
  1735. context.fillStyle = colours[colourIndex];
  1736. context.fillRect(0, 0, canvasWidth, canvasHeight);
  1737. context.font = canvasWidth / 2 + 'px Arial';
  1738. context.textAlign = 'center';
  1739. context.fillStyle = '#FFF';
  1740. context.fillText(initials, canvasCssWidth / 2, canvasCssHeight / 1.5);
  1741. return $canvas.get(0).toDataURL();
  1742. },
  1743. /**
  1744. * Returns an maintenace avatar image
  1745. *
  1746. * @memberOf common
  1747. * @name common#getMaintenanceAvatar
  1748. * @method
  1749. *
  1750. * @param {string} size Possible values XS,S,M,L,XL
  1751. * @return {string} base64 image url
  1752. */
  1753. getMaintenanceAvatar: function (size) {
  1754. return this.getIconAvatar(size, 'f0ad');
  1755. },
  1756. /**
  1757. * Returns an icon avatar image from FontAwesome collection
  1758. *
  1759. * @memberOf common
  1760. * @name common#getIconAvatar
  1761. * @method
  1762. *
  1763. * @param {string} size Possible values XS,S,M,L,XL
  1764. * @return {string} base64 image url
  1765. */
  1766. getIconAvatar: function (size, value) {
  1767. var sizes = {
  1768. 'XS': 32,
  1769. 'S': 64,
  1770. 'M': 128,
  1771. 'L': 256,
  1772. 'XL': 512
  1773. };
  1774. var canvasWidth = sizes[size], canvasHeight = sizes[size], canvasCssWidth = canvasWidth, canvasCssHeight = canvasHeight;
  1775. var $canvas = $('<canvas />').attr({
  1776. width: canvasWidth,
  1777. height: canvasHeight
  1778. });
  1779. var context = $canvas.get(0).getContext('2d');
  1780. if (window.devicePixelRatio) {
  1781. $canvas.attr('width', canvasWidth * window.devicePixelRatio);
  1782. $canvas.attr('height', canvasHeight * window.devicePixelRatio);
  1783. $canvas.css('width', canvasCssWidth);
  1784. $canvas.css('height', canvasCssHeight);
  1785. context.scale(window.devicePixelRatio, window.devicePixelRatio);
  1786. }
  1787. context.fillStyle = '#f5f5f5';
  1788. context.fillRect(0, 0, canvasWidth, canvasHeight);
  1789. context.font = canvasWidth / 2 + 'px FontAwesome';
  1790. context.textAlign = 'center';
  1791. context.fillStyle = '#aaa';
  1792. context.fillText(String.fromCharCode('0x' + value), canvasCssWidth / 2, canvasCssHeight / 1.5);
  1793. return $canvas.get(0).toDataURL();
  1794. },
  1795. getTextImage: function (text, size, fontColorHex, backgroundColorHex) {
  1796. var sizes = {
  1797. 'XS': 32,
  1798. 'S': 64,
  1799. 'M': 128,
  1800. 'L': 256,
  1801. 'XL': 512
  1802. };
  1803. if (!fontColorHex)
  1804. fontColorHex = '#aaa';
  1805. if (!backgroundColorHex)
  1806. backgroundColorHex = '#e5e5e5';
  1807. var canvasWidth = sizes[size], canvasHeight = sizes[size], canvasCssWidth = canvasWidth, canvasCssHeight = canvasHeight;
  1808. var $canvas = $('<canvas />').attr({
  1809. width: canvasWidth,
  1810. height: canvasHeight
  1811. });
  1812. var context = $canvas.get(0).getContext('2d');
  1813. if (window.devicePixelRatio) {
  1814. $canvas.attr('width', canvasWidth * window.devicePixelRatio);
  1815. $canvas.attr('height', canvasHeight * window.devicePixelRatio);
  1816. $canvas.css('width', canvasCssWidth);
  1817. $canvas.css('height', canvasCssHeight);
  1818. context.scale(window.devicePixelRatio, window.devicePixelRatio);
  1819. }
  1820. context.fillStyle = backgroundColorHex;
  1821. context.fillRect(0, 0, canvasWidth, canvasHeight);
  1822. context.font = canvasWidth / 2.7 + 'px Arial';
  1823. context.textAlign = 'center';
  1824. context.fillStyle = fontColorHex;
  1825. context.fillText(text, canvasCssWidth / 2, canvasCssHeight / 1.5);
  1826. return $canvas.get(0).toDataURL();
  1827. },
  1828. /**
  1829. * getImageUrl
  1830. *
  1831. * @memberOf common
  1832. * @name common#getImageUrl
  1833. * @method
  1834. *
  1835. * @param ds
  1836. * @param pk
  1837. * @param size
  1838. * @param bustCache
  1839. * @return {string}
  1840. */
  1841. getImageUrl: function (ds, pk, size, bustCache) {
  1842. var url = ds.getBaseUrl() + pk + '?mimeType=image/jpeg';
  1843. if (size && size != 'orig') {
  1844. url += '&size=' + size;
  1845. }
  1846. if (bustCache) {
  1847. url += '&_bust=' + new Date().getTime();
  1848. }
  1849. return url;
  1850. },
  1851. /**
  1852. * getImageCDNUrl
  1853. *
  1854. * @memberOf common
  1855. * @name common#getImageCDNUrl
  1856. * @method
  1857. *
  1858. * @param settings
  1859. * @param groupId
  1860. * @param attachmentId
  1861. * @param size
  1862. * @return {string}
  1863. */
  1864. getImageCDNUrl: function (settings, groupId, attachmentId, size) {
  1865. // https://cheqroom-cdn.s3.amazonaws.com/app-staging/groups/nose/b00f1ae1-941c-11e3-9fc5-1040f389c0d4-M.jpg
  1866. var url = 'https://cheqroom-cdn.s3.amazonaws.com/' + settings.amazonBucket + '/groups/' + groupId + '/' + attachmentId;
  1867. if (size && size.length > 0) {
  1868. var parts = url.split('.');
  1869. var ext = attachmentId.indexOf('.') != -1 ? parts.pop() : '';
  1870. // pop off the extension, we'll change it
  1871. url = parts.join('.') + '-' + size + '.jpg'; // resized images are always jpg
  1872. }
  1873. return url;
  1874. },
  1875. getNoImage: function (size) {
  1876. var sizes = {
  1877. 'XS': 64,
  1878. 'S': 128,
  1879. 'M': 256,
  1880. 'L': 512,
  1881. 'XL': 1024
  1882. };
  1883. var canvasWidth = sizes[size], canvasHeight = sizes[size], canvasCssWidth = canvasWidth, canvasCssHeight = canvasHeight;
  1884. var $canvas = $('<canvas />').attr({
  1885. width: canvasWidth,
  1886. height: canvasHeight
  1887. });
  1888. var context = $canvas.get(0).getContext('2d');
  1889. if (window.devicePixelRatio) {
  1890. $canvas.attr('width', canvasWidth * window.devicePixelRatio);
  1891. $canvas.attr('height', canvasHeight * window.devicePixelRatio);
  1892. $canvas.css('width', canvasCssWidth);
  1893. $canvas.css('height', canvasCssHeight);
  1894. context.scale(window.devicePixelRatio, window.devicePixelRatio);
  1895. }
  1896. context.fillStyle = 'rgba(255,255,255,0.5)';
  1897. context.fillRect(0, 0, canvasWidth, canvasHeight);
  1898. context.font = canvasWidth / 2 + 'px FontAwesome';
  1899. context.textAlign = 'center';
  1900. context.fillStyle = 'rgba(0,0,0,0.2)';
  1901. context.fillText(String.fromCharCode('0xf03e'), canvasCssWidth / 2, canvasCssHeight / 1.5);
  1902. return $canvas.get(0).toDataURL();
  1903. }
  1904. };
  1905. }(jquery);
  1906. common_attachment = function (moment) {
  1907. /**
  1908. * Provides attachment related helper methods
  1909. */
  1910. return {
  1911. /**
  1912. * getImgFileNameFromName
  1913. *
  1914. * @memberOf common
  1915. * @name common#getImgFileNameFromName
  1916. * @method
  1917. *
  1918. * @param name
  1919. * @return {string}
  1920. */
  1921. getImgFileNameFromName: function (name) {
  1922. if (name != null && name.length > 0) {
  1923. return name.split(' ').join('_').split('.').join('_') + '.jpg';
  1924. } else {
  1925. // upload 2014-03-10 at 11.41.45 am.png
  1926. return 'upload ' + moment().format('YYYY-MM-DD at hh:mm:ss a') + '.jpg';
  1927. }
  1928. },
  1929. /**
  1930. * makeFileNameJpg
  1931. *
  1932. * @memberOf common
  1933. * @name common#makeFileNameJpg
  1934. * @method
  1935. *
  1936. * @param name
  1937. * @return {string}
  1938. */
  1939. makeFileNameJpg: function (name) {
  1940. return name.indexOf('.') >= 0 ? name.substr(0, name.lastIndexOf('.')) + '.jpg' : name;
  1941. },
  1942. /**
  1943. * getFileNameFromUrl
  1944. *
  1945. * @memberOf common
  1946. * @name common#getFileNameFromUrl
  1947. * @method
  1948. *
  1949. * @param url
  1950. * @return {string}
  1951. */
  1952. getFileNameFromUrl: function (url) {
  1953. if (url) {
  1954. var m = url.toString().match(/.*\/(.+?)\./);
  1955. if (m && m.length > 1) {
  1956. return m[1];
  1957. }
  1958. }
  1959. return '';
  1960. }
  1961. };
  1962. }(moment);
  1963. common_inflection = function () {
  1964. /**
  1965. * STRING EXTENSIONS
  1966. */
  1967. /**
  1968. * pluralize
  1969. *
  1970. * @memberOf String
  1971. * @name String#pluralize
  1972. * @method
  1973. *
  1974. * @param {int} count
  1975. * @param {string} suffix
  1976. * @return {string}
  1977. */
  1978. String.prototype.pluralize = function (count, suffix) {
  1979. if (this == 'is' && count != 1) {
  1980. return 'are';
  1981. } else if (this == 'this') {
  1982. return count == 1 ? this : 'these';
  1983. } else if (this.endsWith('s')) {
  1984. suffix = suffix || 'es';
  1985. return count == 1 ? this : this + suffix;
  1986. } else if (this.endsWith('y')) {
  1987. suffix = suffix || 'ies';
  1988. return count == 1 ? this : this.substr(0, this.length - 1) + suffix;
  1989. } else {
  1990. suffix = suffix || 's';
  1991. return count == 1 ? this : this + suffix;
  1992. }
  1993. };
  1994. /**
  1995. * capitalize
  1996. *
  1997. * @memberOf String
  1998. * @name String#capitalize
  1999. * @method
  2000. *
  2001. * @param {Boolean} lower
  2002. * @return {string}
  2003. */
  2004. String.prototype.capitalize = function (lower) {
  2005. return (lower ? this.toLowerCase() : this).replace(/(?:^|\s)\S/g, function (a) {
  2006. return a.toUpperCase();
  2007. });
  2008. };
  2009. if (!String.prototype.startsWith) {
  2010. /**
  2011. * startsWith
  2012. *
  2013. * @memberOf String
  2014. * @name String#startsWith
  2015. * @method
  2016. *
  2017. * @param {string} str
  2018. * @return {Boolean}
  2019. */
  2020. String.prototype.startsWith = function (str) {
  2021. return this.indexOf(str) == 0;
  2022. };
  2023. }
  2024. if (!String.prototype.endsWith) {
  2025. /**
  2026. * endsWith
  2027. *
  2028. * @memberOf String
  2029. * @name String#endsWith
  2030. * @method
  2031. *
  2032. * @param {string} str
  2033. * @return {Boolean}
  2034. */
  2035. String.prototype.endsWith = function (str) {
  2036. if (this.length < str.length) {
  2037. return false;
  2038. } else {
  2039. return this.lastIndexOf(str) == this.length - str.length;
  2040. }
  2041. };
  2042. }
  2043. if (!String.prototype.truncate) {
  2044. /**
  2045. * truncate
  2046. *
  2047. * @memberOf String
  2048. * @name String#truncate
  2049. * @method
  2050. *
  2051. * @param {int} len
  2052. * @return {string}
  2053. */
  2054. String.prototype.truncate = function (len) {
  2055. len = len != null ? len : 25;
  2056. var re = this.match(RegExp('^.{0,' + len + '}[S]*'));
  2057. var l = re[0].length;
  2058. re = re[0].replace(/\s$/, '');
  2059. if (l < this.length)
  2060. re = re + '&hellip;';
  2061. return re;
  2062. };
  2063. }
  2064. if (!String.prototype.isValidUrl) {
  2065. /**
  2066. * isValidUrl
  2067. *
  2068. * @memberOf String
  2069. * @name String#isValidUrl
  2070. * @method
  2071. *
  2072. * @return {Boolean}
  2073. */
  2074. String.prototype.isValidUrl = function () {
  2075. var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
  2076. '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
  2077. '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
  2078. '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
  2079. '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
  2080. '(\\#[-a-z\\d_]*)?$', 'i');
  2081. // fragment locator
  2082. if (!pattern.test(this)) {
  2083. return false;
  2084. } else {
  2085. return true;
  2086. }
  2087. };
  2088. }
  2089. if (!String.prototype.hashCode) {
  2090. /**
  2091. * hashCode
  2092. * http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
  2093. *
  2094. * @memberOf String
  2095. * @name String#hashCode
  2096. * @method
  2097. *
  2098. * @return {string}
  2099. */
  2100. String.prototype.hashCode = function () {
  2101. var hash = 0, i, chr, len;
  2102. if (this.length == 0)
  2103. return hash;
  2104. for (i = 0, len = this.length; i < len; i++) {
  2105. chr = this.charCodeAt(i);
  2106. hash = (hash << 5) - hash + chr;
  2107. hash |= 0; // Convert to 32bit integer
  2108. }
  2109. return hash;
  2110. };
  2111. }
  2112. //http://stackoverflow.com/questions/286921/efficiently-replace-all-accented-characters-in-a-string
  2113. var Latinise = {};
  2114. Latinise.latin_map = {
  2115. '\xC1': 'A',
  2116. '\u0102': 'A',
  2117. '\u1EAE': 'A',
  2118. '\u1EB6': 'A',
  2119. '\u1EB0': 'A',
  2120. '\u1EB2': 'A',
  2121. '\u1EB4': 'A',
  2122. '\u01CD': 'A',
  2123. '\xC2': 'A',
  2124. '\u1EA4': 'A',
  2125. '\u1EAC': 'A',
  2126. '\u1EA6': 'A',
  2127. '\u1EA8': 'A',
  2128. '\u1EAA': 'A',
  2129. '\xC4': 'A',
  2130. '\u01DE': 'A',
  2131. '\u0226': 'A',
  2132. '\u01E0': 'A',
  2133. '\u1EA0': 'A',
  2134. '\u0200': 'A',
  2135. '\xC0': 'A',
  2136. '\u1EA2': 'A',
  2137. '\u0202': 'A',
  2138. '\u0100': 'A',
  2139. '\u0104': 'A',
  2140. '\xC5': 'A',
  2141. '\u01FA': 'A',
  2142. '\u1E00': 'A',
  2143. '\u023A': 'A',
  2144. '\xC3': 'A',
  2145. '\uA732': 'AA',
  2146. '\xC6': 'AE',
  2147. '\u01FC': 'AE',
  2148. '\u01E2': 'AE',
  2149. '\uA734': 'AO',
  2150. '\uA736': 'AU',
  2151. '\uA738': 'AV',
  2152. '\uA73A': 'AV',
  2153. '\uA73C': 'AY',
  2154. '\u1E02': 'B',
  2155. '\u1E04': 'B',
  2156. '\u0181': 'B',
  2157. '\u1E06': 'B',
  2158. '\u0243': 'B',
  2159. '\u0182': 'B',
  2160. '\u0106': 'C',
  2161. '\u010C': 'C',
  2162. '\xC7': 'C',
  2163. '\u1E08': 'C',
  2164. '\u0108': 'C',
  2165. '\u010A': 'C',
  2166. '\u0187': 'C',
  2167. '\u023B': 'C',
  2168. '\u010E': 'D',
  2169. '\u1E10': 'D',
  2170. '\u1E12': 'D',
  2171. '\u1E0A': 'D',
  2172. '\u1E0C': 'D',
  2173. '\u018A': 'D',
  2174. '\u1E0E': 'D',
  2175. '\u01F2': 'D',
  2176. '\u01C5': 'D',
  2177. '\u0110': 'D',
  2178. '\u018B': 'D',
  2179. '\u01F1': 'DZ',
  2180. '\u01C4': 'DZ',
  2181. '\xC9': 'E',
  2182. '\u0114': 'E',
  2183. '\u011A': 'E',
  2184. '\u0228': 'E',
  2185. '\u1E1C': 'E',
  2186. '\xCA': 'E',
  2187. '\u1EBE': 'E',
  2188. '\u1EC6': 'E',
  2189. '\u1EC0': 'E',
  2190. '\u1EC2': 'E',
  2191. '\u1EC4': 'E',
  2192. '\u1E18': 'E',
  2193. '\xCB': 'E',
  2194. '\u0116': 'E',
  2195. '\u1EB8': 'E',
  2196. '\u0204': 'E',
  2197. '\xC8': 'E',
  2198. '\u1EBA': 'E',
  2199. '\u0206': 'E',
  2200. '\u0112': 'E',
  2201. '\u1E16': 'E',
  2202. '\u1E14': 'E',
  2203. '\u0118': 'E',
  2204. '\u0246': 'E',
  2205. '\u1EBC': 'E',
  2206. '\u1E1A': 'E',
  2207. '\uA76A': 'ET',
  2208. '\u1E1E': 'F',
  2209. '\u0191': 'F',
  2210. '\u01F4': 'G',
  2211. '\u011E': 'G',
  2212. '\u01E6': 'G',
  2213. '\u0122': 'G',
  2214. '\u011C': 'G',
  2215. '\u0120': 'G',
  2216. '\u0193': 'G',
  2217. '\u1E20': 'G',
  2218. '\u01E4': 'G',
  2219. '\u1E2A': 'H',
  2220. '\u021E': 'H',
  2221. '\u1E28': 'H',
  2222. '\u0124': 'H',
  2223. '\u2C67': 'H',
  2224. '\u1E26': 'H',
  2225. '\u1E22': 'H',
  2226. '\u1E24': 'H',
  2227. '\u0126': 'H',
  2228. '\xCD': 'I',
  2229. '\u012C': 'I',
  2230. '\u01CF': 'I',
  2231. '\xCE': 'I',
  2232. '\xCF': 'I',
  2233. '\u1E2E': 'I',
  2234. '\u0130': 'I',
  2235. '\u1ECA': 'I',
  2236. '\u0208': 'I',
  2237. '\xCC': 'I',
  2238. '\u1EC8': 'I',
  2239. '\u020A': 'I',
  2240. '\u012A': 'I',
  2241. '\u012E': 'I',
  2242. '\u0197': 'I',
  2243. '\u0128': 'I',
  2244. '\u1E2C': 'I',
  2245. '\uA779': 'D',
  2246. '\uA77B': 'F',
  2247. '\uA77D': 'G',
  2248. '\uA782': 'R',
  2249. '\uA784': 'S',
  2250. '\uA786': 'T',
  2251. '\uA76C': 'IS',
  2252. '\u0134': 'J',
  2253. '\u0248': 'J',
  2254. '\u1E30': 'K',
  2255. '\u01E8': 'K',
  2256. '\u0136': 'K',
  2257. '\u2C69': 'K',
  2258. '\uA742': 'K',
  2259. '\u1E32': 'K',
  2260. '\u0198': 'K',
  2261. '\u1E34': 'K',
  2262. '\uA740': 'K',
  2263. '\uA744': 'K',
  2264. '\u0139': 'L',
  2265. '\u023D': 'L',
  2266. '\u013D': 'L',
  2267. '\u013B': 'L',
  2268. '\u1E3C': 'L',
  2269. '\u1E36': 'L',
  2270. '\u1E38': 'L',
  2271. '\u2C60': 'L',
  2272. '\uA748': 'L',
  2273. '\u1E3A': 'L',
  2274. '\u013F': 'L',
  2275. '\u2C62': 'L',
  2276. '\u01C8': 'L',
  2277. '\u0141': 'L',
  2278. '\u01C7': 'LJ',
  2279. '\u1E3E': 'M',
  2280. '\u1E40': 'M',
  2281. '\u1E42': 'M',
  2282. '\u2C6E': 'M',
  2283. '\u0143': 'N',
  2284. '\u0147': 'N',
  2285. '\u0145': 'N',
  2286. '\u1E4A': 'N',
  2287. '\u1E44': 'N',
  2288. '\u1E46': 'N',
  2289. '\u01F8': 'N',
  2290. '\u019D': 'N',
  2291. '\u1E48': 'N',
  2292. '\u0220': 'N',
  2293. '\u01CB': 'N',
  2294. '\xD1': 'N',
  2295. '\u01CA': 'NJ',
  2296. '\xD3': 'O',
  2297. '\u014E': 'O',
  2298. '\u01D1': 'O',
  2299. '\xD4': 'O',
  2300. '\u1ED0': 'O',
  2301. '\u1ED8': 'O',
  2302. '\u1ED2': 'O',
  2303. '\u1ED4': 'O',
  2304. '\u1ED6': 'O',
  2305. '\xD6': 'O',
  2306. '\u022A': 'O',
  2307. '\u022E': 'O',
  2308. '\u0230': 'O',
  2309. '\u1ECC': 'O',
  2310. '\u0150': 'O',
  2311. '\u020C': 'O',
  2312. '\xD2': 'O',
  2313. '\u1ECE': 'O',
  2314. '\u01A0': 'O',
  2315. '\u1EDA': 'O',
  2316. '\u1EE2': 'O',
  2317. '\u1EDC': 'O',
  2318. '\u1EDE': 'O',
  2319. '\u1EE0': 'O',
  2320. '\u020E': 'O',
  2321. '\uA74A': 'O',
  2322. '\uA74C': 'O',
  2323. '\u014C': 'O',
  2324. '\u1E52': 'O',
  2325. '\u1E50': 'O',
  2326. '\u019F': 'O',
  2327. '\u01EA': 'O',
  2328. '\u01EC': 'O',
  2329. '\xD8': 'O',
  2330. '\u01FE': 'O',
  2331. '\xD5': 'O',
  2332. '\u1E4C': 'O',
  2333. '\u1E4E': 'O',
  2334. '\u022C': 'O',
  2335. '\u01A2': 'OI',
  2336. '\uA74E': 'OO',
  2337. '\u0190': 'E',
  2338. '\u0186': 'O',
  2339. '\u0222': 'OU',
  2340. '\u1E54': 'P',
  2341. '\u1E56': 'P',
  2342. '\uA752': 'P',
  2343. '\u01A4': 'P',
  2344. '\uA754': 'P',
  2345. '\u2C63': 'P',
  2346. '\uA750': 'P',
  2347. '\uA758': 'Q',
  2348. '\uA756': 'Q',
  2349. '\u0154': 'R',
  2350. '\u0158': 'R',
  2351. '\u0156': 'R',
  2352. '\u1E58': 'R',
  2353. '\u1E5A': 'R',
  2354. '\u1E5C': 'R',
  2355. '\u0210': 'R',
  2356. '\u0212': 'R',
  2357. '\u1E5E': 'R',
  2358. '\u024C': 'R',
  2359. '\u2C64': 'R',
  2360. '\uA73E': 'C',
  2361. '\u018E': 'E',
  2362. '\u015A': 'S',
  2363. '\u1E64': 'S',
  2364. '\u0160': 'S',
  2365. '\u1E66': 'S',
  2366. '\u015E': 'S',
  2367. '\u015C': 'S',
  2368. '\u0218': 'S',
  2369. '\u1E60': 'S',
  2370. '\u1E62': 'S',
  2371. '\u1E68': 'S',
  2372. '\u0164': 'T',
  2373. '\u0162': 'T',
  2374. '\u1E70': 'T',
  2375. '\u021A': 'T',
  2376. '\u023E': 'T',
  2377. '\u1E6A': 'T',
  2378. '\u1E6C': 'T',
  2379. '\u01AC': 'T',
  2380. '\u1E6E': 'T',
  2381. '\u01AE': 'T',
  2382. '\u0166': 'T',
  2383. '\u2C6F': 'A',
  2384. '\uA780': 'L',
  2385. '\u019C': 'M',
  2386. '\u0245': 'V',
  2387. '\uA728': 'TZ',
  2388. '\xDA': 'U',
  2389. '\u016C': 'U',
  2390. '\u01D3': 'U',
  2391. '\xDB': 'U',
  2392. '\u1E76': 'U',
  2393. '\xDC': 'U',
  2394. '\u01D7': 'U',
  2395. '\u01D9': 'U',
  2396. '\u01DB': 'U',
  2397. '\u01D5': 'U',
  2398. '\u1E72': 'U',
  2399. '\u1EE4': 'U',
  2400. '\u0170': 'U',
  2401. '\u0214': 'U',
  2402. '\xD9': 'U',
  2403. '\u1EE6': 'U',
  2404. '\u01AF': 'U',
  2405. '\u1EE8': 'U',
  2406. '\u1EF0': 'U',
  2407. '\u1EEA': 'U',
  2408. '\u1EEC': 'U',
  2409. '\u1EEE': 'U',
  2410. '\u0216': 'U',
  2411. '\u016A': 'U',
  2412. '\u1E7A': 'U',
  2413. '\u0172': 'U',
  2414. '\u016E': 'U',
  2415. '\u0168': 'U',
  2416. '\u1E78': 'U',
  2417. '\u1E74': 'U',
  2418. '\uA75E': 'V',
  2419. '\u1E7E': 'V',
  2420. '\u01B2': 'V',
  2421. '\u1E7C': 'V',
  2422. '\uA760': 'VY',
  2423. '\u1E82': 'W',
  2424. '\u0174': 'W',
  2425. '\u1E84': 'W',
  2426. '\u1E86': 'W',
  2427. '\u1E88': 'W',
  2428. '\u1E80': 'W',
  2429. '\u2C72': 'W',
  2430. '\u1E8C': 'X',
  2431. '\u1E8A': 'X',
  2432. '\xDD': 'Y',
  2433. '\u0176': 'Y',
  2434. '\u0178': 'Y',
  2435. '\u1E8E': 'Y',
  2436. '\u1EF4': 'Y',
  2437. '\u1EF2': 'Y',
  2438. '\u01B3': 'Y',
  2439. '\u1EF6': 'Y',
  2440. '\u1EFE': 'Y',
  2441. '\u0232': 'Y',
  2442. '\u024E': 'Y',
  2443. '\u1EF8': 'Y',
  2444. '\u0179': 'Z',
  2445. '\u017D': 'Z',
  2446. '\u1E90': 'Z',
  2447. '\u2C6B': 'Z',
  2448. '\u017B': 'Z',
  2449. '\u1E92': 'Z',
  2450. '\u0224': 'Z',
  2451. '\u1E94': 'Z',
  2452. '\u01B5': 'Z',
  2453. '\u0132': 'IJ',
  2454. '\u0152': 'OE',
  2455. '\u1D00': 'A',
  2456. '\u1D01': 'AE',
  2457. '\u0299': 'B',
  2458. '\u1D03': 'B',
  2459. '\u1D04': 'C',
  2460. '\u1D05': 'D',
  2461. '\u1D07': 'E',
  2462. '\uA730': 'F',
  2463. '\u0262': 'G',
  2464. '\u029B': 'G',
  2465. '\u029C': 'H',
  2466. '\u026A': 'I',
  2467. '\u0281': 'R',
  2468. '\u1D0A': 'J',
  2469. '\u1D0B': 'K',
  2470. '\u029F': 'L',
  2471. '\u1D0C': 'L',
  2472. '\u1D0D': 'M',
  2473. '\u0274': 'N',
  2474. '\u1D0F': 'O',
  2475. '\u0276': 'OE',
  2476. '\u1D10': 'O',
  2477. '\u1D15': 'OU',
  2478. '\u1D18': 'P',
  2479. '\u0280': 'R',
  2480. '\u1D0E': 'N',
  2481. '\u1D19': 'R',
  2482. '\uA731': 'S',
  2483. '\u1D1B': 'T',
  2484. '\u2C7B': 'E',
  2485. '\u1D1A': 'R',
  2486. '\u1D1C': 'U',
  2487. '\u1D20': 'V',
  2488. '\u1D21': 'W',
  2489. '\u028F': 'Y',
  2490. '\u1D22': 'Z',
  2491. '\xE1': 'a',
  2492. '\u0103': 'a',
  2493. '\u1EAF': 'a',
  2494. '\u1EB7': 'a',
  2495. '\u1EB1': 'a',
  2496. '\u1EB3': 'a',
  2497. '\u1EB5': 'a',
  2498. '\u01CE': 'a',
  2499. '\xE2': 'a',
  2500. '\u1EA5': 'a',
  2501. '\u1EAD': 'a',
  2502. '\u1EA7': 'a',
  2503. '\u1EA9': 'a',
  2504. '\u1EAB': 'a',
  2505. '\xE4': 'a',
  2506. '\u01DF': 'a',
  2507. '\u0227': 'a',
  2508. '\u01E1': 'a',
  2509. '\u1EA1': 'a',
  2510. '\u0201': 'a',
  2511. '\xE0': 'a',
  2512. '\u1EA3': 'a',
  2513. '\u0203': 'a',
  2514. '\u0101': 'a',
  2515. '\u0105': 'a',
  2516. '\u1D8F': 'a',
  2517. '\u1E9A': 'a',
  2518. '\xE5': 'a',
  2519. '\u01FB': 'a',
  2520. '\u1E01': 'a',
  2521. '\u2C65': 'a',
  2522. '\xE3': 'a',
  2523. '\uA733': 'aa',
  2524. '\xE6': 'ae',
  2525. '\u01FD': 'ae',
  2526. '\u01E3': 'ae',
  2527. '\uA735': 'ao',
  2528. '\uA737': 'au',
  2529. '\uA739': 'av',
  2530. '\uA73B': 'av',
  2531. '\uA73D': 'ay',
  2532. '\u1E03': 'b',
  2533. '\u1E05': 'b',
  2534. '\u0253': 'b',
  2535. '\u1E07': 'b',
  2536. '\u1D6C': 'b',
  2537. '\u1D80': 'b',
  2538. '\u0180': 'b',
  2539. '\u0183': 'b',
  2540. '\u0275': 'o',
  2541. '\u0107': 'c',
  2542. '\u010D': 'c',
  2543. '\xE7': 'c',
  2544. '\u1E09': 'c',
  2545. '\u0109': 'c',
  2546. '\u0255': 'c',
  2547. '\u010B': 'c',
  2548. '\u0188': 'c',
  2549. '\u023C': 'c',
  2550. '\u010F': 'd',
  2551. '\u1E11': 'd',
  2552. '\u1E13': 'd',
  2553. '\u0221': 'd',
  2554. '\u1E0B': 'd',
  2555. '\u1E0D': 'd',
  2556. '\u0257': 'd',
  2557. '\u1D91': 'd',
  2558. '\u1E0F': 'd',
  2559. '\u1D6D': 'd',
  2560. '\u1D81': 'd',
  2561. '\u0111': 'd',
  2562. '\u0256': 'd',
  2563. '\u018C': 'd',
  2564. '\u0131': 'i',
  2565. '\u0237': 'j',
  2566. '\u025F': 'j',
  2567. '\u0284': 'j',
  2568. '\u01F3': 'dz',
  2569. '\u01C6': 'dz',
  2570. '\xE9': 'e',
  2571. '\u0115': 'e',
  2572. '\u011B': 'e',
  2573. '\u0229': 'e',
  2574. '\u1E1D': 'e',
  2575. '\xEA': 'e',
  2576. '\u1EBF': 'e',
  2577. '\u1EC7': 'e',
  2578. '\u1EC1': 'e',
  2579. '\u1EC3': 'e',
  2580. '\u1EC5': 'e',
  2581. '\u1E19': 'e',
  2582. '\xEB': 'e',
  2583. '\u0117': 'e',
  2584. '\u1EB9': 'e',
  2585. '\u0205': 'e',
  2586. '\xE8': 'e',
  2587. '\u1EBB': 'e',
  2588. '\u0207': 'e',
  2589. '\u0113': 'e',
  2590. '\u1E17': 'e',
  2591. '\u1E15': 'e',
  2592. '\u2C78': 'e',
  2593. '\u0119': 'e',
  2594. '\u1D92': 'e',
  2595. '\u0247': 'e',
  2596. '\u1EBD': 'e',
  2597. '\u1E1B': 'e',
  2598. '\uA76B': 'et',
  2599. '\u1E1F': 'f',
  2600. '\u0192': 'f',
  2601. '\u1D6E': 'f',
  2602. '\u1D82': 'f',
  2603. '\u01F5': 'g',
  2604. '\u011F': 'g',
  2605. '\u01E7': 'g',
  2606. '\u0123': 'g',
  2607. '\u011D': 'g',
  2608. '\u0121': 'g',
  2609. '\u0260': 'g',
  2610. '\u1E21': 'g',
  2611. '\u1D83': 'g',
  2612. '\u01E5': 'g',
  2613. '\u1E2B': 'h',
  2614. '\u021F': 'h',
  2615. '\u1E29': 'h',
  2616. '\u0125': 'h',
  2617. '\u2C68': 'h',
  2618. '\u1E27': 'h',
  2619. '\u1E23': 'h',
  2620. '\u1E25': 'h',
  2621. '\u0266': 'h',
  2622. '\u1E96': 'h',
  2623. '\u0127': 'h',
  2624. '\u0195': 'hv',
  2625. '\xED': 'i',
  2626. '\u012D': 'i',
  2627. '\u01D0': 'i',
  2628. '\xEE': 'i',
  2629. '\xEF': 'i',
  2630. '\u1E2F': 'i',
  2631. '\u1ECB': 'i',
  2632. '\u0209': 'i',
  2633. '\xEC': 'i',
  2634. '\u1EC9': 'i',
  2635. '\u020B': 'i',
  2636. '\u012B': 'i',
  2637. '\u012F': 'i',
  2638. '\u1D96': 'i',
  2639. '\u0268': 'i',
  2640. '\u0129': 'i',
  2641. '\u1E2D': 'i',
  2642. '\uA77A': 'd',
  2643. '\uA77C': 'f',
  2644. '\u1D79': 'g',
  2645. '\uA783': 'r',
  2646. '\uA785': 's',
  2647. '\uA787': 't',
  2648. '\uA76D': 'is',
  2649. '\u01F0': 'j',
  2650. '\u0135': 'j',
  2651. '\u029D': 'j',
  2652. '\u0249': 'j',
  2653. '\u1E31': 'k',
  2654. '\u01E9': 'k',
  2655. '\u0137': 'k',
  2656. '\u2C6A': 'k',
  2657. '\uA743': 'k',
  2658. '\u1E33': 'k',
  2659. '\u0199': 'k',
  2660. '\u1E35': 'k',
  2661. '\u1D84': 'k',
  2662. '\uA741': 'k',
  2663. '\uA745': 'k',
  2664. '\u013A': 'l',
  2665. '\u019A': 'l',
  2666. '\u026C': 'l',
  2667. '\u013E': 'l',
  2668. '\u013C': 'l',
  2669. '\u1E3D': 'l',
  2670. '\u0234': 'l',
  2671. '\u1E37': 'l',
  2672. '\u1E39': 'l',
  2673. '\u2C61': 'l',
  2674. '\uA749': 'l',
  2675. '\u1E3B': 'l',
  2676. '\u0140': 'l',
  2677. '\u026B': 'l',
  2678. '\u1D85': 'l',
  2679. '\u026D': 'l',
  2680. '\u0142': 'l',
  2681. '\u01C9': 'lj',
  2682. '\u017F': 's',
  2683. '\u1E9C': 's',
  2684. '\u1E9B': 's',
  2685. '\u1E9D': 's',
  2686. '\u1E3F': 'm',
  2687. '\u1E41': 'm',
  2688. '\u1E43': 'm',
  2689. '\u0271': 'm',
  2690. '\u1D6F': 'm',
  2691. '\u1D86': 'm',
  2692. '\u0144': 'n',
  2693. '\u0148': 'n',
  2694. '\u0146': 'n',
  2695. '\u1E4B': 'n',
  2696. '\u0235': 'n',
  2697. '\u1E45': 'n',
  2698. '\u1E47': 'n',
  2699. '\u01F9': 'n',
  2700. '\u0272': 'n',
  2701. '\u1E49': 'n',
  2702. '\u019E': 'n',
  2703. '\u1D70': 'n',
  2704. '\u1D87': 'n',
  2705. '\u0273': 'n',
  2706. '\xF1': 'n',
  2707. '\u01CC': 'nj',
  2708. '\xF3': 'o',
  2709. '\u014F': 'o',
  2710. '\u01D2': 'o',
  2711. '\xF4': 'o',
  2712. '\u1ED1': 'o',
  2713. '\u1ED9': 'o',
  2714. '\u1ED3': 'o',
  2715. '\u1ED5': 'o',
  2716. '\u1ED7': 'o',
  2717. '\xF6': 'o',
  2718. '\u022B': 'o',
  2719. '\u022F': 'o',
  2720. '\u0231': 'o',
  2721. '\u1ECD': 'o',
  2722. '\u0151': 'o',
  2723. '\u020D': 'o',
  2724. '\xF2': 'o',
  2725. '\u1ECF': 'o',
  2726. '\u01A1': 'o',
  2727. '\u1EDB': 'o',
  2728. '\u1EE3': 'o',
  2729. '\u1EDD': 'o',
  2730. '\u1EDF': 'o',
  2731. '\u1EE1': 'o',
  2732. '\u020F': 'o',
  2733. '\uA74B': 'o',
  2734. '\uA74D': 'o',
  2735. '\u2C7A': 'o',
  2736. '\u014D': 'o',
  2737. '\u1E53': 'o',
  2738. '\u1E51': 'o',
  2739. '\u01EB': 'o',
  2740. '\u01ED': 'o',
  2741. '\xF8': 'o',
  2742. '\u01FF': 'o',
  2743. '\xF5': 'o',
  2744. '\u1E4D': 'o',
  2745. '\u1E4F': 'o',
  2746. '\u022D': 'o',
  2747. '\u01A3': 'oi',
  2748. '\uA74F': 'oo',
  2749. '\u025B': 'e',
  2750. '\u1D93': 'e',
  2751. '\u0254': 'o',
  2752. '\u1D97': 'o',
  2753. '\u0223': 'ou',
  2754. '\u1E55': 'p',
  2755. '\u1E57': 'p',
  2756. '\uA753': 'p',
  2757. '\u01A5': 'p',
  2758. '\u1D71': 'p',
  2759. '\u1D88': 'p',
  2760. '\uA755': 'p',
  2761. '\u1D7D': 'p',
  2762. '\uA751': 'p',
  2763. '\uA759': 'q',
  2764. '\u02A0': 'q',
  2765. '\u024B': 'q',
  2766. '\uA757': 'q',
  2767. '\u0155': 'r',
  2768. '\u0159': 'r',
  2769. '\u0157': 'r',
  2770. '\u1E59': 'r',
  2771. '\u1E5B': 'r',
  2772. '\u1E5D': 'r',
  2773. '\u0211': 'r',
  2774. '\u027E': 'r',
  2775. '\u1D73': 'r',
  2776. '\u0213': 'r',
  2777. '\u1E5F': 'r',
  2778. '\u027C': 'r',
  2779. '\u1D72': 'r',
  2780. '\u1D89': 'r',
  2781. '\u024D': 'r',
  2782. '\u027D': 'r',
  2783. '\u2184': 'c',
  2784. '\uA73F': 'c',
  2785. '\u0258': 'e',
  2786. '\u027F': 'r',
  2787. '\u015B': 's',
  2788. '\u1E65': 's',
  2789. '\u0161': 's',
  2790. '\u1E67': 's',
  2791. '\u015F': 's',
  2792. '\u015D': 's',
  2793. '\u0219': 's',
  2794. '\u1E61': 's',
  2795. '\u1E63': 's',
  2796. '\u1E69': 's',
  2797. '\u0282': 's',
  2798. '\u1D74': 's',
  2799. '\u1D8A': 's',
  2800. '\u023F': 's',
  2801. '\u0261': 'g',
  2802. '\u1D11': 'o',
  2803. '\u1D13': 'o',
  2804. '\u1D1D': 'u',
  2805. '\u0165': 't',
  2806. '\u0163': 't',
  2807. '\u1E71': 't',
  2808. '\u021B': 't',
  2809. '\u0236': 't',
  2810. '\u1E97': 't',
  2811. '\u2C66': 't',
  2812. '\u1E6B': 't',
  2813. '\u1E6D': 't',
  2814. '\u01AD': 't',
  2815. '\u1E6F': 't',
  2816. '\u1D75': 't',
  2817. '\u01AB': 't',
  2818. '\u0288': 't',
  2819. '\u0167': 't',
  2820. '\u1D7A': 'th',
  2821. '\u0250': 'a',
  2822. '\u1D02': 'ae',
  2823. '\u01DD': 'e',
  2824. '\u1D77': 'g',
  2825. '\u0265': 'h',
  2826. '\u02AE': 'h',
  2827. '\u02AF': 'h',
  2828. '\u1D09': 'i',
  2829. '\u029E': 'k',
  2830. '\uA781': 'l',
  2831. '\u026F': 'm',
  2832. '\u0270': 'm',
  2833. '\u1D14': 'oe',
  2834. '\u0279': 'r',
  2835. '\u027B': 'r',
  2836. '\u027A': 'r',
  2837. '\u2C79': 'r',
  2838. '\u0287': 't',
  2839. '\u028C': 'v',
  2840. '\u028D': 'w',
  2841. '\u028E': 'y',
  2842. '\uA729': 'tz',
  2843. '\xFA': 'u',
  2844. '\u016D': 'u',
  2845. '\u01D4': 'u',
  2846. '\xFB': 'u',
  2847. '\u1E77': 'u',
  2848. '\xFC': 'u',
  2849. '\u01D8': 'u',
  2850. '\u01DA': 'u',
  2851. '\u01DC': 'u',
  2852. '\u01D6': 'u',
  2853. '\u1E73': 'u',
  2854. '\u1EE5': 'u',
  2855. '\u0171': 'u',
  2856. '\u0215': 'u',
  2857. '\xF9': 'u',
  2858. '\u1EE7': 'u',
  2859. '\u01B0': 'u',
  2860. '\u1EE9': 'u',
  2861. '\u1EF1': 'u',
  2862. '\u1EEB': 'u',
  2863. '\u1EED': 'u',
  2864. '\u1EEF': 'u',
  2865. '\u0217': 'u',
  2866. '\u016B': 'u',
  2867. '\u1E7B': 'u',
  2868. '\u0173': 'u',
  2869. '\u1D99': 'u',
  2870. '\u016F': 'u',
  2871. '\u0169': 'u',
  2872. '\u1E79': 'u',
  2873. '\u1E75': 'u',
  2874. '\u1D6B': 'ue',
  2875. '\uA778': 'um',
  2876. '\u2C74': 'v',
  2877. '\uA75F': 'v',
  2878. '\u1E7F': 'v',
  2879. '\u028B': 'v',
  2880. '\u1D8C': 'v',
  2881. '\u2C71': 'v',
  2882. '\u1E7D': 'v',
  2883. '\uA761': 'vy',
  2884. '\u1E83': 'w',
  2885. '\u0175': 'w',
  2886. '\u1E85': 'w',
  2887. '\u1E87': 'w',
  2888. '\u1E89': 'w',
  2889. '\u1E81': 'w',
  2890. '\u2C73': 'w',
  2891. '\u1E98': 'w',
  2892. '\u1E8D': 'x',
  2893. '\u1E8B': 'x',
  2894. '\u1D8D': 'x',
  2895. '\xFD': 'y',
  2896. '\u0177': 'y',
  2897. '\xFF': 'y',
  2898. '\u1E8F': 'y',
  2899. '\u1EF5': 'y',
  2900. '\u1EF3': 'y',
  2901. '\u01B4': 'y',
  2902. '\u1EF7': 'y',
  2903. '\u1EFF': 'y',
  2904. '\u0233': 'y',
  2905. '\u1E99': 'y',
  2906. '\u024F': 'y',
  2907. '\u1EF9': 'y',
  2908. '\u017A': 'z',
  2909. '\u017E': 'z',
  2910. '\u1E91': 'z',
  2911. '\u0291': 'z',
  2912. '\u2C6C': 'z',
  2913. '\u017C': 'z',
  2914. '\u1E93': 'z',
  2915. '\u0225': 'z',
  2916. '\u1E95': 'z',
  2917. '\u1D76': 'z',
  2918. '\u1D8E': 'z',
  2919. '\u0290': 'z',
  2920. '\u01B6': 'z',
  2921. '\u0240': 'z',
  2922. '\uFB00': 'ff',
  2923. '\uFB03': 'ffi',
  2924. '\uFB04': 'ffl',
  2925. '\uFB01': 'fi',
  2926. '\uFB02': 'fl',
  2927. '\u0133': 'ij',
  2928. '\u0153': 'oe',
  2929. '\uFB06': 'st',
  2930. '\u2090': 'a',
  2931. '\u2091': 'e',
  2932. '\u1D62': 'i',
  2933. '\u2C7C': 'j',
  2934. '\u2092': 'o',
  2935. '\u1D63': 'r',
  2936. '\u1D64': 'u',
  2937. '\u1D65': 'v',
  2938. '\u2093': 'x'
  2939. };
  2940. /**
  2941. * latinise
  2942. *
  2943. * @memberOf String
  2944. * @name String#latinise
  2945. * @method
  2946. *
  2947. * @return {string}
  2948. */
  2949. String.prototype.latinise = function () {
  2950. return this.replace(/[^A-Za-z0-9\[\] ]/g, function (a) {
  2951. return Latinise.latin_map[a] || a;
  2952. });
  2953. };
  2954. /**
  2955. * latinize
  2956. *
  2957. * @memberOf String
  2958. * @name String#latinize
  2959. * @method
  2960. *
  2961. * @return {string}
  2962. */
  2963. String.prototype.latinize = String.prototype.latinise;
  2964. /**
  2965. * isLatin
  2966. *
  2967. * @memberOf String
  2968. * @name String#isLatin
  2969. * @method
  2970. *
  2971. * @return {Boolean}
  2972. */
  2973. String.prototype.isLatin = function () {
  2974. return this == this.latinise();
  2975. };
  2976. /**
  2977. * OnlyAlphaNumSpaceAndUnderscore
  2978. *
  2979. * @memberOf String
  2980. * @name String#OnlyAlphaNumSpaceAndUnderscore
  2981. * @method
  2982. *
  2983. */
  2984. String.prototype.OnlyAlphaNumSpaceAndUnderscore = function () {
  2985. // \s Matches a single white space character, including space, tab, form feed, line feed and other Unicode spaces.
  2986. // \W Matches any character that is not a word character from the basic Latin alphabet. Equivalent to [^A-Za-z0-9_]
  2987. // Preceding or trailing whitespaces are removed, and words are also latinised
  2988. return $.trim(this).toLowerCase().replace(/[\s-]+/g, '_').latinise().replace(/[\W]/g, '');
  2989. };
  2990. /**
  2991. * OnlyAlphaNumSpaceUnderscoreAndDot
  2992. *
  2993. * @memberOf String
  2994. * @name String#OnlyAlphaNumSpaceUnderscoreAndDot
  2995. * @method
  2996. *
  2997. */
  2998. String.prototype.OnlyAlphaNumSpaceUnderscoreAndDot = function () {
  2999. // \s Matches a single white space character, including space, tab, form feed, line feed and other Unicode spaces.
  3000. // \W Matches any character that is not a word character from the basic Latin alphabet. Equivalent to [^A-Za-z0-9_]
  3001. // Preceding or trailing whitespaces are removed, and words are also latinised
  3002. return $.trim(this).toLowerCase().replace(/[\s-]+/g, '_').latinise().replace(/[^a-z0-9_\.]/g, '');
  3003. };
  3004. if (!String.prototype.NoWhiteSpaceInWord) {
  3005. /**
  3006. * NoWhiteSpaceInWord
  3007. *
  3008. * @memberOf String
  3009. * @name String#NoWhiteSpaceInWord
  3010. * @method
  3011. * @returns {string}
  3012. */
  3013. String.prototype.NoWhiteSpaceInWord = function () {
  3014. return this.replace(/[\s]+/g, '');
  3015. };
  3016. }
  3017. if (!String.prototype.addLeadingZero) {
  3018. /**
  3019. * addLeadingZero adds zeros in front of a number
  3020. * http://stackoverflow.com/questions/6466135/adding-extra-zeros-in-front-of-a-number-using-jquery
  3021. * ex: 5.pad(3) --> 005
  3022. *
  3023. * @param {string} str
  3024. * @param {Number} max
  3025. * @return {string}
  3026. */
  3027. String.prototype.addLeadingZero = function (max) {
  3028. var str = this.toString();
  3029. return str.length < max ? ('0' + str).addLeadingZero(max) : str;
  3030. };
  3031. }
  3032. /**
  3033. * Pad a number with leading zeros f.e. "5".lpad('0',2) -> 005
  3034. * @param padString
  3035. * @param length
  3036. * @return {string}
  3037. */
  3038. String.prototype.lpad = function (padString, length) {
  3039. var str = this;
  3040. while (str.length < length)
  3041. str = padString + str;
  3042. return str;
  3043. };
  3044. // trimLeft/trimRight polyfill
  3045. //https://gist.github.com/eliperelman/1036520
  3046. if (!String.prototype.trimLeft) {
  3047. String.prototype.trimLeft = function () {
  3048. return this.replace(/^\s+/, '');
  3049. };
  3050. }
  3051. if (!String.prototype.trimRight) {
  3052. String.prototype.trimRight = function () {
  3053. return this.replace(/\s+$/, '');
  3054. };
  3055. }
  3056. /**
  3057. * NUMBER EXTENSIONS
  3058. */
  3059. if (!Number.prototype.between) {
  3060. /**
  3061. * between
  3062. *
  3063. * @memberOf Number
  3064. * @name Number#between
  3065. * @method
  3066. *
  3067. * @param {int} a
  3068. * @param {int} b
  3069. * @return {Boolean}
  3070. */
  3071. Number.prototype.between = function (a, b) {
  3072. var min = Math.min(a, b), max = Math.max(a, b);
  3073. return this >= min && this <= max;
  3074. };
  3075. }
  3076. /**
  3077. * ARRAY EXTENSTIONS
  3078. */
  3079. if (!Array.prototype.joinOther) {
  3080. /**
  3081. * joinOther
  3082. *
  3083. * Makes a friendly joined list of strings
  3084. * constrained to a certain maxLength
  3085. * where the text would be:
  3086. * Kit 1, Kit2 +3 other
  3087. * or
  3088. * Kit 1 +4 other (if no params were passed)
  3089. *
  3090. * @memberOf Array
  3091. * @name Array#joinOther
  3092. * @method
  3093. *
  3094. * @param maxLength {int} 30
  3095. * @param sep {string} ", "
  3096. * @param other {string} "other"
  3097. */
  3098. Array.prototype.joinOther = function (maxLength, sep, other) {
  3099. // If we only have 1 item, no need to join anything
  3100. if (this.length < 2) {
  3101. return this.join(sep);
  3102. }
  3103. sep = sep || ', ';
  3104. other = other || 'other';
  3105. // Take the minimum length if no maxLength was passed
  3106. if (!maxLength || maxLength < 0) {
  3107. maxLength = 1;
  3108. }
  3109. // Keep popping off entries in the array
  3110. // until there's only one left, or until
  3111. // the joined text is shorter than maxLength
  3112. var copy = this.slice(0);
  3113. var joined = copy.join(sep);
  3114. while (copy.length > 1 && joined.length > maxLength) {
  3115. copy.pop();
  3116. joined = copy.join(sep);
  3117. }
  3118. var numOther = this.length - copy.length;
  3119. if (numOther > 0) {
  3120. joined += ' +' + numOther + ' ' + other;
  3121. }
  3122. return joined;
  3123. };
  3124. }
  3125. if (!Array.prototype.joinAdvanced) {
  3126. /**
  3127. * Special join method
  3128. * ['a','a','a'].specialJoin(',', 'and') -> 'a, a and a'
  3129. * http://stackoverflow.com/questions/15069587/is-there-a-way-to-join-the-elements-in-an-js-array-but-let-the-last-separator-b
  3130. *
  3131. * @param {string} sep
  3132. * @param {string} sepLast
  3133. */
  3134. Array.prototype.joinAdvanced = function (sep, sepLast) {
  3135. var arr = this.slice(0);
  3136. var outStr = '';
  3137. if (arr.length === 1) {
  3138. outStr = arr[0];
  3139. } else if (arr.length === 2) {
  3140. //joins all with "and" but no commas
  3141. //example: "bob and sam"
  3142. outStr = arr.join(sepLast);
  3143. } else if (arr.length > 2) {
  3144. //joins all with commas, but last one gets ", and" (oxford comma!)
  3145. //example: "bob, joe, and sam"
  3146. outStr = arr.slice(0, -1).join(sep) + sepLast + arr.slice(-1);
  3147. }
  3148. return outStr;
  3149. };
  3150. }
  3151. if (!Array.prototype.find) {
  3152. /**
  3153. * Returns a value in the array, if an element in the array
  3154. * satisfies the provided testing function.
  3155. * Otherwise undefined is returned.
  3156. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
  3157. *
  3158. * @param {function} function to contains check to find item
  3159. * @return {object}
  3160. */
  3161. Array.prototype.find = function (predicate) {
  3162. if (this === null) {
  3163. throw new TypeError('Array.prototype.find called on null or undefined');
  3164. }
  3165. if (typeof predicate !== 'function') {
  3166. throw new TypeError('predicate must be a function');
  3167. }
  3168. var list = Object(this);
  3169. var length = list.length >>> 0;
  3170. var thisArg = arguments[1];
  3171. var value;
  3172. for (var i = 0; i < length; i++) {
  3173. value = list[i];
  3174. if (predicate.call(thisArg, value, i, list)) {
  3175. return value;
  3176. }
  3177. }
  3178. return undefined;
  3179. };
  3180. }
  3181. if (!Object.values) {
  3182. Object.values = function (obj) {
  3183. var vals = [];
  3184. for (var key in obj) {
  3185. if (obj.hasOwnProperty(key)) {
  3186. vals.push(obj[key]);
  3187. }
  3188. }
  3189. return vals;
  3190. };
  3191. }
  3192. }();
  3193. common_validation = {
  3194. /**
  3195. * isValidEmail
  3196. * @memberOf common
  3197. * @name common#isValidEmail
  3198. * @method
  3199. * @param {string} email
  3200. * @return {Boolean}
  3201. */
  3202. isValidEmail: function (email) {
  3203. var re = /^([\w-\+]+(?:\.[\w-\+]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,}(?:\.[a-z]{2})?)$/i;
  3204. return re.test(email);
  3205. },
  3206. /**
  3207. * isFreeEmail
  3208. * @memberOf common
  3209. * @name common#isFreeEmail
  3210. * @method
  3211. * @param email
  3212. * @returns {boolean}
  3213. */
  3214. isFreeEmail: function (email) {
  3215. var re = /^([\w-\+]+(?:\.[\w-\+]+)*)@(?!gmail\.com)(?!yahoo\.com)(?!hotmail\.com)((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
  3216. return !re.test(email);
  3217. },
  3218. /**
  3219. * isValidPhone
  3220. * @memberOf common
  3221. * @name common#isValidPhone
  3222. * @method
  3223. * @param {string} phone
  3224. * @return {Boolean}
  3225. */
  3226. isValidPhone: function (phone) {
  3227. var isnum = /^\d{9,}$/.test(phone);
  3228. if (isnum) {
  3229. return true;
  3230. }
  3231. var m = phone.match(/^[\s()+-]*([0-9][\s()+-]*){10,20}(( x| ext)\d{1,5}){0,1}$/);
  3232. return m != null && m.length > 0;
  3233. },
  3234. /**
  3235. * isValidURL
  3236. * @memberOf common
  3237. * @name common#isValidURL
  3238. * @method
  3239. * @param {string} url
  3240. * @returns {boolean}
  3241. */
  3242. isValidURL: function (url) {
  3243. // http://stackoverflow.com/questions/1303872/trying-to-validate-url-using-javascript
  3244. var re = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
  3245. return re.test(url);
  3246. },
  3247. /**
  3248. * isValidPassword
  3249. * @memberOf common
  3250. * @name common#isValidPassword
  3251. * @method
  3252. * @param password
  3253. * @returns {boolean}
  3254. */
  3255. isValidPassword: function (password) {
  3256. var hasDigit = password.match(/[0-9]/);
  3257. return password.length >= 4 && hasDigit;
  3258. }
  3259. };
  3260. common_utils = function ($) {
  3261. var utils = {};
  3262. /**
  3263. * Sorts a given fields dict based on a list of fieldDefs
  3264. * @memberOf utils
  3265. * @name utils#sortFields
  3266. * @param {object} fields
  3267. * @param {Array} fieldDefs
  3268. * @param {Boolean} onlyFormFields (optional)
  3269. * @param {int} limit (optional)
  3270. * @return {Array} list of dicts
  3271. */
  3272. utils.sortFields = function (fields, fieldDefs, onlyFormFields, limit) {
  3273. var sortedFields = [], fieldDef = null, fieldValue = null, fieldText = null;
  3274. // Work on copy of fieldDefs array
  3275. fieldDefs = fieldDefs.slice();
  3276. // Return only form field definitions?
  3277. if (onlyFormFields != null) {
  3278. fieldDefs = fieldDefs.filter(function (def) {
  3279. return def.form == onlyFormFields;
  3280. });
  3281. }
  3282. // Create a Field dict for each field definition
  3283. for (var i = 0; i < fieldDefs.length; i++) {
  3284. fieldDef = fieldDefs[i];
  3285. fieldValue = fields[fieldDef.name];
  3286. sortedFields.push($.extend({ value: fieldValue }, fieldDef));
  3287. if (limit != null && sortedFields.length >= limit) {
  3288. break;
  3289. }
  3290. }
  3291. return sortedFields;
  3292. };
  3293. /**
  3294. * Stringifies an object while first sorting the keys
  3295. * Ensures we can use it to check object equality
  3296. * http://stackoverflow.com/questions/16167581/sort-object-properties-and-json-stringify
  3297. * @memberOf utils
  3298. * @name utils#stringifyOrdered
  3299. * @method
  3300. * @param obj
  3301. * @return {string}
  3302. */
  3303. utils.stringifyOrdered = function (obj) {
  3304. keys = [];
  3305. if (obj) {
  3306. for (var key in obj) {
  3307. keys.push(key);
  3308. }
  3309. }
  3310. keys.sort();
  3311. var tObj = {};
  3312. var key;
  3313. for (var index in keys) {
  3314. key = keys[index];
  3315. tObj[key] = obj[key];
  3316. }
  3317. return JSON.stringify(tObj);
  3318. };
  3319. /**
  3320. * Checks if two objects are equal
  3321. * Mimics behaviour from http://underscorejs.org/#isEqual
  3322. * @memberOf utils
  3323. * @name utils#areEqual
  3324. * @method
  3325. * @param obj1
  3326. * @param obj2
  3327. * @return {boolean}
  3328. */
  3329. utils.areEqual = function (obj1, obj2) {
  3330. return utils.stringifyOrdered(obj1 || {}) == utils.stringifyOrdered(obj2 || {});
  3331. };
  3332. /**
  3333. * Turns an integer into a compact text to show in a badge
  3334. * @memberOf utils
  3335. * @name utils#badgeify
  3336. * @method
  3337. * @param {int} count
  3338. * @return {string}
  3339. */
  3340. utils.badgeify = function (count) {
  3341. if (count > 100) {
  3342. return '99+';
  3343. } else if (count > 10) {
  3344. return '10+';
  3345. } else if (count > 0) {
  3346. return '' + count;
  3347. } else {
  3348. return '';
  3349. }
  3350. };
  3351. /**
  3352. * Turns a firstName lastName into a fistname.lastname login
  3353. * @memberOf utils
  3354. * @name utils#getLoginName
  3355. * @method
  3356. * @param {string} firstName
  3357. * @param {string} lastName
  3358. * @return {string}
  3359. */
  3360. utils.getLoginName = function (firstName, lastName) {
  3361. var patt = /[\s-]*/gim;
  3362. return firstName.latinise().toLowerCase().replace(patt, '') + '.' + lastName.latinise().toLowerCase().replace(patt, '');
  3363. };
  3364. /**
  3365. * Gets a parameter from the querystring (returns null if not found)
  3366. * @memberOf utils
  3367. * @name utils#getUrlParam
  3368. * @method
  3369. * @param {string} name
  3370. * @param {string} default
  3371. * @return {string}
  3372. */
  3373. utils.getUrlParam = function (name, def) {
  3374. name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
  3375. var regexS = '[\\?&]' + name + '=([^&#]*)';
  3376. var regex = new RegExp(regexS);
  3377. var results = regex.exec(window.location.href);
  3378. return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : def;
  3379. };
  3380. // jQuery extension method
  3381. $.urlParam = utils.getUrlParam;
  3382. /**
  3383. * getParsedLines
  3384. * @memberOf utils
  3385. * @name utils#getParsedLines
  3386. * @method
  3387. * @param {string} text
  3388. * @return {Array}
  3389. */
  3390. utils.getParsedLines = function (text) {
  3391. if (text && text.length > 0) {
  3392. var customs = text.split(/\s*([,;\r\n]+|\s\s)\s*/);
  3393. return customs.filter(function (cust, idx, arr) {
  3394. return cust.length > 0 && cust.indexOf(',') < 0 && cust.indexOf(';') < 0 && $.trim(cust).length > 0 && arr.indexOf(cust) >= idx;
  3395. });
  3396. } else {
  3397. return [];
  3398. }
  3399. };
  3400. /**
  3401. * getFriendlyFileName
  3402. * @memberOf utils
  3403. * @name utils#getFriendlyFileName
  3404. * @method
  3405. * @param {string} name
  3406. * @return {string}
  3407. */
  3408. utils.getFriendlyFileName = function (name) {
  3409. return name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
  3410. };
  3411. return utils;
  3412. }(jquery);
  3413. common_slimdown = function () {
  3414. /**
  3415. * Javascript version of https://gist.github.com/jbroadway/2836900
  3416. *
  3417. * Slimdown - A very basic regex-based Markdown parser. Supports the
  3418. * following elements (and can be extended via Slimdown::add_rule()):
  3419. *
  3420. * - Headers
  3421. * - Links
  3422. * - Bold
  3423. * - Emphasis
  3424. * - Deletions
  3425. * - Quotes
  3426. * - Inline code
  3427. * - Blockquotes
  3428. * - Ordered/unordered lists
  3429. * - Horizontal rules
  3430. *
  3431. * Author: Johnny Broadway <johnny@johnnybroadway.com>
  3432. * Website: https://gist.github.com/jbroadway/2836900
  3433. * License: MIT
  3434. *
  3435. * @global
  3436. * @name Slimdown
  3437. */
  3438. var Slimdown = function () {
  3439. // Rules
  3440. this.rules = [
  3441. {
  3442. regex: /(#{1,6})\s(.*)/g,
  3443. replacement: header
  3444. },
  3445. // headers
  3446. {
  3447. regex: /!\[([^\[]+)\]\(([^\)]+)\)/g,
  3448. replacement: '<img src=\'$2\' alt=\'$1\'>'
  3449. },
  3450. // image
  3451. {
  3452. regex: /\[([^\[]+)\]\(([^\)]+)\)/g,
  3453. replacement: '<a href=\'$2\'>$1</a>'
  3454. },
  3455. // hyperlink
  3456. {
  3457. regex: /(\*\*|__)(.*?)\1/g,
  3458. replacement: '<strong>$2</strong>'
  3459. },
  3460. // bold
  3461. {
  3462. regex: /(\*|_)(.*?)\1/g,
  3463. replacement: '<em>$2</em>'
  3464. },
  3465. // emphasis
  3466. {
  3467. regex: /\~\~(.*?)\~\~/g,
  3468. replacement: '<del>$1</del>'
  3469. },
  3470. // del
  3471. {
  3472. regex: /\:\"(.*?)\"\:/g,
  3473. replacement: '<q>$1</q>'
  3474. },
  3475. // quote
  3476. {
  3477. regex: /`(.*?)`/g,
  3478. replacement: '<code>$1</code>'
  3479. },
  3480. // inline code
  3481. {
  3482. regex: /\n\*(.*)/g,
  3483. replacement: ulList
  3484. },
  3485. // ul lists
  3486. {
  3487. regex: /\n[0-9]+\.\s(.*)/g,
  3488. replacement: olList
  3489. },
  3490. // ol lists
  3491. {
  3492. regex: /\n(&gt;|\>)(.*)/g,
  3493. replacement: blockquote
  3494. },
  3495. // blockquotes
  3496. {
  3497. regex: /\n-{5,}/g,
  3498. replacement: '\n<hr />'
  3499. },
  3500. // horizontal rule
  3501. {
  3502. regex: /(?:[^\n]|\n(?! *\n))+/g,
  3503. replacement: para
  3504. },
  3505. // add paragraphs
  3506. {
  3507. regex: /<\/ul>\s?<ul>/g,
  3508. replacement: ''
  3509. },
  3510. // fix extra ul
  3511. {
  3512. regex: /<\/ol>\s?<ol>/g,
  3513. replacement: ''
  3514. },
  3515. // fix extra ol
  3516. {
  3517. regex: /<\/blockquote><blockquote>/g,
  3518. replacement: '\n'
  3519. } // fix extra blockquote
  3520. ];
  3521. // Add a rule.
  3522. this.addRule = function (regex, replacement) {
  3523. regex.global = true;
  3524. regex.multiline = false;
  3525. this.rules.push({
  3526. regex: regex,
  3527. replacement: replacement
  3528. });
  3529. };
  3530. // Render some Markdown into HTML.
  3531. this.render = function (text) {
  3532. text = '\n' + text + '\n';
  3533. text = autolink(text);
  3534. this.rules.forEach(function (rule) {
  3535. text = text.replace(rule.regex, rule.replacement);
  3536. });
  3537. return text.trim();
  3538. };
  3539. function para(text, line) {
  3540. var trimmed = ('' + text).trimLeft().trimRight();
  3541. if (/^<\/?(ul|ol|li|h|p|bl)/i.test(trimmed)) {
  3542. return trimmed;
  3543. }
  3544. trimmed = trimmed.replace(/\n/g, '<br />');
  3545. return '<p>' + trimmed + '</p>';
  3546. }
  3547. function ulList(text, item) {
  3548. return '\n<ul>\n\t<li>' + item.trim() + '</li>\n</ul>';
  3549. }
  3550. function olList(text, item) {
  3551. return '\n<ol>\n\t<li>' + item.trim() + '</li>\n</ol>';
  3552. }
  3553. function blockquote(text, tmp, item) {
  3554. return '\n<blockquote>' + item.trim() + '</blockquote>';
  3555. }
  3556. function header(text, chars, content) {
  3557. var level = chars.length;
  3558. return '<h' + level + '>' + content.trim() + '</h' + level + '>';
  3559. }
  3560. function autolink(text) {
  3561. var urls = text.match(/(\(?https?:\/\/[-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#\/%=~_()|])(">|<\/a>|\)|\])?/gm);
  3562. if (urls == null)
  3563. return text;
  3564. var cleanedUrls = {};
  3565. for (var i = 0; i < urls.length; i++) {
  3566. var url = urls[i];
  3567. // ignore urls that are part of a markdown link already
  3568. if (url.lastIndexOf(']') != -1 || url.lastIndexOf(')') != -1)
  3569. break;
  3570. // already replaced this url
  3571. if (cleanedUrls[url])
  3572. break;
  3573. cleanedUrls[url] = url;
  3574. text = text.replace(url, '[' + url + '](' + url + ')');
  3575. }
  3576. return text;
  3577. }
  3578. };
  3579. window.Slimdown = Slimdown;
  3580. }();
  3581. common_kit = function ($, itemHelpers) {
  3582. var that = {};
  3583. /**
  3584. * Checks if a kit can be checked out (any items available)
  3585. * @memberOf common
  3586. * @name common#kitCanCheckout
  3587. * @method
  3588. * @param kit
  3589. * @returns {boolean}
  3590. */
  3591. that.kitCanCheckout = function (kit) {
  3592. return common.getAvailableItems(kit.items || []).length > 0;
  3593. };
  3594. /**
  3595. * Checks if a kit can be reserved (any items active)
  3596. * @memberOf common
  3597. * @name common#kitCanReserve
  3598. * @method
  3599. * @param kit
  3600. * @returns {boolean}
  3601. */
  3602. that.kitCanReserve = function (kit) {
  3603. return common.getActiveItems(kit.items || []).length > 0;
  3604. };
  3605. /**
  3606. * Checks if custody can be taken for a kit (based on status)
  3607. * @memberOf common
  3608. * @name common#kitCanTakeCustody
  3609. * @method
  3610. * @param kit
  3611. * @returns {boolean}
  3612. */
  3613. that.kitCanTakeCustody = function (kit) {
  3614. return kit.status == 'available';
  3615. };
  3616. /**
  3617. * Checks if custody can be released for a kit (based on status)
  3618. * @memberOf common
  3619. * @name common#kitCanReleaseCustody
  3620. * @method
  3621. * @param kit
  3622. * @returns {boolean}
  3623. */
  3624. that.kitCanReleaseCustody = function (kit) {
  3625. return kit.status == 'in_custody';
  3626. };
  3627. /**
  3628. * Checks if custody can be transferred for a kit (based on status)
  3629. * @memberOf common
  3630. * @name common#kitCanTransferCustody
  3631. * @method
  3632. * @param kit
  3633. * @returns {boolean}
  3634. */
  3635. that.kitCanTransferCustody = function (kit) {
  3636. return kit.status == 'in_custody';
  3637. };
  3638. /**
  3639. * getKitStatus
  3640. * Available => if all items in the kit are available
  3641. * Checking out => if all item in the kit is checking out
  3642. * Checked out => if all item in the kit is checked out
  3643. * Expired => if all item in the kit is expired
  3644. * Unknown => if not all items in the kit have the same status
  3645. *
  3646. * @memberOf common
  3647. * @name common#getKitStatus
  3648. * @method
  3649. *
  3650. * @param status
  3651. * @return {string}
  3652. */
  3653. that.getKitStatus = function (items) {
  3654. var statuses = {};
  3655. var itemStatuses = [];
  3656. var orders = {};
  3657. var itemOrders = [];
  3658. // Make dictionary of different item statuses
  3659. $.each(items, function (i, item) {
  3660. // Unique item statuses
  3661. if (!statuses[item.status]) {
  3662. statuses[item.status] = true;
  3663. itemStatuses.push(item.status);
  3664. }
  3665. // Unique item orders
  3666. if (!orders[item.order]) {
  3667. orders[item.order] = true;
  3668. itemOrders.push(item.order);
  3669. }
  3670. });
  3671. if (itemStatuses.length == 1 && itemOrders.length <= 1) {
  3672. // All items in the kit have the same status and are in the same order
  3673. return itemStatuses[0];
  3674. } else {
  3675. // Kit has items with different statuses
  3676. return 'incomplete';
  3677. }
  3678. };
  3679. /**
  3680. * getFriendlyKitStatus
  3681. *
  3682. * @memberOf common
  3683. * @name common#getFriendlyKitStatus
  3684. * @method
  3685. *
  3686. * @param status
  3687. * @return {string}
  3688. */
  3689. that.getFriendlyKitStatus = function (status) {
  3690. if (status == 'incomplete') {
  3691. return 'Incomplete';
  3692. }
  3693. return itemHelpers.getFriendlyItemStatus(status);
  3694. };
  3695. /**
  3696. * getKitStatusCss
  3697. *
  3698. * @memberOf common
  3699. * @name common#getKitStatusCss
  3700. * @method
  3701. *
  3702. * @param status
  3703. * @return {string}
  3704. */
  3705. that.getKitStatusCss = function (status) {
  3706. if (status == 'incomplete') {
  3707. return 'label-incomplete';
  3708. }
  3709. return itemHelpers.getItemStatusCss(status);
  3710. };
  3711. /**
  3712. * getKitIds
  3713. *
  3714. * @memberOf common
  3715. * @name common#getKitIds
  3716. * @method
  3717. *
  3718. * @param items
  3719. * @return {array}
  3720. */
  3721. that.getKitIds = function (items) {
  3722. var kitDictionary = {};
  3723. var ids = [];
  3724. $.each(items, function (i, item) {
  3725. if (item.kit) {
  3726. var kitId = typeof item.kit == 'string' ? item.kit : item.kit._id;
  3727. if (!kitDictionary[kitId]) {
  3728. kitDictionary[kitId] = true;
  3729. ids.push(kitId);
  3730. }
  3731. }
  3732. });
  3733. return ids;
  3734. };
  3735. return that;
  3736. }(jquery, common_item);
  3737. common_contact = function (imageHelper) {
  3738. var that = {};
  3739. that.contactGetUserId = function (contact) {
  3740. if (contact.user) {
  3741. if (typeof contact.user === 'string') {
  3742. return contact.user;
  3743. } else if (contact.user.hasOwnProperty('_id')) {
  3744. return contact._id;
  3745. }
  3746. }
  3747. };
  3748. that.contactGetUserSync = function (contact) {
  3749. if (contact.user && contact.user.sync) {
  3750. return contact.user.sync;
  3751. }
  3752. };
  3753. that.contactCanReserve = function (contact) {
  3754. return contact.status == 'active';
  3755. };
  3756. that.contactCanCheckout = function (contact) {
  3757. return contact.status == 'active';
  3758. };
  3759. that.contactCanGenerateDocument = function (contact) {
  3760. return contact.status == 'active';
  3761. };
  3762. that.contactCanArchive = function (contact) {
  3763. return contact.status == 'active' && !that.contactGetUserSync(contact);
  3764. };
  3765. that.contactCanUndoArchive = function (contact) {
  3766. return contact.status == 'archived' && !that.contactGetUserSync(contact);
  3767. };
  3768. that.contactCanDelete = function (contact) {
  3769. return !that.contactGetUserSync(contact);
  3770. };
  3771. /**
  3772. * getContactImageUrl
  3773. *
  3774. * @memberOf common
  3775. * @name common#getContactImageUrl
  3776. * @method
  3777. *
  3778. * @param cr.Contact or contact object
  3779. * @return {string} image path or base64 image
  3780. */
  3781. that.getContactImageUrl = function (ds, contact, size, bustCache) {
  3782. // Show maintenance avatar?
  3783. if (contact.kind == 'maintenance')
  3784. return imageHelper.getMaintenanceAvatar(size);
  3785. // Show profile picture of user?
  3786. if (contact.user && contact.user.picture)
  3787. return imageHelper.getImageUrl(ds, contact.user.picture, size, bustCache);
  3788. // Show avatar initials
  3789. return imageHelper.getAvatarInitial(contact.name, size);
  3790. };
  3791. /**
  3792. * getContactImageCDNUrl
  3793. *
  3794. * @memberOf common
  3795. * @name common#getContactImageCDNUrl
  3796. * @method
  3797. *
  3798. * @param cr.Contact or contact object
  3799. * @return {string} image path or base64 image
  3800. */
  3801. that.getContactImageCDNUrl = function (settings, groupid, contact, size, bustCache) {
  3802. // Show maintenance avatar?
  3803. if (contact.kind == 'maintenance')
  3804. return imageHelper.getMaintenanceAvatar(size);
  3805. // Show profile picture of user?
  3806. if (contact.user && contact.user.picture)
  3807. return imageHelper.getImageCDNUrl(settings, groupid, contact.user.picture, size, bustCache);
  3808. // Show avatar initials
  3809. return imageHelper.getAvatarInitial(contact.name, size);
  3810. };
  3811. return that;
  3812. }(common_image);
  3813. common_user = function (imageHelper) {
  3814. return {
  3815. /**
  3816. * getUserImageUrl
  3817. *
  3818. * @memberOf common
  3819. * @name common#getUserImageUrl
  3820. * @method
  3821. *
  3822. * @param cr.User or user object
  3823. * @return {string} image path or base64 image
  3824. */
  3825. getUserImageUrl: function (ds, user, size, bustCache) {
  3826. // Show profile picture of user?
  3827. if (user && user.picture)
  3828. return imageHelper.getImageUrl(ds, user.picture, size, bustCache);
  3829. // Show avatar initials
  3830. return imageHelper.getAvatarInitial(user.name, size);
  3831. },
  3832. /**
  3833. * getUserImageCDNUrl
  3834. *
  3835. * @memberOf common
  3836. * @name common#getUserImageCDNUrl
  3837. * @method
  3838. *
  3839. * @param cr.User or user object
  3840. * @return {string} image path or base64 image
  3841. */
  3842. getUserImageCDNUrl: function (settings, groupid, user, size, bustCache) {
  3843. // Show profile picture of user?
  3844. if (user && user.picture)
  3845. return imageHelper.getImageCDNUrl(settings, groupid, user.picture, size, bustCache);
  3846. // Show avatar initials
  3847. return imageHelper.getAvatarInitial(user.name, size);
  3848. }
  3849. };
  3850. }(common_image);
  3851. common_template = function (moment) {
  3852. return {
  3853. templateIsArchived: function (templ) {
  3854. return !(templ.archived == null);
  3855. },
  3856. templateCanDelete: function (templ) {
  3857. return !templ.system;
  3858. },
  3859. templateCanActivate: function (templ) {
  3860. return templ.status == 'inactive' && templ.archived == null;
  3861. },
  3862. templateCanDeactivate: function (templ) {
  3863. return templ.status == 'active' && templ.archived == null;
  3864. },
  3865. templateCanArchive: function (templ) {
  3866. return templ.archived == null;
  3867. },
  3868. templateCanUndoArchive: function (templ) {
  3869. return templ.archived != null;
  3870. },
  3871. /**
  3872. * getFriendlyTemplateStatus
  3873. *
  3874. * @memberOf common
  3875. * @name common#getFriendlyTemplateStatus
  3876. * @method
  3877. *
  3878. * @param {string} status
  3879. * @return {string}
  3880. */
  3881. getFriendlyTemplateStatus: function (status) {
  3882. switch (status) {
  3883. case 'inactive':
  3884. return 'Inactive';
  3885. case 'active':
  3886. return 'Active';
  3887. case 'archived':
  3888. return 'Archived';
  3889. default:
  3890. return 'Unknown';
  3891. }
  3892. },
  3893. /**
  3894. * getFriendlyTemplateCss
  3895. *
  3896. * @memberOf common
  3897. * @name common#getFriendlyTemplateCss
  3898. * @method
  3899. *
  3900. * @param {string} status
  3901. * @return {string}
  3902. */
  3903. getFriendlyTemplateCss: function (status) {
  3904. switch (status) {
  3905. case 'inactive':
  3906. return 'label-inactive';
  3907. case 'active':
  3908. return 'label-active';
  3909. case 'archived':
  3910. return 'label-archived';
  3911. default:
  3912. return '';
  3913. }
  3914. },
  3915. /**
  3916. * getFriendlyTemplateSize
  3917. *
  3918. * @memberOf common
  3919. * @name common#getFriendlyTemplateSize
  3920. * @method
  3921. *
  3922. * @param {float} width
  3923. * @param {float} height
  3924. * @param {string} unit
  3925. * @return {string}
  3926. */
  3927. getFriendlyTemplateSize: function (width, height, unit) {
  3928. if (width == 0 || height == 0) {
  3929. return '';
  3930. } else if (unit == 'inch' && width == 8.5 && height == 11) {
  3931. return 'US Letter';
  3932. } else if (unit == 'mm' && width == 210 && height == 297) {
  3933. return 'A4';
  3934. } else {
  3935. var friendlyUnit = unit == 'inch' ? '"' : unit;
  3936. return width + friendlyUnit + ' x ' + height + friendlyUnit;
  3937. }
  3938. }
  3939. };
  3940. }(moment);
  3941. common_clientStorage = function () {
  3942. var setItem = localStorage.setItem, getItem = localStorage.getItem, removeItem = localStorage.removeItem;
  3943. var _data = {};
  3944. /**
  3945. * Override default localStorage.setItem
  3946. * Try to set an object for a key in local storage
  3947. *
  3948. * @param {string} k
  3949. * @param {object|string} v
  3950. * @return {bool}
  3951. */
  3952. Storage.prototype.setItem = function (k, v) {
  3953. try {
  3954. setItem.apply(this, [
  3955. k,
  3956. v
  3957. ]);
  3958. } catch (e) {
  3959. _data[k] = String(v);
  3960. }
  3961. return true;
  3962. };
  3963. /**
  3964. * Override default localStorage.getItem
  3965. * Try to get an object for a key in local storage
  3966. *
  3967. * @param {string} k
  3968. * @return {object|string|null}
  3969. */
  3970. Storage.prototype.getItem = function (k) {
  3971. try {
  3972. return getItem.apply(this, [k]);
  3973. } catch (e) {
  3974. return _data.hasOwnProperty(k) ? _data[k] : undefined;
  3975. }
  3976. return null;
  3977. };
  3978. /**
  3979. * Override default localStorage.removeItem
  3980. * Try to remove an object for a key in local storage
  3981. *
  3982. * @param {string} k
  3983. * @return {object|string|null}
  3984. */
  3985. Storage.prototype.removeItem = function (k) {
  3986. try {
  3987. removeItem.apply(this, [k]);
  3988. } catch (e) {
  3989. delete _data[k];
  3990. }
  3991. return true;
  3992. };
  3993. }();
  3994. common_document = {
  3995. /**
  3996. * Checks if a given object (document) contains given value
  3997. *
  3998. * @memberOf common
  3999. * @name common#searchDocument
  4000. * @method
  4001. *
  4002. * @param doc
  4003. * @param value
  4004. * @param fields
  4005. * @return Boolean
  4006. */
  4007. containsValue: function (doc, value, fields) {
  4008. doc = doc || {};
  4009. fields = fields || [
  4010. 'name',
  4011. 'fields',
  4012. 'codes',
  4013. 'barcodes'
  4014. ];
  4015. // Trim search
  4016. value = $.trim(value).toLowerCase();
  4017. // Name contains value?
  4018. if (fields.indexOf('name') != -1) {
  4019. if (doc.name && doc.name.toLowerCase().indexOf(value) != -1)
  4020. return true;
  4021. }
  4022. // Field contains value?
  4023. if (fields.indexOf('fields') != -1) {
  4024. if (doc.fields) {
  4025. var findValue = Object.values(doc.fields).find(function (fieldValue) {
  4026. return fieldValue.toString().toLowerCase().indexOf(value) != -1;
  4027. });
  4028. if (findValue) {
  4029. return true;
  4030. }
  4031. }
  4032. }
  4033. // Code contains value?
  4034. if (fields.indexOf('codes') != -1) {
  4035. if (doc.codes) {
  4036. var findValue = doc.codes.find(function (code) {
  4037. return code.toLowerCase().indexOf(value) != -1;
  4038. });
  4039. if (findValue) {
  4040. return true;
  4041. }
  4042. }
  4043. }
  4044. // Barcode contains value?
  4045. if (fields.indexOf('barcodes') != -1) {
  4046. if (doc.barcodes) {
  4047. var findValue = doc.barcodes.find(function (code) {
  4048. return code.toLowerCase().indexOf(value) != -1;
  4049. });
  4050. if (findValue) {
  4051. return true;
  4052. }
  4053. }
  4054. }
  4055. return false;
  4056. },
  4057. /**
  4058. * getDocumentIds
  4059. * @memberOf common
  4060. * @name common#getDocumentIds
  4061. * @method
  4062. * @param docs
  4063. * @return {array}
  4064. */
  4065. getDocumentIds: function (docs) {
  4066. return docs.map(function (doc) {
  4067. return typeof doc === 'string' ? doc : doc._id;
  4068. });
  4069. }
  4070. };
  4071. common_transaction = function (moment, keyValues) {
  4072. var that = {};
  4073. /**
  4074. * getTransactionSummary
  4075. * Return a friendly summary for a given transaction or custom name
  4076. *
  4077. * @memberOf common
  4078. * @name common#getTransactionSummary
  4079. * @method
  4080. *
  4081. * @param {object} transaction
  4082. * @param {string} emptyText
  4083. * @return {string}
  4084. */
  4085. that.getTransactionSummary = function (transaction, emptyText) {
  4086. if (transaction) {
  4087. if (transaction.name) {
  4088. return transaction.name;
  4089. } else if (transaction.itemSummary) {
  4090. return transaction.itemSummary;
  4091. } else if (transaction.items && transaction.items.length > 0) {
  4092. return keyValues.getCategorySummary(transaction.items);
  4093. }
  4094. }
  4095. return emptyText || 'No items';
  4096. };
  4097. /**
  4098. * getOrderDuration
  4099. * Gets a moment duration object
  4100. *
  4101. * @memberOf common
  4102. * @name common#getOrderDuration
  4103. * @method
  4104. *
  4105. * @returns {duration}
  4106. */
  4107. that.getOrderDuration = function (transaction) {
  4108. if (transaction.started != null) {
  4109. var to = transaction.status == 'closed' ? transaction.finished : transaction.due;
  4110. if (to) {
  4111. return moment.duration(to - transaction.started);
  4112. }
  4113. }
  4114. return null;
  4115. };
  4116. /**
  4117. * getFriendlyOrderDuration
  4118. * Gets a friendly duration for a given order
  4119. *
  4120. * @memberOf common
  4121. * @name common#getFriendlyOrderDuration
  4122. * @method
  4123. *
  4124. * @param transaction
  4125. * @param dateHelper
  4126. * @returns {string}
  4127. */
  4128. that.getFriendlyOrderDuration = function (transaction, dateHelper) {
  4129. var duration = that.getOrderDuration(transaction);
  4130. return duration != null ? dateHelper.getFriendlyDuration(duration) : '';
  4131. };
  4132. /**
  4133. * getReservationDuration
  4134. * Return a Moment duration for a given reservation
  4135. *
  4136. * @memberOf common
  4137. * @name common#getReservationDuration
  4138. * @method
  4139. *
  4140. * @param transaction
  4141. * @returns {duration}
  4142. */
  4143. that.getReservationDuration = function (transaction) {
  4144. if (transaction) {
  4145. if (transaction.fromDate != null && transaction.toDate != null) {
  4146. return moment.duration(transaction.toDate - transaction.fromDate);
  4147. }
  4148. return null;
  4149. }
  4150. };
  4151. /**
  4152. * getFriendlyReservationDuration
  4153. * Gets a friendly duration for a given reservation
  4154. *
  4155. * @memberOf common
  4156. * @name common#getFriendlyReservationDuration
  4157. * @method
  4158. *
  4159. * @param transaction
  4160. * @param dateHelper
  4161. * @returns {string}
  4162. */
  4163. that.getFriendlyReservationDuration = function (transaction, dateHelper) {
  4164. var duration = that.getReservationDuration(transaction);
  4165. return duration != null ? dateHelper.getFriendlyDuration(duration) : '';
  4166. };
  4167. return that;
  4168. }(moment, common_keyValues);
  4169. common_queue = function ($) {
  4170. $.fn.ajaxQueue = function () {
  4171. var previous = new $.Deferred().resolve();
  4172. return function (fn, fail) {
  4173. if (typeof fn !== 'function') {
  4174. throw 'must be a function';
  4175. }
  4176. return previous = previous.then(fn, fail || fn);
  4177. };
  4178. };
  4179. }(jquery);
  4180. common_pubsub = function ($) {
  4181. var o = $({});
  4182. $.subscribe = function () {
  4183. o.on.apply(o, arguments);
  4184. };
  4185. $.unsubscribe = function () {
  4186. o.off.apply(o, arguments);
  4187. };
  4188. $.publish = function () {
  4189. o.trigger.apply(o, arguments);
  4190. };
  4191. }(jquery);
  4192. common = function ($, code, order, reservation, item, conflicts, keyvalues, image, attachment, inflection, validation, utils, slimdown, kit, contact, user, template, clientStorage, _document, transaction, ajaxQueue, pubsub) {
  4193. /**
  4194. * Return common object with different helper methods
  4195. */
  4196. return $.extend({}, code, order, reservation, item, conflicts, keyvalues, image, attachment, validation, utils, kit, contact, user, template, _document, transaction);
  4197. }(jquery, common_code, common_order, common_reservation, common_item, common_conflicts, common_keyValues, common_image, common_attachment, common_inflection, common_validation, common_utils, common_slimdown, common_kit, common_contact, common_user, common_template, common_clientStorage, common_document, common_transaction, common_queue, common_pubsub);
  4198. colorLabel = function ($) {
  4199. var DEFAULTS = {
  4200. id: null,
  4201. name: '',
  4202. color: 'Gold',
  4203. readonly: false,
  4204. selected: false
  4205. };
  4206. /**
  4207. * @name ColorLabel
  4208. * @class
  4209. * @param spec
  4210. * @constructor
  4211. */
  4212. var ColorLabel = function (spec) {
  4213. spec = spec || {};
  4214. this.raw = $.extend({}, DEFAULTS, spec);
  4215. this.id = spec.id || DEFAULTS.id;
  4216. this.name = spec.name || DEFAULTS.name;
  4217. this.color = spec.color || DEFAULTS.color;
  4218. this.readonly = spec.readonly || DEFAULTS.readonly;
  4219. this.selected = spec.selected || DEFAULTS.selected;
  4220. };
  4221. /**
  4222. * isDirty
  4223. * @name ColorLabel#isDirty
  4224. * @method
  4225. * @returns {boolean}
  4226. */
  4227. ColorLabel.prototype.isDirty = function () {
  4228. return this.raw.name != this.name || this.raw.color != this.color;
  4229. };
  4230. /**
  4231. * isValid
  4232. * @name ColorLabel#isValid
  4233. * @method
  4234. * @returns {boolean}
  4235. */
  4236. ColorLabel.prototype.isValid = function () {
  4237. return this.name && this.name.length > 0;
  4238. };
  4239. /**
  4240. * _fromJson
  4241. * @name ColorLabel#_fromJson
  4242. * @method
  4243. * @returns {boolean}
  4244. */
  4245. ColorLabel.prototype._fromJson = function (data) {
  4246. this.id = data.id || DEFAULTS.id;
  4247. this.name = data.name || DEFAULTS.name;
  4248. this.color = data.color || DEFAULTS.color;
  4249. this.selected = data.selected || DEFAULTS.selected;
  4250. this.readonly = data.readonly || DEFAULTS.readonly;
  4251. return $.Deferred().resolve();
  4252. };
  4253. /**
  4254. * _toJson
  4255. * @name ColorLabel#_toJson
  4256. * @method
  4257. * @returns {boolean}
  4258. */
  4259. ColorLabel.prototype._toJson = function () {
  4260. return {
  4261. id: this.id,
  4262. name: this.name,
  4263. color: this.color,
  4264. selected: this.selected,
  4265. readonly: this.readonly
  4266. };
  4267. };
  4268. return ColorLabel;
  4269. }(jquery);
  4270. document = function ($, common, api, ColorLabel) {
  4271. // Some constant values
  4272. var DEFAULTS = { id: '' };
  4273. /**
  4274. * @name Document
  4275. * @class
  4276. * @constructor
  4277. * @property {ApiDataSource} ds - The documents primary key
  4278. * @property {array} _fields - The raw, unprocessed json response
  4279. * @property {string} id - The documents primary key
  4280. * @property {string} raw - The raw, unprocessed json response
  4281. */
  4282. var Document = function (spec) {
  4283. this.raw = null;
  4284. // raw json object
  4285. this.id = spec.id || DEFAULTS.id;
  4286. // doc _id
  4287. this.ds = spec.ds;
  4288. // ApiDataSource object
  4289. this._fields = spec._fields; // e.g. [*]
  4290. };
  4291. /**
  4292. * Resets the object
  4293. * @name Document#reset
  4294. * @method
  4295. * @returns {promise}
  4296. */
  4297. Document.prototype.reset = function () {
  4298. // By default, reset just reads from the DEFAULTS dict again
  4299. return this._fromJson(this._getDefaults(), null);
  4300. };
  4301. /**
  4302. * Checks if the document exists in the database
  4303. * @name Document#existsInDb
  4304. * @method
  4305. * @returns {boolean}
  4306. */
  4307. Document.prototype.existsInDb = function () {
  4308. // Check if we have a primary key
  4309. return this.id != null && this.id.length > 0;
  4310. };
  4311. /**
  4312. * Checks if the object is empty
  4313. * @name Document#isEmpty
  4314. * @method
  4315. * @returns {boolean}
  4316. */
  4317. Document.prototype.isEmpty = function () {
  4318. return true;
  4319. };
  4320. /**
  4321. * Checks if the object needs to be saved
  4322. * We don't check any of the keyvalues (or comments, attachments) here
  4323. * @name Document#isDirty
  4324. * @method
  4325. * @returns {boolean}
  4326. */
  4327. Document.prototype.isDirty = function () {
  4328. return false;
  4329. };
  4330. /**
  4331. * Checks if the object is valid
  4332. * @name Document#isValid
  4333. * @method
  4334. * @returns {boolean}
  4335. */
  4336. Document.prototype.isValid = function () {
  4337. return true;
  4338. };
  4339. /**
  4340. * Discards any changes made to the object from the previously loaded raw response
  4341. * or resets it when no old raw response was found
  4342. * @name Document#discardChanges
  4343. * @method
  4344. * @returns {promise}
  4345. */
  4346. Document.prototype.discardChanges = function () {
  4347. return this.raw ? this._fromJson(this.raw, null) : this.reset();
  4348. };
  4349. /**
  4350. * Reloads the object from db
  4351. * @name Document#reload
  4352. * @method
  4353. * @param _fields
  4354. * @returns {promise}
  4355. */
  4356. Document.prototype.reload = function (_fields) {
  4357. if (this.existsInDb()) {
  4358. return this.get(_fields);
  4359. } else {
  4360. return $.Deferred().reject(new api.ApiError('Cannot reload document, id is empty or null'));
  4361. }
  4362. };
  4363. /**
  4364. * Gets an object by the default api.get
  4365. * @name Document#get
  4366. * @method
  4367. * @param _fields
  4368. * @returns {promise}
  4369. */
  4370. Document.prototype.get = function (_fields) {
  4371. if (this.existsInDb()) {
  4372. var that = this;
  4373. return this.ds.get(this.id, _fields || this._fields).then(function (data) {
  4374. return that._fromJson(data);
  4375. });
  4376. } else {
  4377. return $.Deferred().reject(new api.ApiError('Cannot get document, id is empty or null'));
  4378. }
  4379. };
  4380. /**
  4381. * Creates an object by the default api.create
  4382. * @name Document#create
  4383. * @method
  4384. * @param skipRead skips reading the response via _fromJson (false)
  4385. * @returns {promise}
  4386. */
  4387. Document.prototype.create = function (skipRead) {
  4388. if (this.existsInDb()) {
  4389. return $.Deferred().reject(new Error('Cannot create document, already exists in database'));
  4390. }
  4391. if (this.isEmpty()) {
  4392. return $.Deferred().reject(new Error('Cannot create empty document'));
  4393. }
  4394. if (!this.isValid()) {
  4395. return $.Deferred().reject(new Error('Cannot create, invalid document'));
  4396. }
  4397. return this._create(skipRead);
  4398. };
  4399. /**
  4400. * Updates an object by the default api.update
  4401. * @name Document#update
  4402. * @method
  4403. * @param skipRead skips reading the response via _fromJson (false)
  4404. * @returns {promise}
  4405. */
  4406. Document.prototype.update = function (skipRead) {
  4407. if (!this.existsInDb()) {
  4408. return $.Deferred().reject(new Error('Cannot update document without id'));
  4409. }
  4410. if (this.isEmpty()) {
  4411. return $.Deferred().reject(new Error('Cannot update to empty document'));
  4412. }
  4413. if (!this.isValid()) {
  4414. return $.Deferred().reject(new Error('Cannot update, invalid document'));
  4415. }
  4416. return this._update(skipRead);
  4417. };
  4418. /**
  4419. * Deletes an object by the default api.delete
  4420. * @name Document#delete
  4421. * @method
  4422. * @returns {promise}
  4423. */
  4424. Document.prototype.delete = function () {
  4425. // Call the api /delete on this document
  4426. if (this.existsInDb()) {
  4427. return this._delete();
  4428. } else {
  4429. return $.Deferred().reject(new Error('Document does not exist'));
  4430. }
  4431. };
  4432. // toJson, fromJson
  4433. // ----
  4434. Document.prototype._getDefaults = function () {
  4435. return DEFAULTS;
  4436. };
  4437. /**
  4438. * _toJson, makes a dict of this object
  4439. * Possibly inheriting classes will override this method,
  4440. * because not all fields can be set during create / update
  4441. * @method
  4442. * @param options
  4443. * @returns {{}}
  4444. * @private
  4445. */
  4446. Document.prototype._toJson = function (options) {
  4447. return { id: this.id };
  4448. };
  4449. /**
  4450. * _fromJson: in this implementation we'll only read
  4451. * the data.keyValues into: comments, attachments, keyValues
  4452. * @method
  4453. * @param {object} data the json response
  4454. * @param {object} options dict
  4455. * @private
  4456. */
  4457. Document.prototype._fromJson = function (data, options) {
  4458. this.raw = data;
  4459. this.id = data._id || DEFAULTS.id;
  4460. return $.Deferred().resolve(data);
  4461. };
  4462. // Implementation stuff
  4463. // ---
  4464. /**
  4465. * The actual _create implementation (after all the checks are done)
  4466. * @param skipRead
  4467. * @returns {*}
  4468. * @private
  4469. */
  4470. Document.prototype._create = function (skipRead) {
  4471. var that = this;
  4472. var data = this._toJson();
  4473. delete data.id;
  4474. return this.ds.create(data, this._fields).then(function (data) {
  4475. return skipRead == true ? data : that._fromJson(data);
  4476. });
  4477. };
  4478. /**
  4479. * The actual _update implementation (after all the checks are done)
  4480. * @param skipRead
  4481. * @returns {*}
  4482. * @private
  4483. */
  4484. Document.prototype._update = function (skipRead) {
  4485. var that = this;
  4486. var data = this._toJson();
  4487. delete data.id;
  4488. return this.ds.update(this.id, data, this._fields).then(function (data) {
  4489. return skipRead == true ? data : that._fromJson(data);
  4490. });
  4491. };
  4492. /**
  4493. * The actual _delete implementation (after all the checks are done)
  4494. * @returns {*}
  4495. * @private
  4496. */
  4497. Document.prototype._delete = function () {
  4498. var that = this;
  4499. return this.ds.delete(this.id).then(function () {
  4500. return that.reset();
  4501. });
  4502. };
  4503. /**
  4504. * Helper for checking if a simple object property is dirty
  4505. * compared to the original raw result
  4506. * @param prop
  4507. * @returns {boolean}
  4508. * @private
  4509. */
  4510. Document.prototype._isDirtyProperty = function (prop) {
  4511. return this.raw ? this[prop] != this.raw[prop] : false;
  4512. };
  4513. /**
  4514. * Helper for checking if a simple object property is dirty
  4515. * compared to the original raw result
  4516. * Because we know that the API doesn't return empty string properties,
  4517. * we do a special, extra check on that.
  4518. * @param prop
  4519. * @returns {boolean}
  4520. * @private
  4521. */
  4522. Document.prototype._isDirtyStringProperty = function (prop) {
  4523. if (this.raw) {
  4524. var same = this[prop] == this.raw[prop] || this[prop] == '' && this.raw[prop] == null;
  4525. return !same;
  4526. } else {
  4527. return false;
  4528. }
  4529. };
  4530. /**
  4531. * Helper for checking if a simple object property is dirty
  4532. * compared to the original raw result
  4533. * @param prop
  4534. * @returns {boolean}
  4535. * @private
  4536. */
  4537. Document.prototype._isDirtyMomentProperty = function (prop) {
  4538. if (this.raw) {
  4539. var newVal = this[prop], oldVal = this.raw[prop];
  4540. if (newVal == null && oldVal == null) {
  4541. return false;
  4542. } else if (newVal && oldVal) {
  4543. return !newVal.isSame(oldVal);
  4544. } else {
  4545. return true;
  4546. }
  4547. } else {
  4548. return false;
  4549. }
  4550. };
  4551. /**
  4552. * Gets the id of a document
  4553. * @param obj
  4554. * @param prop
  4555. * @returns {string}
  4556. * @private
  4557. */
  4558. Document.prototype._getId = function (obj, prop) {
  4559. return typeof obj === 'string' ? obj : obj[prop || '_id'];
  4560. };
  4561. Document.prototype._getIds = function (objs, prop) {
  4562. return objs.map(function (obj) {
  4563. return typeof obj == 'string' ? obj : obj[prop || '_id'];
  4564. });
  4565. };
  4566. /**
  4567. * Wrapping the this.ds.call method
  4568. * {pk: '', method: '', params: {}, _fields: '', timeOut: null, usePost: null, skipRead: null}
  4569. * @method
  4570. * @param spec
  4571. * @returns {promise}
  4572. * @private
  4573. */
  4574. Document.prototype._doApiCall = function (spec) {
  4575. var that = this;
  4576. return this.ds.call(spec.collectionCall == true ? null : spec.pk || this.id, spec.method, spec.params, spec._fields || this._fields, spec.timeOut, spec.usePost).then(function (data) {
  4577. return spec.skipRead == true ? data : that._fromJson(data);
  4578. });
  4579. };
  4580. /**
  4581. * Wrapping the this.ds.call method with a longer timeout
  4582. * {pk: '', method: '', params: {}, _fields: '', timeOut: null, usePost: null, skipRead: null}
  4583. * @method
  4584. * @param spec
  4585. * @returns {promise}
  4586. * @private
  4587. */
  4588. Document.prototype._doApiLongCall = function (spec) {
  4589. spec.timeOut = spec.timeOut || 30000;
  4590. return this._doApiCall(spec);
  4591. };
  4592. Document.prototype._getColorLabel = function (data, options) {
  4593. var spec = $.extend({}, options || {}, data);
  4594. return new ColorLabel(spec);
  4595. };
  4596. return Document;
  4597. }(jquery, common, api, colorLabel);
  4598. Availability = function ($, common, api, Document) {
  4599. // Some constant values
  4600. var DEFAULTS = {
  4601. id: '',
  4602. planning: '',
  4603. item: '',
  4604. from: null,
  4605. to: null,
  4606. order: '',
  4607. reservation: ''
  4608. };
  4609. // Allow overriding the ctor during inheritance
  4610. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  4611. var tmp = function () {
  4612. };
  4613. tmp.prototype = Document.prototype;
  4614. /**
  4615. * Availability represents the **un**availability of an Item between two dates
  4616. * Each of these unavailable timeslots have a reference to an Order or
  4617. * a Reservation for which it's or will be booked
  4618. *
  4619. * @name Availability
  4620. * @class
  4621. * @property {string} planning the planning primary key
  4622. * @property {string} item the item primary key
  4623. * @property {Moment} from the from date
  4624. * @property {Moment} to the to date
  4625. * @property {string} order the order primary key (if any)
  4626. * @property {string} reservation the reservation primary key (if any)
  4627. * @constructor
  4628. * @extends Document
  4629. */
  4630. var Availability = function (opt) {
  4631. var spec = $.extend({}, opt);
  4632. Document.call(this, spec);
  4633. this.planning = spec.planning || DEFAULTS.planning;
  4634. this.item = spec.item || DEFAULTS.item;
  4635. this.from = spec.from || DEFAULTS.from;
  4636. this.to = spec.to || DEFAULTS.to;
  4637. this.order = spec.order || DEFAULTS.order;
  4638. this.reservation = spec.reservation || DEFAULTS.reservation;
  4639. };
  4640. Availability.prototype = new tmp();
  4641. Availability.prototype.constructor = Availability;
  4642. //
  4643. // Document overrides
  4644. //
  4645. Availability.prototype._getDefaults = function () {
  4646. return DEFAULTS;
  4647. };
  4648. /**
  4649. * Checks if the object is empty, it never is
  4650. * @name Availability#isEmpty
  4651. * @method
  4652. * @returns {boolean}
  4653. * @override
  4654. */
  4655. Availability.prototype.isEmpty = function () {
  4656. // An Availability is never empty because it's generated on the server side
  4657. return false;
  4658. };
  4659. /**
  4660. * Checks via the api if we can delete the Availability document
  4661. * @name Availability#canDelete
  4662. * @method
  4663. * @returns {promise}
  4664. * @override
  4665. */
  4666. Availability.prototype.canDelete = function () {
  4667. // An Availability can never be deleted
  4668. return $.Deferred().resolve(false);
  4669. };
  4670. // toJson, fromJson
  4671. // ----
  4672. /**
  4673. * _toJson, makes a dict of params to use during create / update
  4674. * @param options
  4675. * @returns {{}}
  4676. * @private
  4677. */
  4678. Availability.prototype._toJson = function (options) {
  4679. var data = Document.prototype._toJson.call(this, options);
  4680. data.planning = this.planning;
  4681. data.item = this.item;
  4682. data.fromDate = this.from;
  4683. data.toDate = this.to;
  4684. data.order = this.order;
  4685. data.reservation = this.reservation;
  4686. return data;
  4687. };
  4688. /**
  4689. * _fromJson: read some basic information
  4690. * @method
  4691. * @param {object} data the json response
  4692. * @param {object} options dict
  4693. * @returns {Promise}
  4694. * @private
  4695. */
  4696. Availability.prototype._fromJson = function (data, options) {
  4697. var that = this;
  4698. return Document.prototype._fromJson.call(this, data, options).then(function () {
  4699. that.planning = data.planning || DEFAULTS.planning;
  4700. that.item = data.item || DEFAULTS.item;
  4701. that.from = data.fromDate || DEFAULTS.from;
  4702. that.to = data.toDate || DEFAULTS.to;
  4703. that.order = data.order || DEFAULTS.order;
  4704. that.reservation = data.reservation || DEFAULTS.reservation;
  4705. return data;
  4706. });
  4707. };
  4708. return Availability;
  4709. }(jquery, common, api, document);
  4710. Attachment = function ($) {
  4711. var EXT = /(?:\.([^.]+))?$/;
  4712. var IMAGES = [
  4713. 'jpg',
  4714. 'jpeg',
  4715. 'png',
  4716. 'gif'
  4717. ];
  4718. var PREVIEWS = [
  4719. 'jpg',
  4720. 'jpeg',
  4721. 'png',
  4722. 'gif',
  4723. 'doc',
  4724. 'docx',
  4725. 'pdf'
  4726. ];
  4727. var DEFAULTS = {
  4728. fileName: '',
  4729. fileSize: 0,
  4730. isCover: false,
  4731. canBeCover: true
  4732. };
  4733. /**
  4734. * @name Attachment
  4735. * @class
  4736. * @property {ApiDataSource} ds attachments datasource
  4737. * @property {bool} isCover is this the cover image of a document
  4738. * @property {bool} canBeCover can this attachment be the cover of a document?
  4739. * @constructor
  4740. */
  4741. var Attachment = function (spec) {
  4742. spec = spec || {};
  4743. this.ds = spec.ds;
  4744. this.raw = null;
  4745. // the raw json object
  4746. this.fileName = spec.fileName || DEFAULTS.fileName;
  4747. this.fileSize = spec.fileSize || DEFAULTS.fileSize;
  4748. this.value = spec.value || DEFAULTS.value;
  4749. this.created = spec.created || DEFAULTS.created;
  4750. this.by = spec.by || DEFAULTS.by;
  4751. this.isCover = spec.isCover != null ? spec.isCover : DEFAULTS.isCover;
  4752. this.canBeCover = spec.canBeCover != null ? spec.canBeCover : DEFAULTS.canBeCover;
  4753. };
  4754. /**
  4755. * Gets the url of a thumbnail
  4756. * "XS": 32,
  4757. * "S": 64,
  4758. * "M": 128,
  4759. * "L": 256,
  4760. * "XL": 512
  4761. * "orig": original size
  4762. * @name Attachment#getThumbnailUrl
  4763. * @method
  4764. * @param size
  4765. * @returns {string}
  4766. */
  4767. Attachment.prototype.getThumbnailUrl = function (size) {
  4768. return this.hasPreview() ? this.helper.getImageUrl(this.ds, this.value, size || 'S') : '';
  4769. };
  4770. /**
  4771. * Gets the url where the attachment can be downloaded
  4772. * @name Attachment#getDownloadUrl
  4773. * @method
  4774. * @returns {string}
  4775. */
  4776. Attachment.prototype.getDownloadUrl = function () {
  4777. return this.ds.getBaseUrl() + this.value + '?download=True';
  4778. };
  4779. /**
  4780. * Gets the extension part of a filename
  4781. * @name Attachment#getExt
  4782. * @method
  4783. * @param fileName
  4784. * @returns {string}
  4785. */
  4786. Attachment.prototype.getExt = function (fileName) {
  4787. fileName = fileName || this.fileName;
  4788. return (EXT.exec(fileName)[1] || '').toLowerCase();
  4789. };
  4790. /**
  4791. * Gets a friendly file size
  4792. * @param {int} size
  4793. * @return {string}
  4794. */
  4795. Attachment.prototype.getFriendlyFileSize = function () {
  4796. var size = this.fileSize;
  4797. if (isNaN(size))
  4798. size = 0;
  4799. if (size < 1024)
  4800. return size + ' Bytes';
  4801. size /= 1024;
  4802. if (size < 1024)
  4803. return size.toFixed(2) + ' Kb';
  4804. size /= 1024;
  4805. if (size < 1024)
  4806. return size.toFixed(2) + ' Mb';
  4807. size /= 1024;
  4808. if (size < 1024)
  4809. return size.toFixed(2) + ' Gb';
  4810. size /= 1024;
  4811. return size.toFixed(2) + ' Tb';
  4812. };
  4813. /**
  4814. * Checks if the attachment is an image
  4815. * @name Attachment#isImage
  4816. * @method
  4817. * @returns {boolean}
  4818. */
  4819. Attachment.prototype.isImage = function () {
  4820. var ext = this.getExt(this.fileName);
  4821. return $.inArray(ext, IMAGES) >= 0;
  4822. };
  4823. /**
  4824. * Checks if the attachment has a preview
  4825. * @name Attachment#hasPreview
  4826. * @method
  4827. * @returns {boolean}
  4828. */
  4829. Attachment.prototype.hasPreview = function () {
  4830. var ext = this.getExt(this.fileName);
  4831. return $.inArray(ext, PREVIEWS) >= 0;
  4832. };
  4833. /**
  4834. * _toJson, makes a dict of the object
  4835. * @method
  4836. * @param options
  4837. * @returns {object}
  4838. * @private
  4839. */
  4840. Attachment.prototype._toJson = function (options) {
  4841. return {
  4842. fileName: this.fileName,
  4843. fileSize: this.fileSize,
  4844. value: this.value,
  4845. created: this.created,
  4846. by: this.by
  4847. };
  4848. };
  4849. /**
  4850. * _fromJson: reads the Attachment object from json
  4851. * @method
  4852. * @param {object} data the json response
  4853. * @param {object} options dict
  4854. * @returns promise
  4855. * @private
  4856. */
  4857. Attachment.prototype._fromJson = function (data, options) {
  4858. this.raw = data;
  4859. this.fileName = data.fileName || DEFAULTS.fileName;
  4860. this.fileSize = data.fileSize || DEFAULTS.fileSize;
  4861. this.value = data.value || DEFAULTS.value;
  4862. this.created = data.created || DEFAULTS.created;
  4863. this.by = data.by || DEFAULTS.by;
  4864. return $.Deferred().resolve(data);
  4865. };
  4866. return Attachment;
  4867. }(jquery);
  4868. comment = function ($) {
  4869. var DEFAULTS = {
  4870. id: '',
  4871. value: null,
  4872. created: null,
  4873. modified: null,
  4874. by: null
  4875. };
  4876. /**
  4877. * @name Comment
  4878. * @class
  4879. * @param spec
  4880. * @constructor
  4881. */
  4882. var Comment = function (spec) {
  4883. spec = spec || {};
  4884. this.ds = spec.ds;
  4885. this.raw = null;
  4886. // the raw json object
  4887. this.id = spec.id || DEFAULTS.id;
  4888. this.value = spec.value || DEFAULTS.value;
  4889. this.created = spec.created || DEFAULTS.created;
  4890. this.modified = spec.modified || DEFAULTS.modified;
  4891. this.by = spec.by || DEFAULTS.by;
  4892. this.fromReservation = spec.fromReservation || false;
  4893. };
  4894. /**
  4895. * _toJson, makes a dict of the object
  4896. * @method
  4897. * @param options
  4898. * @returns {object}
  4899. * @private
  4900. */
  4901. Comment.prototype._toJson = function (options) {
  4902. return {
  4903. id: this.id,
  4904. value: this.value,
  4905. created: this.created,
  4906. modified: this.modified,
  4907. by: this.by
  4908. };
  4909. };
  4910. /**
  4911. * _fromJson: reads the Comment object from json
  4912. * @method
  4913. * @param {object} data the json response
  4914. * @param {object} options dict
  4915. * @returns promise
  4916. * @private
  4917. */
  4918. Comment.prototype._fromJson = function (data, options) {
  4919. this.raw = data;
  4920. this.id = data.id || DEFAULTS.id;
  4921. this.value = data.value || DEFAULTS.value;
  4922. this.created = data.created || DEFAULTS.created;
  4923. this.modified = data.modified || DEFAULTS.modified;
  4924. this.by = data.by || DEFAULTS.by;
  4925. return $.Deferred().resolve(data);
  4926. };
  4927. return Comment;
  4928. }(jquery);
  4929. attachment = function ($) {
  4930. var EXT = /(?:\.([^.]+))?$/;
  4931. var IMAGES = [
  4932. 'jpg',
  4933. 'jpeg',
  4934. 'png',
  4935. 'gif'
  4936. ];
  4937. var PREVIEWS = [
  4938. 'jpg',
  4939. 'jpeg',
  4940. 'png',
  4941. 'gif',
  4942. 'doc',
  4943. 'docx',
  4944. 'pdf'
  4945. ];
  4946. var DEFAULTS = {
  4947. fileName: '',
  4948. fileSize: 0,
  4949. isCover: false,
  4950. canBeCover: true
  4951. };
  4952. /**
  4953. * @name Attachment
  4954. * @class
  4955. * @property {ApiDataSource} ds attachments datasource
  4956. * @property {bool} isCover is this the cover image of a document
  4957. * @property {bool} canBeCover can this attachment be the cover of a document?
  4958. * @constructor
  4959. */
  4960. var Attachment = function (spec) {
  4961. spec = spec || {};
  4962. this.ds = spec.ds;
  4963. this.raw = null;
  4964. // the raw json object
  4965. this.fileName = spec.fileName || DEFAULTS.fileName;
  4966. this.fileSize = spec.fileSize || DEFAULTS.fileSize;
  4967. this.value = spec.value || DEFAULTS.value;
  4968. this.created = spec.created || DEFAULTS.created;
  4969. this.by = spec.by || DEFAULTS.by;
  4970. this.isCover = spec.isCover != null ? spec.isCover : DEFAULTS.isCover;
  4971. this.canBeCover = spec.canBeCover != null ? spec.canBeCover : DEFAULTS.canBeCover;
  4972. };
  4973. /**
  4974. * Gets the url of a thumbnail
  4975. * "XS": 32,
  4976. * "S": 64,
  4977. * "M": 128,
  4978. * "L": 256,
  4979. * "XL": 512
  4980. * "orig": original size
  4981. * @name Attachment#getThumbnailUrl
  4982. * @method
  4983. * @param size
  4984. * @returns {string}
  4985. */
  4986. Attachment.prototype.getThumbnailUrl = function (size) {
  4987. return this.hasPreview() ? this.helper.getImageUrl(this.ds, this.value, size || 'S') : '';
  4988. };
  4989. /**
  4990. * Gets the url where the attachment can be downloaded
  4991. * @name Attachment#getDownloadUrl
  4992. * @method
  4993. * @returns {string}
  4994. */
  4995. Attachment.prototype.getDownloadUrl = function () {
  4996. return this.ds.getBaseUrl() + this.value + '?download=True';
  4997. };
  4998. /**
  4999. * Gets the extension part of a filename
  5000. * @name Attachment#getExt
  5001. * @method
  5002. * @param fileName
  5003. * @returns {string}
  5004. */
  5005. Attachment.prototype.getExt = function (fileName) {
  5006. fileName = fileName || this.fileName;
  5007. return (EXT.exec(fileName)[1] || '').toLowerCase();
  5008. };
  5009. /**
  5010. * Gets a friendly file size
  5011. * @param {int} size
  5012. * @return {string}
  5013. */
  5014. Attachment.prototype.getFriendlyFileSize = function () {
  5015. var size = this.fileSize;
  5016. if (isNaN(size))
  5017. size = 0;
  5018. if (size < 1024)
  5019. return size + ' Bytes';
  5020. size /= 1024;
  5021. if (size < 1024)
  5022. return size.toFixed(2) + ' Kb';
  5023. size /= 1024;
  5024. if (size < 1024)
  5025. return size.toFixed(2) + ' Mb';
  5026. size /= 1024;
  5027. if (size < 1024)
  5028. return size.toFixed(2) + ' Gb';
  5029. size /= 1024;
  5030. return size.toFixed(2) + ' Tb';
  5031. };
  5032. /**
  5033. * Checks if the attachment is an image
  5034. * @name Attachment#isImage
  5035. * @method
  5036. * @returns {boolean}
  5037. */
  5038. Attachment.prototype.isImage = function () {
  5039. var ext = this.getExt(this.fileName);
  5040. return $.inArray(ext, IMAGES) >= 0;
  5041. };
  5042. /**
  5043. * Checks if the attachment has a preview
  5044. * @name Attachment#hasPreview
  5045. * @method
  5046. * @returns {boolean}
  5047. */
  5048. Attachment.prototype.hasPreview = function () {
  5049. var ext = this.getExt(this.fileName);
  5050. return $.inArray(ext, PREVIEWS) >= 0;
  5051. };
  5052. /**
  5053. * _toJson, makes a dict of the object
  5054. * @method
  5055. * @param options
  5056. * @returns {object}
  5057. * @private
  5058. */
  5059. Attachment.prototype._toJson = function (options) {
  5060. return {
  5061. fileName: this.fileName,
  5062. fileSize: this.fileSize,
  5063. value: this.value,
  5064. created: this.created,
  5065. by: this.by
  5066. };
  5067. };
  5068. /**
  5069. * _fromJson: reads the Attachment object from json
  5070. * @method
  5071. * @param {object} data the json response
  5072. * @param {object} options dict
  5073. * @returns promise
  5074. * @private
  5075. */
  5076. Attachment.prototype._fromJson = function (data, options) {
  5077. this.raw = data;
  5078. this.fileName = data.fileName || DEFAULTS.fileName;
  5079. this.fileSize = data.fileSize || DEFAULTS.fileSize;
  5080. this.value = data.value || DEFAULTS.value;
  5081. this.created = data.created || DEFAULTS.created;
  5082. this.by = data.by || DEFAULTS.by;
  5083. return $.Deferred().resolve(data);
  5084. };
  5085. return Attachment;
  5086. }(jquery);
  5087. field = function ($) {
  5088. var DEFAULTS = {
  5089. name: null,
  5090. value: null,
  5091. required: false,
  5092. unit: '',
  5093. kind: 'string',
  5094. form: false,
  5095. editor: null,
  5096. description: ''
  5097. };
  5098. /**
  5099. * @name Field
  5100. * @class
  5101. * @param spec
  5102. * @constructor
  5103. */
  5104. var Field = function (spec) {
  5105. spec = spec || {};
  5106. this.raw = spec;
  5107. this.name = spec.name || DEFAULTS.name;
  5108. this.value = spec.value || DEFAULTS.value;
  5109. this.required = spec.required || DEFAULTS.required;
  5110. this.unit = spec.unit || DEFAULTS.unit;
  5111. this.kind = spec.kind || DEFAULTS.kind;
  5112. this.form = spec.form || DEFAULTS.form;
  5113. this.editor = spec.editor || DEFAULTS.editor;
  5114. this.description = spec.description || DEFAULTS.description;
  5115. };
  5116. /**
  5117. * isValid
  5118. * @name Field#isValid
  5119. * @method
  5120. * @returns {boolean}
  5121. */
  5122. Field.prototype.isValid = function () {
  5123. if (!this.required)
  5124. return true;
  5125. return $.trim(this.value) != '';
  5126. };
  5127. /**
  5128. * isDirty
  5129. * @name Field#isDirty
  5130. * @method
  5131. * @returns {boolean}
  5132. */
  5133. Field.prototype.isDirty = function () {
  5134. return this.raw.value != this.value;
  5135. };
  5136. /**
  5137. * isEmpty
  5138. * @name Field#isEmpty
  5139. * @method
  5140. * @returns {boolean}
  5141. */
  5142. Field.prototype.isEmpty = function () {
  5143. return $.trim(this.value) == '';
  5144. };
  5145. return Field;
  5146. }(jquery);
  5147. Base = function ($, common, api, Document, Comment, Attachment, Field) {
  5148. // Some constant values
  5149. var DEFAULTS = {
  5150. id: '',
  5151. modified: null,
  5152. cover: null,
  5153. flag: null,
  5154. label: null,
  5155. fields: {},
  5156. comments: [],
  5157. attachments: [],
  5158. barcodes: []
  5159. };
  5160. // Allow overriding the ctor during inheritance
  5161. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  5162. var tmp = function () {
  5163. };
  5164. tmp.prototype = Document.prototype;
  5165. /**
  5166. * @name Base
  5167. * @class
  5168. * @property {ApiDataSource} dsAttachments attachments datasource
  5169. * @property {string} crtype e.g. cheqroom.types.customer
  5170. * @property {moment} modified last modified timestamp
  5171. * @property {string} flag the document flag
  5172. * @property {object} fields dictionary of document fields
  5173. * @property {array} comments array of Comment objects
  5174. * @property {array} attachments array of Attachment objects
  5175. * @property {string} cover cover attachment id, default null
  5176. * @constructor
  5177. * @extends Document
  5178. */
  5179. var Base = function (opt) {
  5180. var spec = $.extend({}, opt);
  5181. Document.call(this, spec);
  5182. this.dsAttachments = spec.dsAttachments;
  5183. // ApiDataSource for the attachments coll
  5184. this.crtype = spec.crtype;
  5185. // e.g. cheqroom.types.customer
  5186. this.modified = spec.modified || DEFAULTS.modified;
  5187. // last modified timestamp in momentjs
  5188. this.flag = spec.flag || DEFAULTS.flag;
  5189. // flag
  5190. this.fields = spec.fields || $.extend({}, DEFAULTS.fields);
  5191. // fields dictionary
  5192. this.comments = spec.comments || DEFAULTS.comments.slice();
  5193. // comments array
  5194. this.attachments = spec.attachments || DEFAULTS.attachments.slice();
  5195. // attachments array
  5196. this.cover = spec.cover || DEFAULTS.cover;
  5197. // cover attachment id, default null
  5198. this.barcodes = spec.barcodes || DEFAULTS.barcodes.slice();
  5199. // barcodes array
  5200. this.label = spec.label || DEFAULTS.label; // color label
  5201. };
  5202. Base.prototype = new tmp();
  5203. Base.prototype.constructor = Base;
  5204. //
  5205. // Document overrides
  5206. //
  5207. Base.prototype._getDefaults = function () {
  5208. return DEFAULTS;
  5209. };
  5210. /**
  5211. * Checks if the object is empty
  5212. * after calling reset() isEmpty() should return true
  5213. * We'll only check for fields, comments, attachments here
  5214. * @name Base#isEmpty
  5215. * @method
  5216. * @returns {boolean}
  5217. * @override
  5218. */
  5219. Base.prototype.isEmpty = function () {
  5220. return this.flag == DEFAULTS.flag && (this.fields == null || Object.keys(this.fields).length == 0) && (this.comments == null || this.comments.length == 0) && (this.attachments == null || this.attachments.length == 0);
  5221. };
  5222. /**
  5223. * Checks if the base is dirty and needs saving
  5224. * @name Base#isDirty
  5225. * @returns {boolean}
  5226. */
  5227. Base.prototype.isDirty = function () {
  5228. return this._isDirtyFlag() || this._isDirtyFields();
  5229. };
  5230. /**
  5231. * Checks via the api if we can delete the document
  5232. * @name Base#canDelete
  5233. * @method
  5234. * @returns {promise}
  5235. * @override
  5236. */
  5237. Base.prototype.canDelete = function () {
  5238. // Documents can only be deleted when they have a pk
  5239. if (this.existsInDb()) {
  5240. return this.ds.call(this.id, 'canDelete');
  5241. } else {
  5242. return $.Deferred().resolve({
  5243. result: false,
  5244. message: ''
  5245. });
  5246. }
  5247. };
  5248. // Comments
  5249. // ----
  5250. /**
  5251. * Adds a comment by string
  5252. * @name Base#addComment
  5253. * @method
  5254. * @param comment
  5255. * @param skipRead
  5256. * @returns {promise}
  5257. */
  5258. Base.prototype.addComment = function (comment, skipRead) {
  5259. return this._doApiCall({
  5260. method: 'addComment',
  5261. params: { comment: comment },
  5262. skipRead: skipRead
  5263. });
  5264. };
  5265. /**
  5266. * Updates a comment by id
  5267. * @name Base#updateComment
  5268. * @method
  5269. * @param id
  5270. * @param comment
  5271. * @param skipRead
  5272. * @returns {promise}
  5273. */
  5274. Base.prototype.updateComment = function (id, comment, skipRead) {
  5275. return this._doApiCall({
  5276. method: 'updateComment',
  5277. params: {
  5278. commentId: id,
  5279. comment: comment
  5280. },
  5281. skipRead: skipRead
  5282. });
  5283. };
  5284. /**
  5285. * Deletes a Comment by id
  5286. * @name Base#deleteComment
  5287. * @method
  5288. * @param id
  5289. * @param skipRead
  5290. * @returns {promise}
  5291. */
  5292. Base.prototype.deleteComment = function (id, skipRead) {
  5293. return this._doApiCall({
  5294. method: 'removeComment',
  5295. params: { commentId: id },
  5296. skipRead: skipRead
  5297. });
  5298. };
  5299. // Field stuff
  5300. // ----
  5301. /**
  5302. * Sets multiple custom fields in a single call
  5303. * @name Base#setFields
  5304. * @method
  5305. * @param fields
  5306. * @param skipRead
  5307. * @returns {promise}
  5308. */
  5309. Base.prototype.setFields = function (fields, skipRead) {
  5310. var that = this, changedFields = {};
  5311. $.each(fields, function (key, value) {
  5312. if (that.raw.fields[key] != fields[key]) {
  5313. changedFields[key] = value;
  5314. }
  5315. });
  5316. return this._doApiCall({
  5317. method: 'setFields',
  5318. params: { fields: changedFields },
  5319. skipRead: skipRead,
  5320. usePost: true
  5321. });
  5322. };
  5323. /**
  5324. * Sets a custom field
  5325. * @name Base#setField
  5326. * @method
  5327. * @param field
  5328. * @param value
  5329. * @param skipRead
  5330. * @returns {promise}
  5331. */
  5332. Base.prototype.setField = function (field, value, skipRead) {
  5333. return this._doApiCall({
  5334. method: 'setField',
  5335. params: {
  5336. field: field,
  5337. value: value
  5338. },
  5339. skipRead: skipRead
  5340. });
  5341. };
  5342. /**
  5343. * Clears a custom field
  5344. * @name Base#clearField
  5345. * @method
  5346. * @param field
  5347. * @param skipRead
  5348. */
  5349. Base.prototype.clearField = function (field, skipRead) {
  5350. return this._doApiCall({
  5351. method: 'clearField',
  5352. params: { field: field },
  5353. skipRead: skipRead
  5354. });
  5355. };
  5356. /**
  5357. * Adds a barcode
  5358. * @name Base#addBarcode
  5359. * @param code
  5360. * @param skipRead
  5361. * @returns {promise}
  5362. */
  5363. Base.prototype.addBarcode = function (code, skipRead) {
  5364. return this._doApiCall({
  5365. method: 'addBarcode',
  5366. params: { barcode: code },
  5367. skipRead: skipRead
  5368. });
  5369. };
  5370. /**
  5371. * Removes a barcode
  5372. * @name Item#removeBarcode
  5373. * @param code
  5374. * @param skipRead
  5375. * @returns {promise}
  5376. */
  5377. Base.prototype.removeBarcode = function (code, skipRead) {
  5378. return this._doApiCall({
  5379. method: 'removeBarcode',
  5380. params: { barcode: code },
  5381. skipRead: skipRead
  5382. });
  5383. };
  5384. // Attachments stuff
  5385. // ----
  5386. /**
  5387. * Gets an url for a user avatar
  5388. * 'XS': (64, 64),
  5389. * 'S': (128, 128),
  5390. * 'M': (256, 256),
  5391. * 'L': (512, 512)
  5392. * @param size {string} default null is original size
  5393. * @param groupId {string} Group primary key (only when you're passing an attachment)
  5394. * @param att {string} attachment primary key, by default we take the cover
  5395. * @param bustCache {boolean}
  5396. * @returns {string}
  5397. */
  5398. Base.prototype.getImageUrl = function (size, groupId, att, bustCache) {
  5399. var attachment = att || this.cover;
  5400. return attachment != null && attachment.length > 0 ? this.helper.getImageCDNUrl(groupId, attachment, size) : this.helper.getImageUrl(this.ds, this.id, size, bustCache);
  5401. };
  5402. /**
  5403. * Set the cover image to an Attachment
  5404. * @name Base#setCover
  5405. * @method
  5406. * @param att
  5407. * @param skipRead
  5408. * @returns {promise}
  5409. */
  5410. Base.prototype.setCover = function (att, skipRead) {
  5411. return this._doApiCall({
  5412. method: 'setCover',
  5413. params: { attachmentId: att._id },
  5414. skipRead: skipRead
  5415. });
  5416. };
  5417. /**
  5418. * Clears the cover image
  5419. * @name Base#clearCover
  5420. * @method
  5421. * @param skipRead
  5422. * @returns {promise}
  5423. */
  5424. Base.prototype.clearCover = function (skipRead) {
  5425. return this._doApiCall({
  5426. method: 'clearCover',
  5427. params: {},
  5428. skipRead: skipRead
  5429. });
  5430. };
  5431. /**
  5432. * attaches an Attachment object
  5433. * @name Base#attach
  5434. * @method
  5435. * @param attachmentId
  5436. * @param skipRead
  5437. * @returns {promise}
  5438. */
  5439. Base.prototype.attach = function (attachmentId, skipRead) {
  5440. if (this.existsInDb()) {
  5441. return this._doApiCall({
  5442. method: 'attach',
  5443. params: { attachments: [attachmentId] },
  5444. skipRead: skipRead
  5445. });
  5446. } else {
  5447. return $.Deferred().reject(new api.ApiError('Cannot attach attachment, id is empty or null'));
  5448. }
  5449. };
  5450. /**
  5451. * detaches an Attachment by kvId (guid)
  5452. * @name Base#detach
  5453. * @method
  5454. * @param attachmentId
  5455. * @param skipRead
  5456. * @returns {promise}
  5457. */
  5458. Base.prototype.detach = function (attachmentId, skipRead) {
  5459. if (this.existsInDb()) {
  5460. return this._doApiCall({
  5461. method: 'detach',
  5462. params: { attachments: [attachmentId] },
  5463. skipRead: skipRead
  5464. });
  5465. } else {
  5466. return $.Deferred().reject(new api.ApiError('Cannot detach attachment, id is empty or null'));
  5467. }
  5468. };
  5469. // Flags stuff
  5470. // ----
  5471. /**
  5472. * Sets the flag of an item
  5473. * @name Base#setFlag
  5474. * @param flag
  5475. * @param skipRead
  5476. * @returns {promise}
  5477. */
  5478. Base.prototype.setFlag = function (flag, skipRead) {
  5479. return this._doApiCall({
  5480. method: 'setFlag',
  5481. params: { flag: flag },
  5482. skipRead: skipRead
  5483. });
  5484. };
  5485. /**
  5486. * Clears the flag of an item
  5487. * @name Base#clearFlag
  5488. * @param skipRead
  5489. * @returns {promise}
  5490. */
  5491. Base.prototype.clearFlag = function (skipRead) {
  5492. return this._doApiCall({
  5493. method: 'clearFlag',
  5494. params: {},
  5495. skipRead: skipRead
  5496. });
  5497. };
  5498. /**
  5499. * Sets the label of an item
  5500. * @name Base#setLabel
  5501. * @param labelId
  5502. * @param skipRead
  5503. * @returns {promise}
  5504. */
  5505. Base.prototype.setLabel = function (labelId, skipRead) {
  5506. return this._doApiCall({
  5507. method: 'setLabel',
  5508. params: { labelId: labelId },
  5509. skipRead: skipRead
  5510. });
  5511. };
  5512. /**
  5513. * Clears the label of an item
  5514. * @name Base#clearLabel
  5515. * @param skipRead
  5516. * @returns {promise}
  5517. */
  5518. Base.prototype.clearLabel = function (skipRead) {
  5519. return this._doApiCall({
  5520. method: 'clearLabel',
  5521. params: {},
  5522. skipRead: skipRead
  5523. });
  5524. };
  5525. /**
  5526. * Returns a list of Field objects
  5527. * @param fieldDefs array of field definitions
  5528. * @param onlyFormFields should return only form fields
  5529. * @param limit return no more than x fields
  5530. * @return {Array}
  5531. */
  5532. Base.prototype.getSortedFields = function (fieldDefs, onlyFormFields, limit) {
  5533. var that = this, fields = [], fieldDef = null, fieldValue = null;
  5534. // Work on copy of fieldDefs array
  5535. fieldDefs = fieldDefs.slice();
  5536. // Return only form field definitions?
  5537. fieldDefs = fieldDefs.filter(function (def) {
  5538. return onlyFormFields == true ? def.form : true;
  5539. });
  5540. // Create a Field object for each field definition
  5541. for (var i = 0; i < fieldDefs.length; i++) {
  5542. fieldDef = fieldDefs[i];
  5543. fieldValue = that.fields[fieldDef.name] || '';
  5544. if (limit == null || limit > fields.length) {
  5545. fields.push(that._getField($.extend({ value: fieldValue }, fieldDef)));
  5546. }
  5547. }
  5548. return fields;
  5549. };
  5550. /**
  5551. * Update item fields based on the given Field objects
  5552. * @param {Array} fields array of Field objects
  5553. */
  5554. Base.prototype.setSortedFields = function (fields) {
  5555. for (var i = 0; i < fields.length; i++) {
  5556. var field = fields[i];
  5557. if (field.isEmpty()) {
  5558. delete this.fields[field.name];
  5559. } else {
  5560. this.fields[field.name] = field.value;
  5561. }
  5562. }
  5563. };
  5564. /**
  5565. * Checks if all item fields are valid
  5566. * @param {Array} fields
  5567. * @return {Boolean}
  5568. */
  5569. Base.prototype.validateSortedFields = function (fields) {
  5570. for (var i = 0; i < fields.length; i++) {
  5571. if (!fields[i].isValid()) {
  5572. return false;
  5573. }
  5574. }
  5575. return true;
  5576. };
  5577. /**
  5578. * Update fields of a document
  5579. * @name Base#updateFields
  5580. * @returns {promise}
  5581. */
  5582. Base.prototype.updateFields = function () {
  5583. return this._updateFields();
  5584. };
  5585. // Implementation
  5586. // ----
  5587. /**
  5588. * Checks if the flag is dirty compared to the raw response
  5589. * @returns {boolean}
  5590. * @private
  5591. */
  5592. Base.prototype._isDirtyFlag = function () {
  5593. if (this.raw) {
  5594. return this.flag != this.raw.flag;
  5595. } else {
  5596. return false;
  5597. }
  5598. };
  5599. /**
  5600. * Checks if the fields are dirty compared to the raw response
  5601. * @returns {boolean}
  5602. * @private
  5603. */
  5604. Base.prototype._isDirtyFields = function () {
  5605. if (this.raw) {
  5606. return !common.areEqual(this.fields, this.raw.fields);
  5607. } else {
  5608. return false;
  5609. }
  5610. };
  5611. /**
  5612. * Runs over the custom fields that are dirty and calls `setField`
  5613. * @returns {*}
  5614. * @private
  5615. */
  5616. Base.prototype._updateFields = function () {
  5617. var calls = [];
  5618. if (this.raw) {
  5619. for (var key in this.fields) {
  5620. if (this.fields[key] != this.raw.fields[key]) {
  5621. calls.push(this.setField(key, this.fields[key], true));
  5622. }
  5623. }
  5624. }
  5625. if (calls.length > 0) {
  5626. return $.when(calls);
  5627. } else {
  5628. return $.Deferred().resolve(this);
  5629. }
  5630. };
  5631. // toJson, fromJson
  5632. // ----
  5633. /**
  5634. * _toJson, makes a dict of params to use during create / update
  5635. * @param options
  5636. * @returns {{}}
  5637. * @private
  5638. */
  5639. Base.prototype._toJson = function (options) {
  5640. return Document.prototype._toJson.call(this, options);
  5641. };
  5642. /**
  5643. * _fromJson: read some basic information
  5644. * @method
  5645. * @param {object} data the json response
  5646. * @param {object} options dict
  5647. * @private
  5648. */
  5649. Base.prototype._fromJson = function (data, options) {
  5650. var that = this;
  5651. return Document.prototype._fromJson.call(this, data, options).then(function () {
  5652. that.flag = data.flag || DEFAULTS.flag;
  5653. that.fields = data.fields != null ? $.extend({}, data.fields) : $.extend({}, DEFAULTS.fields);
  5654. that.modified = data.modified || DEFAULTS.modified;
  5655. that.barcodes = data.barcodes || DEFAULTS.barcodes;
  5656. that.label = data.label || DEFAULTS.label;
  5657. return that._fromCommentsJson(data, options).then(function () {
  5658. return that._fromAttachmentsJson(data, options);
  5659. });
  5660. });
  5661. };
  5662. /**
  5663. * _toJsonFields: makes json which can be used to set fields during `create`
  5664. * @method
  5665. * @param options
  5666. * @returns {{}}
  5667. * @private
  5668. */
  5669. Base.prototype._toJsonFields = function (options) {
  5670. var fields = {};
  5671. if (this.fields) {
  5672. for (var key in this.fields) {
  5673. fields['fields__' + key] = this.fields[key];
  5674. }
  5675. }
  5676. return fields;
  5677. };
  5678. /**
  5679. * _fromCommentsJson: reads the data.comments
  5680. * @param data
  5681. * @param options
  5682. * @returns {*}
  5683. * @private
  5684. */
  5685. Base.prototype._fromCommentsJson = function (data, options) {
  5686. var obj = null, that = this;
  5687. this.comments = DEFAULTS.comments.slice();
  5688. if (data.comments && data.comments.length > 0) {
  5689. $.each(data.comments, function (i, comment) {
  5690. obj = that._getComment(comment, options);
  5691. if (obj) {
  5692. that.comments.push(obj);
  5693. }
  5694. });
  5695. }
  5696. return $.Deferred().resolve(data);
  5697. };
  5698. /**
  5699. * _fromAttachmentsJson: reads the data.attachments
  5700. * @param data
  5701. * @param options
  5702. * @returns {*}
  5703. * @private
  5704. */
  5705. Base.prototype._fromAttachmentsJson = function (data, options) {
  5706. var obj = null, that = this;
  5707. this.attachments = DEFAULTS.attachments.slice();
  5708. if (data.attachments && data.attachments.length > 0) {
  5709. $.each(data.attachments, function (i, att) {
  5710. obj = that._getAttachment(att, options);
  5711. if (obj) {
  5712. that.attachments.push(obj);
  5713. }
  5714. });
  5715. }
  5716. return $.Deferred().resolve(data);
  5717. };
  5718. Base.prototype._getComment = function (data, options) {
  5719. var spec = $.extend({ ds: this.ds }, options || {}, data);
  5720. return new Comment(spec);
  5721. };
  5722. Base.prototype._getAttachment = function (data, options) {
  5723. var spec = $.extend({ ds: this.ds }, options || {}, data);
  5724. return new Attachment(spec);
  5725. };
  5726. Base.prototype._getField = function (data, options) {
  5727. var spec = $.extend({}, options || {}, data);
  5728. return new Field(spec);
  5729. };
  5730. return Base;
  5731. }(jquery, common, api, document, comment, attachment, field);
  5732. Category = function ($, common, api, Document) {
  5733. /*
  5734. id = StringField(primary_key=True) # category id in reverse domain format cheqroom.types.customer
  5735. name = StringField() # A friendly name for the category
  5736. count = IntField(default=0) # How many categories are under this parent
  5737. defs = ListField(EmbeddedDocumentField(KeyValueDef)) # a list of allowed KeyValueDefs
  5738. parent = ReferenceField("Category") # the parent category
  5739. by = ReferenceField(User) # The user that add / updated this meta
  5740. modified = DateTimeField(default=DateHelper.getNow) # The data when it was added / update
  5741. */
  5742. // Some constant values
  5743. var DEFAULTS = {
  5744. id: '',
  5745. name: '',
  5746. count: 0,
  5747. defs: [],
  5748. parent: '',
  5749. modified: null
  5750. };
  5751. // Allow overriding the ctor during inheritance
  5752. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  5753. var tmp = function () {
  5754. };
  5755. tmp.prototype = Document.prototype;
  5756. /**
  5757. * Category describes a category which can trigger on certain events (signals)
  5758. * @name Category
  5759. * @class
  5760. * @property {string} name the category name
  5761. * @property {string} count how many categories are under this node
  5762. * @property {array} defs a list of allowed keyvalue definitions
  5763. * @property {string} parent the primary key of the parent category
  5764. * @property {Moment} modified the modified date of the category
  5765. * @constructor
  5766. * @extends Document
  5767. */
  5768. var Category = function (opt) {
  5769. var spec = $.extend({}, opt);
  5770. Document.call(this, spec);
  5771. this.name = spec.name || DEFAULTS.name;
  5772. this.count = spec.count || DEFAULTS.count;
  5773. this.defs = spec.defs || DEFAULTS.defs.slice();
  5774. this.parent = spec.parent || DEFAULTS.parent;
  5775. this.modified = spec.modified || DEFAULTS.modified;
  5776. };
  5777. Category.prototype = new tmp();
  5778. Category.prototype.constructor = Category;
  5779. //
  5780. // Specific validators
  5781. /**
  5782. * Checks if name is valid
  5783. * @name Category#isValidName
  5784. * @method
  5785. * @return {Boolean}
  5786. */
  5787. Category.prototype.isValidName = function () {
  5788. this.name = $.trim(this.name);
  5789. if (this.name.length >= 3) {
  5790. var nospecial = this.name.latinise().replace(/[`~!@#$%^&*()_|+\-=?;:'",.<\{\}\[\]\\\/]/gi, '');
  5791. return this.name == nospecial;
  5792. } else {
  5793. return false;
  5794. }
  5795. };
  5796. //
  5797. // Document overrides
  5798. //
  5799. /**
  5800. * Checks if the category has any validation errors
  5801. * @name Category#isValid
  5802. * @method
  5803. * @returns {boolean}
  5804. * @override
  5805. */
  5806. Category.prototype.isValid = function () {
  5807. return this.isValidName();
  5808. };
  5809. Category.prototype._getDefaults = function () {
  5810. return DEFAULTS;
  5811. };
  5812. /**
  5813. * Checks if the object is empty, it never is
  5814. * @name Category#isEmpty
  5815. * @method
  5816. * @returns {boolean}
  5817. * @override
  5818. */
  5819. Category.prototype.isEmpty = function () {
  5820. return Document.prototype.isEmpty.call(this) && this.name == DEFAULTS.name;
  5821. };
  5822. /**
  5823. * Checks if the category is dirty and needs saving
  5824. * @returns {boolean}
  5825. * @override
  5826. */
  5827. Category.prototype.isDirty = function () {
  5828. var isDirty = Document.prototype.isDirty.call(this);
  5829. if (!isDirty && this.raw) {
  5830. isDirty = this.name != this.raw.name;
  5831. }
  5832. return isDirty;
  5833. };
  5834. /**
  5835. * Checks via the api if we can delete the Category document
  5836. * @name Category#canDelete
  5837. * @method
  5838. * @returns {promise}
  5839. * @override
  5840. */
  5841. Category.prototype.canDelete = function () {
  5842. return this.ds.call(this.id, 'canDeleteCategory', { omitFields: true });
  5843. };
  5844. /**
  5845. * Checks via the api if we can rename the Category document
  5846. * e.g. if it would not clash with any existing categories
  5847. * @method
  5848. * @param name
  5849. * @returns {promise}
  5850. */
  5851. Category.prototype.canChangeName = function (name) {
  5852. return this.ds.call(this.id, 'canChangeName', { name: name });
  5853. };
  5854. /**
  5855. * Changes the name of a category
  5856. * @method
  5857. * @param name
  5858. * @returns {promise}
  5859. */
  5860. Category.prototype.changeName = function (name) {
  5861. return this._doApiCall({
  5862. pk: this.id,
  5863. method: 'changeName',
  5864. params: { name: name }
  5865. });
  5866. };
  5867. /**
  5868. * Checks via the api if we can change the parent of a Category document
  5869. * e.g. if it would not clash with any existing categories
  5870. * @param parentId
  5871. * @returns {promise}
  5872. */
  5873. Category.prototype.canChangeParent = function (parentId) {
  5874. return this.ds.call(this.id, 'canChangeParent', {
  5875. parent: parentId,
  5876. omitFields: true
  5877. });
  5878. };
  5879. /**
  5880. * Changes the parent of a category
  5881. * @param parentId
  5882. * @returns {promise}
  5883. */
  5884. Category.prototype.changeParent = function (parentId) {
  5885. return this._doApiCall({
  5886. pk: this.id,
  5887. method: 'changeParent',
  5888. params: {
  5889. parent: parentId,
  5890. omitFields: true
  5891. }
  5892. });
  5893. };
  5894. // toJson, fromJson
  5895. // ----
  5896. /**
  5897. * _toJson, makes a dict of params to use during create / update
  5898. * @param options
  5899. * @returns {{}}
  5900. * @private
  5901. */
  5902. Category.prototype._toJson = function (options) {
  5903. var data = Document.prototype._toJson.call(this, options);
  5904. data.name = this.name;
  5905. return data;
  5906. };
  5907. /**
  5908. * _fromJson: read some basic information
  5909. * @method
  5910. * @param {object} data the json response
  5911. * @param {object} options dict
  5912. * @returns {promise}
  5913. * @private
  5914. */
  5915. Category.prototype._fromJson = function (data, options) {
  5916. var that = this;
  5917. return Document.prototype._fromJson.call(this, data, options).then(function () {
  5918. that.name = data.name || DEFAULTS.name;
  5919. that.count = data.count || DEFAULTS.count;
  5920. that.defs = data.defs || DEFAULTS.defs.slice();
  5921. that.parent = data.parent || DEFAULTS.parent;
  5922. that.modified = data.modified || DEFAULTS.modified;
  5923. return data;
  5924. });
  5925. };
  5926. return Category;
  5927. }(jquery, common, api, document);
  5928. Comment = function ($) {
  5929. var DEFAULTS = {
  5930. id: '',
  5931. value: null,
  5932. created: null,
  5933. modified: null,
  5934. by: null
  5935. };
  5936. /**
  5937. * @name Comment
  5938. * @class
  5939. * @param spec
  5940. * @constructor
  5941. */
  5942. var Comment = function (spec) {
  5943. spec = spec || {};
  5944. this.ds = spec.ds;
  5945. this.raw = null;
  5946. // the raw json object
  5947. this.id = spec.id || DEFAULTS.id;
  5948. this.value = spec.value || DEFAULTS.value;
  5949. this.created = spec.created || DEFAULTS.created;
  5950. this.modified = spec.modified || DEFAULTS.modified;
  5951. this.by = spec.by || DEFAULTS.by;
  5952. this.fromReservation = spec.fromReservation || false;
  5953. };
  5954. /**
  5955. * _toJson, makes a dict of the object
  5956. * @method
  5957. * @param options
  5958. * @returns {object}
  5959. * @private
  5960. */
  5961. Comment.prototype._toJson = function (options) {
  5962. return {
  5963. id: this.id,
  5964. value: this.value,
  5965. created: this.created,
  5966. modified: this.modified,
  5967. by: this.by
  5968. };
  5969. };
  5970. /**
  5971. * _fromJson: reads the Comment object from json
  5972. * @method
  5973. * @param {object} data the json response
  5974. * @param {object} options dict
  5975. * @returns promise
  5976. * @private
  5977. */
  5978. Comment.prototype._fromJson = function (data, options) {
  5979. this.raw = data;
  5980. this.id = data.id || DEFAULTS.id;
  5981. this.value = data.value || DEFAULTS.value;
  5982. this.created = data.created || DEFAULTS.created;
  5983. this.modified = data.modified || DEFAULTS.modified;
  5984. this.by = data.by || DEFAULTS.by;
  5985. return $.Deferred().resolve(data);
  5986. };
  5987. return Comment;
  5988. }(jquery);
  5989. Conflict = function ($) {
  5990. var DEFAULTS = {
  5991. kind: '',
  5992. doc: '',
  5993. item: '',
  5994. itemName: '',
  5995. locationCurrent: '',
  5996. locationDesired: '',
  5997. fromDate: null,
  5998. toDate: null
  5999. };
  6000. /**
  6001. * Conflict class
  6002. * @name Conflict
  6003. * @class
  6004. * @constructor
  6005. *
  6006. * @param spec
  6007. * @property {string} kind - The conflict kind (status, order, reservation, location)
  6008. * @property {string} doc - The id of the document with which it conflicts
  6009. * @property {string} item - The Item id for this conflict
  6010. * @property {string} itemName - The Item name for this conflict
  6011. * @property {string} locationCurrent - The Location the item is now
  6012. * @property {string} locationDesired - The Location where the item should be
  6013. * @property {moment} fromDate - From when does the conflict start
  6014. * @property {moment} toDate - Until when does the conflict end
  6015. */
  6016. var Conflict = function (spec) {
  6017. this.ds = spec.ds;
  6018. this._fields = spec._fields;
  6019. this.raw = null;
  6020. // the raw json object
  6021. this.kind = spec.kind || DEFAULTS.kind;
  6022. this.doc = spec.doc || DEFAULTS.doc;
  6023. this.item = spec.item || DEFAULTS.item;
  6024. this.itemName = spec.itemName || DEFAULTS.itemName;
  6025. this.locationCurrent = spec.locationCurrent || DEFAULTS.locationCurrent;
  6026. this.locationDesired = spec.locationDesired || DEFAULTS.locationDesired;
  6027. this.fromDate = spec.fromDate || DEFAULTS.fromDate;
  6028. this.toDate = spec.toDate || DEFAULTS.toDate;
  6029. };
  6030. /**
  6031. * _toJson, makes a dict of the object
  6032. * @method
  6033. * @param {object} opt dict
  6034. * @returns {object}
  6035. * @private
  6036. */
  6037. Conflict.prototype._toJson = function (opt) {
  6038. return {
  6039. kind: this.kind,
  6040. doc: this.doc,
  6041. item: this.item,
  6042. itemName: this.itemName,
  6043. locationCurrent: this.locationCurrent,
  6044. locationDesired: this.locationDesired,
  6045. fromDate: this.fromDate,
  6046. toDate: this.toDate
  6047. };
  6048. };
  6049. /**
  6050. * _fromJson
  6051. * @method
  6052. * @param {object} data the json response
  6053. * @param {object} opt dict
  6054. * @returns promise
  6055. * @private
  6056. */
  6057. Conflict.prototype._fromJson = function (data, opt) {
  6058. this.raw = data;
  6059. this.kind = data.kind || DEFAULTS.kind;
  6060. this.item = data.item || DEFAULTS.item;
  6061. this.itemName = data.itemName || DEFAULTS.itemName;
  6062. this.fromDate = data.fromDate || DEFAULTS.fromDate;
  6063. this.toDate = data.toDate || DEFAULTS.toDate;
  6064. return $.Deferred().resolve(data);
  6065. };
  6066. return Conflict;
  6067. }(jquery);
  6068. base = function ($, common, api, Document, Comment, Attachment, Field) {
  6069. // Some constant values
  6070. var DEFAULTS = {
  6071. id: '',
  6072. modified: null,
  6073. cover: null,
  6074. flag: null,
  6075. label: null,
  6076. fields: {},
  6077. comments: [],
  6078. attachments: [],
  6079. barcodes: []
  6080. };
  6081. // Allow overriding the ctor during inheritance
  6082. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  6083. var tmp = function () {
  6084. };
  6085. tmp.prototype = Document.prototype;
  6086. /**
  6087. * @name Base
  6088. * @class
  6089. * @property {ApiDataSource} dsAttachments attachments datasource
  6090. * @property {string} crtype e.g. cheqroom.types.customer
  6091. * @property {moment} modified last modified timestamp
  6092. * @property {string} flag the document flag
  6093. * @property {object} fields dictionary of document fields
  6094. * @property {array} comments array of Comment objects
  6095. * @property {array} attachments array of Attachment objects
  6096. * @property {string} cover cover attachment id, default null
  6097. * @constructor
  6098. * @extends Document
  6099. */
  6100. var Base = function (opt) {
  6101. var spec = $.extend({}, opt);
  6102. Document.call(this, spec);
  6103. this.dsAttachments = spec.dsAttachments;
  6104. // ApiDataSource for the attachments coll
  6105. this.crtype = spec.crtype;
  6106. // e.g. cheqroom.types.customer
  6107. this.modified = spec.modified || DEFAULTS.modified;
  6108. // last modified timestamp in momentjs
  6109. this.flag = spec.flag || DEFAULTS.flag;
  6110. // flag
  6111. this.fields = spec.fields || $.extend({}, DEFAULTS.fields);
  6112. // fields dictionary
  6113. this.comments = spec.comments || DEFAULTS.comments.slice();
  6114. // comments array
  6115. this.attachments = spec.attachments || DEFAULTS.attachments.slice();
  6116. // attachments array
  6117. this.cover = spec.cover || DEFAULTS.cover;
  6118. // cover attachment id, default null
  6119. this.barcodes = spec.barcodes || DEFAULTS.barcodes.slice();
  6120. // barcodes array
  6121. this.label = spec.label || DEFAULTS.label; // color label
  6122. };
  6123. Base.prototype = new tmp();
  6124. Base.prototype.constructor = Base;
  6125. //
  6126. // Document overrides
  6127. //
  6128. Base.prototype._getDefaults = function () {
  6129. return DEFAULTS;
  6130. };
  6131. /**
  6132. * Checks if the object is empty
  6133. * after calling reset() isEmpty() should return true
  6134. * We'll only check for fields, comments, attachments here
  6135. * @name Base#isEmpty
  6136. * @method
  6137. * @returns {boolean}
  6138. * @override
  6139. */
  6140. Base.prototype.isEmpty = function () {
  6141. return this.flag == DEFAULTS.flag && (this.fields == null || Object.keys(this.fields).length == 0) && (this.comments == null || this.comments.length == 0) && (this.attachments == null || this.attachments.length == 0);
  6142. };
  6143. /**
  6144. * Checks if the base is dirty and needs saving
  6145. * @name Base#isDirty
  6146. * @returns {boolean}
  6147. */
  6148. Base.prototype.isDirty = function () {
  6149. return this._isDirtyFlag() || this._isDirtyFields();
  6150. };
  6151. /**
  6152. * Checks via the api if we can delete the document
  6153. * @name Base#canDelete
  6154. * @method
  6155. * @returns {promise}
  6156. * @override
  6157. */
  6158. Base.prototype.canDelete = function () {
  6159. // Documents can only be deleted when they have a pk
  6160. if (this.existsInDb()) {
  6161. return this.ds.call(this.id, 'canDelete');
  6162. } else {
  6163. return $.Deferred().resolve({
  6164. result: false,
  6165. message: ''
  6166. });
  6167. }
  6168. };
  6169. // Comments
  6170. // ----
  6171. /**
  6172. * Adds a comment by string
  6173. * @name Base#addComment
  6174. * @method
  6175. * @param comment
  6176. * @param skipRead
  6177. * @returns {promise}
  6178. */
  6179. Base.prototype.addComment = function (comment, skipRead) {
  6180. return this._doApiCall({
  6181. method: 'addComment',
  6182. params: { comment: comment },
  6183. skipRead: skipRead
  6184. });
  6185. };
  6186. /**
  6187. * Updates a comment by id
  6188. * @name Base#updateComment
  6189. * @method
  6190. * @param id
  6191. * @param comment
  6192. * @param skipRead
  6193. * @returns {promise}
  6194. */
  6195. Base.prototype.updateComment = function (id, comment, skipRead) {
  6196. return this._doApiCall({
  6197. method: 'updateComment',
  6198. params: {
  6199. commentId: id,
  6200. comment: comment
  6201. },
  6202. skipRead: skipRead
  6203. });
  6204. };
  6205. /**
  6206. * Deletes a Comment by id
  6207. * @name Base#deleteComment
  6208. * @method
  6209. * @param id
  6210. * @param skipRead
  6211. * @returns {promise}
  6212. */
  6213. Base.prototype.deleteComment = function (id, skipRead) {
  6214. return this._doApiCall({
  6215. method: 'removeComment',
  6216. params: { commentId: id },
  6217. skipRead: skipRead
  6218. });
  6219. };
  6220. // Field stuff
  6221. // ----
  6222. /**
  6223. * Sets multiple custom fields in a single call
  6224. * @name Base#setFields
  6225. * @method
  6226. * @param fields
  6227. * @param skipRead
  6228. * @returns {promise}
  6229. */
  6230. Base.prototype.setFields = function (fields, skipRead) {
  6231. var that = this, changedFields = {};
  6232. $.each(fields, function (key, value) {
  6233. if (that.raw.fields[key] != fields[key]) {
  6234. changedFields[key] = value;
  6235. }
  6236. });
  6237. return this._doApiCall({
  6238. method: 'setFields',
  6239. params: { fields: changedFields },
  6240. skipRead: skipRead,
  6241. usePost: true
  6242. });
  6243. };
  6244. /**
  6245. * Sets a custom field
  6246. * @name Base#setField
  6247. * @method
  6248. * @param field
  6249. * @param value
  6250. * @param skipRead
  6251. * @returns {promise}
  6252. */
  6253. Base.prototype.setField = function (field, value, skipRead) {
  6254. return this._doApiCall({
  6255. method: 'setField',
  6256. params: {
  6257. field: field,
  6258. value: value
  6259. },
  6260. skipRead: skipRead
  6261. });
  6262. };
  6263. /**
  6264. * Clears a custom field
  6265. * @name Base#clearField
  6266. * @method
  6267. * @param field
  6268. * @param skipRead
  6269. */
  6270. Base.prototype.clearField = function (field, skipRead) {
  6271. return this._doApiCall({
  6272. method: 'clearField',
  6273. params: { field: field },
  6274. skipRead: skipRead
  6275. });
  6276. };
  6277. /**
  6278. * Adds a barcode
  6279. * @name Base#addBarcode
  6280. * @param code
  6281. * @param skipRead
  6282. * @returns {promise}
  6283. */
  6284. Base.prototype.addBarcode = function (code, skipRead) {
  6285. return this._doApiCall({
  6286. method: 'addBarcode',
  6287. params: { barcode: code },
  6288. skipRead: skipRead
  6289. });
  6290. };
  6291. /**
  6292. * Removes a barcode
  6293. * @name Item#removeBarcode
  6294. * @param code
  6295. * @param skipRead
  6296. * @returns {promise}
  6297. */
  6298. Base.prototype.removeBarcode = function (code, skipRead) {
  6299. return this._doApiCall({
  6300. method: 'removeBarcode',
  6301. params: { barcode: code },
  6302. skipRead: skipRead
  6303. });
  6304. };
  6305. // Attachments stuff
  6306. // ----
  6307. /**
  6308. * Gets an url for a user avatar
  6309. * 'XS': (64, 64),
  6310. * 'S': (128, 128),
  6311. * 'M': (256, 256),
  6312. * 'L': (512, 512)
  6313. * @param size {string} default null is original size
  6314. * @param groupId {string} Group primary key (only when you're passing an attachment)
  6315. * @param att {string} attachment primary key, by default we take the cover
  6316. * @param bustCache {boolean}
  6317. * @returns {string}
  6318. */
  6319. Base.prototype.getImageUrl = function (size, groupId, att, bustCache) {
  6320. var attachment = att || this.cover;
  6321. return attachment != null && attachment.length > 0 ? this.helper.getImageCDNUrl(groupId, attachment, size) : this.helper.getImageUrl(this.ds, this.id, size, bustCache);
  6322. };
  6323. /**
  6324. * Set the cover image to an Attachment
  6325. * @name Base#setCover
  6326. * @method
  6327. * @param att
  6328. * @param skipRead
  6329. * @returns {promise}
  6330. */
  6331. Base.prototype.setCover = function (att, skipRead) {
  6332. return this._doApiCall({
  6333. method: 'setCover',
  6334. params: { attachmentId: att._id },
  6335. skipRead: skipRead
  6336. });
  6337. };
  6338. /**
  6339. * Clears the cover image
  6340. * @name Base#clearCover
  6341. * @method
  6342. * @param skipRead
  6343. * @returns {promise}
  6344. */
  6345. Base.prototype.clearCover = function (skipRead) {
  6346. return this._doApiCall({
  6347. method: 'clearCover',
  6348. params: {},
  6349. skipRead: skipRead
  6350. });
  6351. };
  6352. /**
  6353. * attaches an Attachment object
  6354. * @name Base#attach
  6355. * @method
  6356. * @param attachmentId
  6357. * @param skipRead
  6358. * @returns {promise}
  6359. */
  6360. Base.prototype.attach = function (attachmentId, skipRead) {
  6361. if (this.existsInDb()) {
  6362. return this._doApiCall({
  6363. method: 'attach',
  6364. params: { attachments: [attachmentId] },
  6365. skipRead: skipRead
  6366. });
  6367. } else {
  6368. return $.Deferred().reject(new api.ApiError('Cannot attach attachment, id is empty or null'));
  6369. }
  6370. };
  6371. /**
  6372. * detaches an Attachment by kvId (guid)
  6373. * @name Base#detach
  6374. * @method
  6375. * @param attachmentId
  6376. * @param skipRead
  6377. * @returns {promise}
  6378. */
  6379. Base.prototype.detach = function (attachmentId, skipRead) {
  6380. if (this.existsInDb()) {
  6381. return this._doApiCall({
  6382. method: 'detach',
  6383. params: { attachments: [attachmentId] },
  6384. skipRead: skipRead
  6385. });
  6386. } else {
  6387. return $.Deferred().reject(new api.ApiError('Cannot detach attachment, id is empty or null'));
  6388. }
  6389. };
  6390. // Flags stuff
  6391. // ----
  6392. /**
  6393. * Sets the flag of an item
  6394. * @name Base#setFlag
  6395. * @param flag
  6396. * @param skipRead
  6397. * @returns {promise}
  6398. */
  6399. Base.prototype.setFlag = function (flag, skipRead) {
  6400. return this._doApiCall({
  6401. method: 'setFlag',
  6402. params: { flag: flag },
  6403. skipRead: skipRead
  6404. });
  6405. };
  6406. /**
  6407. * Clears the flag of an item
  6408. * @name Base#clearFlag
  6409. * @param skipRead
  6410. * @returns {promise}
  6411. */
  6412. Base.prototype.clearFlag = function (skipRead) {
  6413. return this._doApiCall({
  6414. method: 'clearFlag',
  6415. params: {},
  6416. skipRead: skipRead
  6417. });
  6418. };
  6419. /**
  6420. * Sets the label of an item
  6421. * @name Base#setLabel
  6422. * @param labelId
  6423. * @param skipRead
  6424. * @returns {promise}
  6425. */
  6426. Base.prototype.setLabel = function (labelId, skipRead) {
  6427. return this._doApiCall({
  6428. method: 'setLabel',
  6429. params: { labelId: labelId },
  6430. skipRead: skipRead
  6431. });
  6432. };
  6433. /**
  6434. * Clears the label of an item
  6435. * @name Base#clearLabel
  6436. * @param skipRead
  6437. * @returns {promise}
  6438. */
  6439. Base.prototype.clearLabel = function (skipRead) {
  6440. return this._doApiCall({
  6441. method: 'clearLabel',
  6442. params: {},
  6443. skipRead: skipRead
  6444. });
  6445. };
  6446. /**
  6447. * Returns a list of Field objects
  6448. * @param fieldDefs array of field definitions
  6449. * @param onlyFormFields should return only form fields
  6450. * @param limit return no more than x fields
  6451. * @return {Array}
  6452. */
  6453. Base.prototype.getSortedFields = function (fieldDefs, onlyFormFields, limit) {
  6454. var that = this, fields = [], fieldDef = null, fieldValue = null;
  6455. // Work on copy of fieldDefs array
  6456. fieldDefs = fieldDefs.slice();
  6457. // Return only form field definitions?
  6458. fieldDefs = fieldDefs.filter(function (def) {
  6459. return onlyFormFields == true ? def.form : true;
  6460. });
  6461. // Create a Field object for each field definition
  6462. for (var i = 0; i < fieldDefs.length; i++) {
  6463. fieldDef = fieldDefs[i];
  6464. fieldValue = that.fields[fieldDef.name] || '';
  6465. if (limit == null || limit > fields.length) {
  6466. fields.push(that._getField($.extend({ value: fieldValue }, fieldDef)));
  6467. }
  6468. }
  6469. return fields;
  6470. };
  6471. /**
  6472. * Update item fields based on the given Field objects
  6473. * @param {Array} fields array of Field objects
  6474. */
  6475. Base.prototype.setSortedFields = function (fields) {
  6476. for (var i = 0; i < fields.length; i++) {
  6477. var field = fields[i];
  6478. if (field.isEmpty()) {
  6479. delete this.fields[field.name];
  6480. } else {
  6481. this.fields[field.name] = field.value;
  6482. }
  6483. }
  6484. };
  6485. /**
  6486. * Checks if all item fields are valid
  6487. * @param {Array} fields
  6488. * @return {Boolean}
  6489. */
  6490. Base.prototype.validateSortedFields = function (fields) {
  6491. for (var i = 0; i < fields.length; i++) {
  6492. if (!fields[i].isValid()) {
  6493. return false;
  6494. }
  6495. }
  6496. return true;
  6497. };
  6498. /**
  6499. * Update fields of a document
  6500. * @name Base#updateFields
  6501. * @returns {promise}
  6502. */
  6503. Base.prototype.updateFields = function () {
  6504. return this._updateFields();
  6505. };
  6506. // Implementation
  6507. // ----
  6508. /**
  6509. * Checks if the flag is dirty compared to the raw response
  6510. * @returns {boolean}
  6511. * @private
  6512. */
  6513. Base.prototype._isDirtyFlag = function () {
  6514. if (this.raw) {
  6515. return this.flag != this.raw.flag;
  6516. } else {
  6517. return false;
  6518. }
  6519. };
  6520. /**
  6521. * Checks if the fields are dirty compared to the raw response
  6522. * @returns {boolean}
  6523. * @private
  6524. */
  6525. Base.prototype._isDirtyFields = function () {
  6526. if (this.raw) {
  6527. return !common.areEqual(this.fields, this.raw.fields);
  6528. } else {
  6529. return false;
  6530. }
  6531. };
  6532. /**
  6533. * Runs over the custom fields that are dirty and calls `setField`
  6534. * @returns {*}
  6535. * @private
  6536. */
  6537. Base.prototype._updateFields = function () {
  6538. var calls = [];
  6539. if (this.raw) {
  6540. for (var key in this.fields) {
  6541. if (this.fields[key] != this.raw.fields[key]) {
  6542. calls.push(this.setField(key, this.fields[key], true));
  6543. }
  6544. }
  6545. }
  6546. if (calls.length > 0) {
  6547. return $.when(calls);
  6548. } else {
  6549. return $.Deferred().resolve(this);
  6550. }
  6551. };
  6552. // toJson, fromJson
  6553. // ----
  6554. /**
  6555. * _toJson, makes a dict of params to use during create / update
  6556. * @param options
  6557. * @returns {{}}
  6558. * @private
  6559. */
  6560. Base.prototype._toJson = function (options) {
  6561. return Document.prototype._toJson.call(this, options);
  6562. };
  6563. /**
  6564. * _fromJson: read some basic information
  6565. * @method
  6566. * @param {object} data the json response
  6567. * @param {object} options dict
  6568. * @private
  6569. */
  6570. Base.prototype._fromJson = function (data, options) {
  6571. var that = this;
  6572. return Document.prototype._fromJson.call(this, data, options).then(function () {
  6573. that.flag = data.flag || DEFAULTS.flag;
  6574. that.fields = data.fields != null ? $.extend({}, data.fields) : $.extend({}, DEFAULTS.fields);
  6575. that.modified = data.modified || DEFAULTS.modified;
  6576. that.barcodes = data.barcodes || DEFAULTS.barcodes;
  6577. that.label = data.label || DEFAULTS.label;
  6578. return that._fromCommentsJson(data, options).then(function () {
  6579. return that._fromAttachmentsJson(data, options);
  6580. });
  6581. });
  6582. };
  6583. /**
  6584. * _toJsonFields: makes json which can be used to set fields during `create`
  6585. * @method
  6586. * @param options
  6587. * @returns {{}}
  6588. * @private
  6589. */
  6590. Base.prototype._toJsonFields = function (options) {
  6591. var fields = {};
  6592. if (this.fields) {
  6593. for (var key in this.fields) {
  6594. fields['fields__' + key] = this.fields[key];
  6595. }
  6596. }
  6597. return fields;
  6598. };
  6599. /**
  6600. * _fromCommentsJson: reads the data.comments
  6601. * @param data
  6602. * @param options
  6603. * @returns {*}
  6604. * @private
  6605. */
  6606. Base.prototype._fromCommentsJson = function (data, options) {
  6607. var obj = null, that = this;
  6608. this.comments = DEFAULTS.comments.slice();
  6609. if (data.comments && data.comments.length > 0) {
  6610. $.each(data.comments, function (i, comment) {
  6611. obj = that._getComment(comment, options);
  6612. if (obj) {
  6613. that.comments.push(obj);
  6614. }
  6615. });
  6616. }
  6617. return $.Deferred().resolve(data);
  6618. };
  6619. /**
  6620. * _fromAttachmentsJson: reads the data.attachments
  6621. * @param data
  6622. * @param options
  6623. * @returns {*}
  6624. * @private
  6625. */
  6626. Base.prototype._fromAttachmentsJson = function (data, options) {
  6627. var obj = null, that = this;
  6628. this.attachments = DEFAULTS.attachments.slice();
  6629. if (data.attachments && data.attachments.length > 0) {
  6630. $.each(data.attachments, function (i, att) {
  6631. obj = that._getAttachment(att, options);
  6632. if (obj) {
  6633. that.attachments.push(obj);
  6634. }
  6635. });
  6636. }
  6637. return $.Deferred().resolve(data);
  6638. };
  6639. Base.prototype._getComment = function (data, options) {
  6640. var spec = $.extend({ ds: this.ds }, options || {}, data);
  6641. return new Comment(spec);
  6642. };
  6643. Base.prototype._getAttachment = function (data, options) {
  6644. var spec = $.extend({ ds: this.ds }, options || {}, data);
  6645. return new Attachment(spec);
  6646. };
  6647. Base.prototype._getField = function (data, options) {
  6648. var spec = $.extend({}, options || {}, data);
  6649. return new Field(spec);
  6650. };
  6651. return Base;
  6652. }(jquery, common, api, document, comment, attachment, field);
  6653. user = function ($, Base, common) {
  6654. var DEFAULTS = {
  6655. name: '',
  6656. email: '',
  6657. group: '',
  6658. // groupid
  6659. picture: '',
  6660. role: 'user',
  6661. // user, admin
  6662. active: true,
  6663. isOwner: false,
  6664. archived: null,
  6665. restrictLocations: []
  6666. };
  6667. // Allow overriding the ctor during inheritance
  6668. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  6669. var tmp = function () {
  6670. };
  6671. tmp.prototype = Base.prototype;
  6672. /**
  6673. * @name User
  6674. * @class User
  6675. * @constructor
  6676. * @extends Base
  6677. * @property {string} name - The name
  6678. * @property {string} role - The role (admin, user)
  6679. * @property {boolean} active - Is the user active?
  6680. */
  6681. var User = function (opt) {
  6682. var spec = $.extend({
  6683. _fields: [
  6684. '*',
  6685. 'group',
  6686. 'picture'
  6687. ]
  6688. }, opt);
  6689. Base.call(this, spec);
  6690. this.helper = spec.helper;
  6691. this.name = spec.name || DEFAULTS.name;
  6692. this.picture = spec.picture || DEFAULTS.picture;
  6693. this.email = spec.email || DEFAULTS.email;
  6694. this.role = spec.role || DEFAULTS.role;
  6695. this.group = spec.group || DEFAULTS.group;
  6696. this.active = spec.active != null ? spec.active : DEFAULTS.active;
  6697. this.isOwner = spec.isOwner != null ? spec.isOwner : DEFAULTS.isOwner;
  6698. this.archived = spec.archived || DEFAULTS.archived;
  6699. this.restrictLocations = spec.restrictLocations ? spec.restrictLocations.slice() : DEFAULTS.restrictLocations.slice();
  6700. this.dsAnonymous = spec.dsAnonymous;
  6701. };
  6702. User.prototype = new tmp();
  6703. User.prototype.constructor = User;
  6704. //
  6705. // Document overrides
  6706. //
  6707. User.prototype.isValidName = function () {
  6708. this.name = $.trim(this.name);
  6709. return this.name.length >= 4;
  6710. };
  6711. User.prototype.isValidEmail = function () {
  6712. this.email = $.trim(this.email);
  6713. return common.isValidEmail(this.email);
  6714. };
  6715. User.prototype.isValidRole = function () {
  6716. switch (this.role) {
  6717. case 'user':
  6718. case 'admin':
  6719. case 'root':
  6720. case 'selfservice':
  6721. return true;
  6722. default:
  6723. return false;
  6724. }
  6725. };
  6726. User.prototype.emailExists = function () {
  6727. if (this.isValidEmail()) {
  6728. // Don't check for emailExists for exisiting user
  6729. if (this.id != null && this.email == this.raw.email) {
  6730. return $.Deferred().resolve(false);
  6731. }
  6732. return this.dsAnonymous.call('emailExists', { email: this.email }).then(function (resp) {
  6733. return resp.result;
  6734. });
  6735. } else {
  6736. return $.Deferred().resolve(false);
  6737. }
  6738. };
  6739. User.prototype.isValidPassword = function () {
  6740. this.password = $.trim(this.password);
  6741. return common.isValidPassword(this.password);
  6742. };
  6743. /**
  6744. * Checks if the user is valid
  6745. * @returns {boolean}
  6746. */
  6747. User.prototype.isValid = function () {
  6748. return this.isValidName() && this.isValidEmail() && this.isValidRole();
  6749. };
  6750. /**
  6751. * Checks if the user is empty
  6752. * @method
  6753. * @name User#isEmpty
  6754. * @returns {boolean}
  6755. */
  6756. User.prototype.isEmpty = function () {
  6757. // We check: name, role
  6758. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.email == DEFAULTS.email && this.role == DEFAULTS.role && (this.restrictLocations && this.restrictLocations.length == 0);
  6759. };
  6760. User.prototype._isDirtyInfo = function () {
  6761. if (this.raw) {
  6762. var name = this.raw.name || DEFAULTS.name;
  6763. var role = this.raw.role || DEFAULTS.role;
  6764. var email = this.raw.email || DEFAULTS.email;
  6765. var active = this.raw.active != null ? this.raw.active : DEFAULTS.active;
  6766. return this.name != name || this.email != email || this.role != role || this.active != active;
  6767. }
  6768. return false;
  6769. };
  6770. User.prototype._isDirtyRestrictLocations = function () {
  6771. if (this.raw) {
  6772. var that = this, restrictLocations = this.raw.restrictLocations || DEFAULTS.restrictLocations;
  6773. // Check if other locations have been selected
  6774. return this.restrictLocations.filter(function (x) {
  6775. return restrictLocations.indexOf(x) < 0;
  6776. }).length > 0 || restrictLocations.filter(function (x) {
  6777. return that.restrictLocations.indexOf(x) < 0;
  6778. }).length > 0;
  6779. }
  6780. return false;
  6781. };
  6782. /**
  6783. * Checks if the user is dirty and needs saving
  6784. * @method
  6785. * @name User#isDirty
  6786. * @returns {boolean}
  6787. */
  6788. User.prototype.isDirty = function () {
  6789. var isDirty = Base.prototype.isDirty.call(this);
  6790. return isDirty || this._isDirtyInfo() || this._isDirtyRestrictLocations();
  6791. };
  6792. /**
  6793. * Gets a url for a user avatar
  6794. * 'XS': (64, 64),
  6795. * 'S': (128, 128),
  6796. * 'M': (256, 256),
  6797. * 'L': (512, 512)
  6798. * @param size {string} default null is original size
  6799. * @param bustCache {boolean}
  6800. * @returns {string}
  6801. */
  6802. User.prototype.getImageUrl = function (size, bustCache) {
  6803. return this.picture != null && this.picture.length > 0 ? this.helper.getImageCDNUrl(this.group, this.picture, size, bustCache) : this.helper.getImageUrl(this.ds, this.id, size, bustCache);
  6804. };
  6805. User.prototype._getDefaults = function () {
  6806. return DEFAULTS;
  6807. };
  6808. // OVERRIDE BASE: addKeyValue not implemented
  6809. User.prototype.addKeyValue = function (key, value, kind, skipRead) {
  6810. return $.Deferred().reject('Not implemented for User, use setPicture instead?');
  6811. };
  6812. // OVERRIDE BASE: addKeyValue not implemented
  6813. User.prototype.addKeyValue = function (id, key, value, kind, skipRead) {
  6814. return $.Deferred().reject('Not implemented for User, use setPicture instead?');
  6815. };
  6816. // OVERRIDE BASE: removeKeyValue not implemented
  6817. User.prototype.removeKeyValue = function (id, skipRead) {
  6818. return $.Deferred().reject('Not implemented for User, use clearPicture instead?');
  6819. };
  6820. User.prototype.setPicture = function (attachmentId, skipRead) {
  6821. if (!this.existsInDb()) {
  6822. return $.Deferred().reject('User does not exist in database');
  6823. }
  6824. this.picture = attachmentId;
  6825. return this._doApiCall({
  6826. method: 'setPicture',
  6827. params: { attachment: attachmentId },
  6828. skipRead: skipRead
  6829. });
  6830. };
  6831. User.prototype.clearPicture = function (skipRead) {
  6832. if (!this.existsInDb()) {
  6833. return $.Deferred().reject('User does not exist in database');
  6834. }
  6835. return this._doApiCall({
  6836. method: 'clearPicture',
  6837. skipRead: skipRead
  6838. });
  6839. };
  6840. //
  6841. // Business logic
  6842. //
  6843. /**
  6844. * Checks if a user can be activated
  6845. * @returns {boolean}
  6846. */
  6847. User.prototype.canActivate = function () {
  6848. return !this.active && this.archived == null;
  6849. };
  6850. /**
  6851. * Checks if a user can be deactivated
  6852. * @returns {boolean}
  6853. */
  6854. User.prototype.canDeactivate = function () {
  6855. // TODO: We should also check if we're not deactivating the last or only user
  6856. return this.active && this.archived == null && !this.isOwner;
  6857. };
  6858. /**
  6859. * Checks if a user can be archived
  6860. * @returns {boolean}
  6861. */
  6862. User.prototype.canArchive = function () {
  6863. // TODO: We should also check if we're not deactivating the last or only user
  6864. return this.archived == null && !this.isOwner;
  6865. };
  6866. /**
  6867. * Checks if a user can be unarchived
  6868. * @returns {boolean}
  6869. */
  6870. User.prototype.canUndoArchive = function () {
  6871. return this.archived != null;
  6872. };
  6873. /**
  6874. * Checks if a user can be owner
  6875. * @returns {boolean}
  6876. */
  6877. User.prototype.canBeOwner = function () {
  6878. return this.archived == null && this.active && !this.isOwner && this.role == 'admin';
  6879. };
  6880. /**
  6881. * Activates a user
  6882. * @param skipRead
  6883. * @returns {promise}
  6884. */
  6885. User.prototype.activate = function (skipRead) {
  6886. if (!this.existsInDb()) {
  6887. return $.Deferred().reject('User does not exist in database');
  6888. }
  6889. return this._doApiCall({
  6890. method: 'activate',
  6891. skipRead: skipRead
  6892. });
  6893. };
  6894. /**
  6895. * Deactivates a user
  6896. * @param skipRead
  6897. * @returns {promise}
  6898. */
  6899. User.prototype.deactivate = function (skipRead) {
  6900. if (!this.existsInDb()) {
  6901. return $.Deferred().reject('User does not exist in database');
  6902. }
  6903. return this._doApiCall({
  6904. method: 'deactivate',
  6905. skipRead: skipRead
  6906. });
  6907. };
  6908. /**
  6909. * Archives a user
  6910. * @param skipRead
  6911. * @returns {promise}
  6912. */
  6913. User.prototype.archive = function (skipRead) {
  6914. if (!this.existsInDb()) {
  6915. return $.Deferred().reject('User does not exist in database');
  6916. }
  6917. return this._doApiCall({
  6918. method: 'archive',
  6919. skipRead: skipRead
  6920. });
  6921. };
  6922. /**
  6923. * Unarchives a user
  6924. * @param skipRead
  6925. * @returns {promise}
  6926. */
  6927. User.prototype.undoArchive = function (skipRead) {
  6928. if (!this.existsInDb()) {
  6929. return $.Deferred().reject('User does not exist in database');
  6930. }
  6931. return this._doApiCall({
  6932. method: 'undoArchive',
  6933. skipRead: skipRead
  6934. });
  6935. };
  6936. /**
  6937. * Restrict user access to specific location(s)
  6938. * @param locations
  6939. * @param skipRead
  6940. * @returns {promise}
  6941. */
  6942. User.prototype.setRestrictLocations = function (locations, skipRead) {
  6943. if (!this.existsInDb()) {
  6944. return $.Deferred().reject('User does not exist in database');
  6945. }
  6946. return this._doApiCall({
  6947. method: 'setRestrictLocations',
  6948. params: { restrictLocations: locations },
  6949. skipRead: skipRead
  6950. });
  6951. };
  6952. /**
  6953. * Clear user location(s) access (makes all location accessible for the user)
  6954. * @param skipRead
  6955. * @returns {promise}
  6956. */
  6957. User.prototype.clearRestrictLocations = function (skipRead) {
  6958. if (!this.existsInDb()) {
  6959. return $.Deferred().reject('User does not exist in database');
  6960. }
  6961. return this._doApiCall({
  6962. method: 'clearRestrictLocations',
  6963. skipRead: skipRead
  6964. });
  6965. };
  6966. /**
  6967. * Updates the user
  6968. * @param skipRead
  6969. * @returns {*}
  6970. */
  6971. User.prototype.update = function (skipRead) {
  6972. if (this.isEmpty()) {
  6973. return $.Deferred().reject(new Error('Cannot update to empty user'));
  6974. }
  6975. if (!this.existsInDb()) {
  6976. return $.Deferred().reject(new Error('Cannot update user without id'));
  6977. }
  6978. if (!this.isValid()) {
  6979. return $.Deferred().reject(new Error('Cannot update, invalid user'));
  6980. }
  6981. var that = this, dfdRestrictLocations = $.Deferred(), dfdInfo = $.Deferred();
  6982. if (this._isDirtyInfo()) {
  6983. dfdInfo = this.ds.update(this.id, this._toJson(), this._fields);
  6984. } else {
  6985. dfdInfo.resolve();
  6986. }
  6987. if (this._isDirtyRestrictLocations()) {
  6988. if (this.restrictLocations.length != 0) {
  6989. dfdRestrictLocations = this.setRestrictLocations(this.restrictLocations, true);
  6990. } else {
  6991. dfdRestrictLocations = this.clearRestrictLocations(true);
  6992. }
  6993. } else {
  6994. dfdRestrictLocations.resolve();
  6995. }
  6996. return $.when(dfdInfo, dfdRestrictLocations);
  6997. };
  6998. /**
  6999. * Writes the user to a json object
  7000. * @param options
  7001. * @returns {object}
  7002. * @private
  7003. */
  7004. User.prototype._toJson = function (options) {
  7005. var data = Base.prototype._toJson.call(this, options);
  7006. data.name = this.name || DEFAULTS.name;
  7007. data.email = this.email || DEFAULTS.email;
  7008. data.group = this.group || DEFAULTS.group;
  7009. data.role = this.role || DEFAULTS.role;
  7010. return data;
  7011. };
  7012. /**
  7013. * Reads the user from the json object
  7014. * @param data
  7015. * @param options
  7016. * @returns {promise}
  7017. * @private
  7018. */
  7019. User.prototype._fromJson = function (data, options) {
  7020. var that = this;
  7021. return Base.prototype._fromJson.call(this, data, options).then(function () {
  7022. // Read the group id from group or group._id
  7023. // depending on the fields
  7024. that.group = data.group && data.group._id != null ? data.group._id : data.group || DEFAULTS.group;
  7025. that.name = data.name || DEFAULTS.name;
  7026. that.picture = data.picture || DEFAULTS.picture;
  7027. that.email = data.email || DEFAULTS.email;
  7028. that.role = data.role || DEFAULTS.role;
  7029. that.active = data.active != null ? data.active : DEFAULTS.active;
  7030. that.isOwner = data.isOwner != null ? data.isOwner : DEFAULTS.isOwner;
  7031. that.archived = data.archived || DEFAULTS.archived;
  7032. that.restrictLocations = data.restrictLocations ? data.restrictLocations.slice() : DEFAULTS.restrictLocations.slice();
  7033. $.publish('user.fromJson', data);
  7034. return data;
  7035. });
  7036. };
  7037. return User;
  7038. }(jquery, base, common);
  7039. helper = function ($, defaultSettings, common) {
  7040. /**
  7041. * Allows you to call helpers based on the settings file
  7042. * and also settings in group.profile and user.profile
  7043. * @name Helper
  7044. * @class Helper
  7045. * @constructor
  7046. * @property {object} settings
  7047. */
  7048. return function (settings) {
  7049. settings = settings || defaultSettings;
  7050. return {
  7051. /**
  7052. * getSettings return settings file which helper uses internally
  7053. * @return {object}
  7054. */
  7055. getSettings: function () {
  7056. return settings;
  7057. },
  7058. /**
  7059. * getImageCDNUrl gets an image by using the path to a CDN location
  7060. *
  7061. * @memberOf helper
  7062. * @method
  7063. * @name helper#getImageCDNUrl
  7064. *
  7065. * @param groupId
  7066. * @param attachmentId
  7067. * @param size
  7068. * @returns {string}
  7069. */
  7070. getImageCDNUrl: function (groupId, attachmentId, size) {
  7071. return common.getImageCDNUrl(settings, groupId, attachmentId, size);
  7072. },
  7073. /**
  7074. * getImageUrl gets an image by using the datasource /get style and a mimeType
  7075. * 'XS': (64, 64),
  7076. * 'S': (128, 128),
  7077. * 'M': (256, 256),
  7078. * 'L': (512, 512)
  7079. *
  7080. * @memberOf helper
  7081. * @method
  7082. * @name helper#getImageUrl
  7083. *
  7084. * @param ds
  7085. * @param pk
  7086. * @param size
  7087. * @param bustCache
  7088. * @returns {string}
  7089. */
  7090. getImageUrl: function (ds, pk, size, bustCache) {
  7091. var url = ds.getBaseUrl() + pk + '?mimeType=image/jpeg';
  7092. if (size && size != 'orig') {
  7093. url += '&size=' + size;
  7094. }
  7095. if (bustCache) {
  7096. url += '&_bust=' + new Date().getTime();
  7097. }
  7098. return url;
  7099. },
  7100. getICalUrl: function (urlApi, userId, userPublicKey, orderLabels, reservationLabels, customerId, locationId) {
  7101. orderLabels = orderLabels || [];
  7102. reservationLabels = reservationLabels || [];
  7103. var url = urlApi + '/ical/' + userId + '/' + userPublicKey + '/public/locations/call/ical', parts = [];
  7104. if (locationId) {
  7105. parts.push('locations[]=' + locationId);
  7106. }
  7107. if (customerId) {
  7108. parts.push('customer=' + customerId);
  7109. }
  7110. var selectedReservationLabels = reservationLabels.filter(function (lbl) {
  7111. return lbl.selected;
  7112. }).map(function (lbl) {
  7113. return lbl.id || '';
  7114. });
  7115. if (selectedReservationLabels.length == 0) {
  7116. parts.push('skipOpenReservations=true');
  7117. } else {
  7118. // Only pass reservationLabels if user has made a custom selection
  7119. if (selectedReservationLabels.length != reservationLabels.length) {
  7120. parts.push($.param({ 'rlab': selectedReservationLabels }));
  7121. }
  7122. }
  7123. var selectedOrderLabels = orderLabels.filter(function (lbl) {
  7124. return lbl.selected;
  7125. }).map(function (lbl) {
  7126. return lbl.id || '';
  7127. });
  7128. if (selectedOrderLabels.length == 0) {
  7129. parts.push('skipOpenOrders=true');
  7130. } else {
  7131. // Only pass orderLabels if user has made a custom selection
  7132. if (selectedOrderLabels.length != orderLabels.length) {
  7133. parts.push($.param({ 'olab': selectedOrderLabels }));
  7134. }
  7135. }
  7136. return parts.length > 0 ? url + '?' + parts.join('&') : url;
  7137. },
  7138. /**
  7139. * getQRCodeUrl
  7140. *
  7141. * @memberOf helper
  7142. * @method
  7143. * @name helper#getQRCodeUrl
  7144. *
  7145. * @param {string} code
  7146. * @param {number} size
  7147. * @return {string}
  7148. */
  7149. getQRCodeUrl: function (code, size) {
  7150. return common.getQRCodeUrl(settings.urlApi, code, size);
  7151. },
  7152. /**
  7153. * getBarcodeUrl
  7154. *
  7155. * @memberOf helper
  7156. * @method
  7157. * @name helper#getBarcodeUrl
  7158. *
  7159. * @param {string} code
  7160. * @param {number} size
  7161. * @return {string}
  7162. */
  7163. getBarcodeUrl: function (code, width, height) {
  7164. return common.getBarcodeUrl(settings.urlApi, code, width, height);
  7165. },
  7166. /**
  7167. * getNumItemsLeft
  7168. *
  7169. * @memberOf helper
  7170. * @method
  7171. * @name helper#getNumItemsLeft
  7172. *
  7173. * @param limits
  7174. * @param stats
  7175. * @return {Number}
  7176. */
  7177. getNumItemsLeft: function (limits, stats) {
  7178. var itemsPerStatus = this.getStat(stats, 'items', 'status');
  7179. return limits.maxItems - this.getStat(stats, 'items', 'total') + itemsPerStatus.expired;
  7180. },
  7181. /**
  7182. * getNumUsersLeft
  7183. *
  7184. * @memberOf helper
  7185. * @method
  7186. * @name helper#getNumUsersLeft
  7187. *
  7188. * @param limits
  7189. * @param stats
  7190. * @return {Number}
  7191. */
  7192. getNumUsersLeft: function (limits, stats) {
  7193. var usersPerStatus = this.getStat(stats, 'users', 'status');
  7194. return limits.maxUsers - usersPerStatus.active;
  7195. },
  7196. /**
  7197. * getStat for location
  7198. *
  7199. * @memberOf helper
  7200. * @method
  7201. * @name helper#getStat
  7202. *
  7203. * @param stats
  7204. * @param location
  7205. * @param type
  7206. * @param name
  7207. * @param mode
  7208. * @return {object} number or object
  7209. */
  7210. getStat: function (stats, type, name, location, mode) {
  7211. // make sure stats object isn't undefined
  7212. stats = stats || {};
  7213. //if no stats for given location found, use all stats object
  7214. stats = stats[location && location != 'null' ? location : 'all'] || stats['all'];
  7215. if (stats === undefined)
  7216. throw 'Invalid stats';
  7217. // load stats for given mode (defaults to production)
  7218. stats = stats[mode || 'production'];
  7219. var statType = stats[type];
  7220. if (statType === undefined)
  7221. throw 'Stat doesn\'t exist';
  7222. if (!name)
  7223. return statType;
  7224. var statTypeValue = statType[name];
  7225. if (statTypeValue === undefined)
  7226. throw 'Stat value doesn\'t exist';
  7227. return statTypeValue;
  7228. },
  7229. /**
  7230. * ensureValue, returns specific prop value of object or if you pass a string it returns that exact string
  7231. *
  7232. * @memberOf helper
  7233. * @method
  7234. * @name helper#ensureValue
  7235. *
  7236. * @param obj
  7237. * @param prop
  7238. * @return {string}
  7239. */
  7240. ensureValue: function (obj, prop) {
  7241. if (typeof obj === 'string') {
  7242. return obj;
  7243. } else if (obj && obj.hasOwnProperty(prop)) {
  7244. return obj[prop];
  7245. } else {
  7246. return obj;
  7247. }
  7248. },
  7249. /**
  7250. * ensureId, returns id value of object or if you pass a string it returns that exact string
  7251. * For example:
  7252. * ensureId("abc123") --> "abc123"
  7253. * ensureId({ id:"abc123", name:"example" }) --> "abc123"
  7254. *
  7255. * @memberOf helper
  7256. * @method
  7257. * @name helper#ensureId
  7258. *
  7259. * @param obj
  7260. * @return {string}
  7261. */
  7262. ensureId: function (obj) {
  7263. return this.ensureValue(obj, '_id');
  7264. }
  7265. };
  7266. };
  7267. }(jquery, settings, common);
  7268. Contact = function ($, Base, common, User, Helper) {
  7269. var DEFAULTS = {
  7270. name: '',
  7271. email: '',
  7272. status: 'active',
  7273. user: {},
  7274. kind: 'contact'
  7275. };
  7276. // Allow overriding the ctor during inheritance
  7277. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  7278. var tmp = function () {
  7279. };
  7280. tmp.prototype = Base.prototype;
  7281. /**
  7282. * Contact class
  7283. * @name Contact
  7284. * @class
  7285. * @constructor
  7286. * @extends Base
  7287. */
  7288. var Contact = function (opt) {
  7289. var spec = $.extend({
  7290. _fields: ['*'],
  7291. crtype: 'cheqroom.types.customer'
  7292. }, opt);
  7293. Base.call(this, spec);
  7294. this.helper = spec.helper || new Helper();
  7295. this.name = spec.name || DEFAULTS.name;
  7296. this.email = spec.email || DEFAULTS.email;
  7297. this.status = spec.status || DEFAULTS.status;
  7298. this.user = spec.user || DEFAULTS.user;
  7299. this.kind = spec.kind || DEFAULTS.kind;
  7300. };
  7301. Contact.prototype = new tmp();
  7302. Contact.prototype.constructor = Contact;
  7303. //
  7304. // Specific validators
  7305. /**
  7306. * Checks if name is valid
  7307. * @name Contact#isValidName
  7308. * @method
  7309. * @return {Boolean} [description]
  7310. */
  7311. Contact.prototype.isValidName = function () {
  7312. this.name = $.trim(this.name);
  7313. return this.name.length >= 3;
  7314. };
  7315. /**
  7316. * Check is email is valid
  7317. * @name Contact#isValidEmail
  7318. * @method
  7319. * @return {Boolean} [description]
  7320. */
  7321. Contact.prototype.isValidEmail = function () {
  7322. this.email = $.trim(this.email);
  7323. return common.isValidEmail(this.email);
  7324. };
  7325. /**
  7326. * If the contact is linked to a user,
  7327. * return its user id
  7328. * Remark: needs field user
  7329. * @name Contact#getUserId
  7330. * @method
  7331. * @return {string}
  7332. */
  7333. Contact.prototype.getUserId = function () {
  7334. return this.helper.ensureId(this.user);
  7335. };
  7336. /**
  7337. * Checks if the user is a synced user
  7338. * Remark: needs field user
  7339. * @name Contact#getUserSync
  7340. * @method
  7341. * @return {string}
  7342. */
  7343. Contact.prototype.getUserSync = function () {
  7344. return this.user != null && this.user.sync != null ? this.user.sync : '';
  7345. };
  7346. //
  7347. // Business logic
  7348. //
  7349. /**
  7350. * Checks if a contact can be used in a reservation (based on status)
  7351. * @name Contact#canReserve
  7352. * @returns {boolean}
  7353. */
  7354. Contact.prototype.canReserve = function () {
  7355. return common.contactCanReserve(this);
  7356. };
  7357. /**
  7358. * Checks if a contact can be used in a checkout (based on status)
  7359. * @name Contact#canCheckout
  7360. * @returns {boolean}
  7361. */
  7362. Contact.prototype.canCheckout = function () {
  7363. return common.contactCanCheckout(this);
  7364. };
  7365. /**
  7366. * Checks if we can generate a document for this contact (based on status)
  7367. * @name Contact#canGenerateDocument
  7368. * @returns {boolean}
  7369. */
  7370. Contact.prototype.canGenerateDocument = function () {
  7371. return common.contactCanGenerateDocument(this);
  7372. };
  7373. /**
  7374. * Checks if a contact can be archived (based on status)
  7375. * @name Contact#canArchive
  7376. * @returns {boolean}
  7377. */
  7378. Contact.prototype.canArchive = function () {
  7379. return common.contactCanArchive(this);
  7380. };
  7381. /**
  7382. * Checks if a contact can be unarchived (based on status)
  7383. * @name Contact#canUndoArchive
  7384. * @returns {boolean}
  7385. */
  7386. Contact.prototype.canUndoArchive = function () {
  7387. return common.contactCanUndoArchive(this);
  7388. };
  7389. /**
  7390. * Checks if a contact can be deleted (based on status and link to user)
  7391. * @name Contact#canDelete
  7392. * @returns {boolean}
  7393. */
  7394. Contact.prototype.canDelete = function () {
  7395. return common.contactCanDelete(this);
  7396. };
  7397. /**
  7398. * Archive a contact
  7399. * @name Contact#archive
  7400. * @param skipRead
  7401. * @returns {promise}
  7402. */
  7403. Contact.prototype.archive = function (skipRead) {
  7404. return this._doApiCall({
  7405. method: 'archive',
  7406. params: {},
  7407. skipRead: skipRead
  7408. });
  7409. };
  7410. /**
  7411. * Undo archive of a contact
  7412. * @name Contact#undoArchive
  7413. * @param skipRead
  7414. * @returns {promise}
  7415. */
  7416. Contact.prototype.undoArchive = function (skipRead) {
  7417. return this._doApiCall({
  7418. method: 'undoArchive',
  7419. params: {},
  7420. skipRead: skipRead
  7421. });
  7422. };
  7423. /**
  7424. * Generates a PDF document for the reservation
  7425. * @method
  7426. * @name Contact#generateDocument
  7427. * @param {string} template id
  7428. * @param {string} signature (base64)
  7429. * @param {bool} skipRead
  7430. * @returns {promise}
  7431. */
  7432. Contact.prototype.generateDocument = function (template, signature, skipRead) {
  7433. return this._doApiLongCall({
  7434. method: 'generateDocument',
  7435. params: {
  7436. template: template,
  7437. signature: signature
  7438. },
  7439. skipRead: skipRead
  7440. });
  7441. };
  7442. //
  7443. // Base overrides
  7444. //
  7445. /**
  7446. * Checks if the contact has any validation errors
  7447. * @name Contact#isValid
  7448. * @method
  7449. * @returns {boolean}
  7450. * @override
  7451. */
  7452. Contact.prototype.isValid = function () {
  7453. return this.isValidName() && this.isValidEmail();
  7454. };
  7455. /**
  7456. * Checks if the contact is empty
  7457. * @returns {boolean}
  7458. * @override
  7459. */
  7460. Contact.prototype.isEmpty = function () {
  7461. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.email == DEFAULTS.email;
  7462. };
  7463. /**
  7464. * Checks if the contact is dirty and needs saving
  7465. * @returns {boolean}
  7466. * @override
  7467. */
  7468. Contact.prototype.isDirty = function () {
  7469. var isDirty = Base.prototype.isDirty.call(this);
  7470. if (!isDirty && this.raw) {
  7471. isDirty = this._isDirtyStringProperty('name') || this._isDirtyStringProperty('email');
  7472. }
  7473. return isDirty;
  7474. };
  7475. Contact.prototype._getDefaults = function () {
  7476. return DEFAULTS;
  7477. };
  7478. Contact.prototype._toJson = function (options) {
  7479. var data = Base.prototype._toJson.call(this, options);
  7480. data.name = this.name || DEFAULTS.name;
  7481. data.email = this.email || DEFAULTS.email;
  7482. return data;
  7483. };
  7484. Contact.prototype._fromJson = function (data, options) {
  7485. var that = this;
  7486. return Base.prototype._fromJson.call(this, data, options).then(function (data) {
  7487. that.name = data.name || DEFAULTS.name;
  7488. that.email = data.email || DEFAULTS.email;
  7489. that.status = data.status || DEFAULTS.status;
  7490. that.user = data.user || DEFAULTS.user;
  7491. that.kind = data.kind || DEFAULTS.kind;
  7492. $.publish('contact.fromJson', data);
  7493. return data;
  7494. });
  7495. };
  7496. Contact.prototype._create = function (skipRead) {
  7497. // We override create because we also want
  7498. // to set possible `fields` during the `create` command
  7499. var that = this, data = $.extend({}, this._toJson(), this._toJsonFields());
  7500. delete data.id;
  7501. return this.ds.create(data, this._fields).then(function (data) {
  7502. return skipRead == true ? data : that._fromJson(data);
  7503. });
  7504. };
  7505. Contact.prototype._update = function (skipRead) {
  7506. var that = this;
  7507. // Don't use the original `_update`
  7508. // because it uses `_toJson` and lists fields, even if they didn't change
  7509. // var data = this._toJson();
  7510. var data = {};
  7511. if (this._isDirtyStringProperty('name')) {
  7512. data['name'] = that.name;
  7513. }
  7514. if (this._isDirtyStringProperty('email')) {
  7515. data['email'] = that.email;
  7516. }
  7517. return this.ds.update(this.id, data, this._fields).then(function (data) {
  7518. return skipRead == true ? data : that._fromJson(data);
  7519. });
  7520. };
  7521. return Contact;
  7522. }(jquery, base, common, user, helper);
  7523. DateHelper = function ($, moment) {
  7524. // Add a new function to moment
  7525. moment.fn.toJSONDate = function () {
  7526. // toISOString gives the time in Zulu timezone
  7527. // we want the local timezone but in ISO formatting
  7528. return this.format('YYYY-MM-DD[T]HH:mm:ss.000[Z]');
  7529. };
  7530. // https://github.com/moment/moment/pull/1595
  7531. //m.roundTo('minute', 15); // Round the moment to the nearest 15 minutes.
  7532. //m.roundTo('minute', 15, 'up'); // Round the moment up to the nearest 15 minutes.
  7533. //m.roundTo('minute', 15, 'down'); // Round the moment down to the nearest 15 minutes.
  7534. moment.fn.roundTo = function (units, offset, midpoint) {
  7535. units = moment.normalizeUnits(units);
  7536. offset = offset || 1;
  7537. var roundUnit = function (unit) {
  7538. switch (midpoint) {
  7539. case 'up':
  7540. unit = Math.ceil(unit / offset);
  7541. break;
  7542. case 'down':
  7543. unit = Math.floor(unit / offset);
  7544. break;
  7545. default:
  7546. unit = Math.round(unit / offset);
  7547. break;
  7548. }
  7549. return unit * offset;
  7550. };
  7551. switch (units) {
  7552. case 'year':
  7553. this.year(roundUnit(this.year()));
  7554. break;
  7555. case 'month':
  7556. this.month(roundUnit(this.month()));
  7557. break;
  7558. case 'week':
  7559. this.weekday(roundUnit(this.weekday()));
  7560. break;
  7561. case 'isoWeek':
  7562. this.isoWeekday(roundUnit(this.isoWeekday()));
  7563. break;
  7564. case 'day':
  7565. this.day(roundUnit(this.day()));
  7566. break;
  7567. case 'hour':
  7568. this.hour(roundUnit(this.hour()));
  7569. break;
  7570. case 'minute':
  7571. this.minute(roundUnit(this.minute()));
  7572. break;
  7573. case 'second':
  7574. this.second(roundUnit(this.second()));
  7575. break;
  7576. default:
  7577. this.millisecond(roundUnit(this.millisecond()));
  7578. break;
  7579. }
  7580. return this;
  7581. };
  7582. /*
  7583. useHours = BooleanField(default=True)
  7584. avgCheckoutHours = IntField(default=4)
  7585. roundMinutes = IntField(default=15)
  7586. roundType = StringField(default="nearest", choices=ROUND_TYPE) # nearest, longer, shorter
  7587. */
  7588. var INCREMENT = 15, START_OF_DAY_HRS = 9, END_OF_DAY_HRS = 17;
  7589. /**
  7590. * @name DateHelper
  7591. * @class
  7592. * @constructor
  7593. */
  7594. var DateHelper = function (spec) {
  7595. spec = spec || {};
  7596. this.roundType = spec.roundType || 'nearest';
  7597. this.roundMinutes = spec.roundMinutes || INCREMENT;
  7598. this.timeFormat24 = spec.timeFormat24 ? spec.timeFormat24 : false;
  7599. this._momentFormat = this.timeFormat24 ? 'MMM D [at] H:mm' : 'MMM D [at] h:mm a';
  7600. this.startOfDayHours = spec.startOfDayHours != null ? startOfDayHours : START_OF_DAY_HRS;
  7601. this.endOfDayHours = spec.endOfDayHours != null ? endOfDayHours : END_OF_DAY_HRS;
  7602. };
  7603. /**
  7604. * @name parseDate
  7605. * @method
  7606. * @param data
  7607. * @returns {moment}
  7608. */
  7609. DateHelper.prototype.parseDate = function (data) {
  7610. if (typeof data == 'string' || data instanceof String) {
  7611. // "2014-04-03T12:15:00+00:00" (length 25)
  7612. // "2014-04-03T09:32:43.841000+00:00" (length 32)
  7613. if (data.endsWith('+00:00')) {
  7614. var len = data.length;
  7615. if (len == 25) {
  7616. return moment(data.substring(0, len - 6));
  7617. } else if (len == 32) {
  7618. return moment(data.substring(0, len - 6).split('.')[0]);
  7619. }
  7620. }
  7621. }
  7622. };
  7623. /**
  7624. * @name DateHelper#getNow
  7625. * @method
  7626. * @return {moment}
  7627. */
  7628. DateHelper.prototype.getNow = function () {
  7629. // TODO: Use the right MomentJS constructor
  7630. // This one will be deprecated in the next version
  7631. return moment();
  7632. };
  7633. /**
  7634. * @name DateHelper#getFriendlyDuration
  7635. * @method
  7636. * @param duration
  7637. * @return {}
  7638. */
  7639. DateHelper.prototype.getFriendlyDuration = function (duration) {
  7640. return duration.humanize();
  7641. };
  7642. /**
  7643. * @name DateHelper#getFriendlyDateParts
  7644. * @param date
  7645. * @param now (optional)
  7646. * @param format (optional)
  7647. * @returns [date string,time string]
  7648. */
  7649. DateHelper.prototype.getFriendlyDateParts = function (date, now, format) {
  7650. /*
  7651. moment().calendar() shows friendlier dates
  7652. - when the date is <=7d away:
  7653. - Today at 4:15 PM
  7654. - Yesterday at 4:15 PM
  7655. - Last Monday at 4:15 PM
  7656. - Wednesday at 4:15 PM
  7657. - when the date is >7d away:
  7658. - 07/25/2015
  7659. */
  7660. if (!moment.isMoment(date)) {
  7661. date = moment(date);
  7662. }
  7663. now = now || this.getNow();
  7664. return date.calendar().replace('AM', 'am').replace('PM', 'pm').split(' at ');
  7665. };
  7666. /**
  7667. * Returns a number of friendly date ranges with a name
  7668. * Each date range is a standard transaction duration
  7669. * @name getDateRanges
  7670. * @method
  7671. * @param avgHours
  7672. * @param numRanges
  7673. * @param now
  7674. * @param i18n
  7675. * @returns {Array} [{counter: 1, from: m(), to: m(), hours: 24, option: 'days', title: '1 Day'}, {...}]
  7676. */
  7677. DateHelper.prototype.getDateRanges = function (avgHours, numRanges, now, i18n) {
  7678. if (now && !moment.isMoment(now)) {
  7679. now = moment(now);
  7680. }
  7681. i18n = i18n || {
  7682. year: 'year',
  7683. years: 'years',
  7684. month: 'month',
  7685. months: 'months',
  7686. week: 'week',
  7687. weeks: 'weeks',
  7688. day: 'day',
  7689. days: 'days',
  7690. hour: 'hour',
  7691. hours: 'hours'
  7692. };
  7693. var timeOptions = [
  7694. 'years',
  7695. 'months',
  7696. 'weeks',
  7697. 'days',
  7698. 'hours'
  7699. ], timeHourVals = [
  7700. 365 * 24,
  7701. 30 * 24,
  7702. 7 * 24,
  7703. 24,
  7704. 1
  7705. ], opt = null, val = null, title = null, counter = 0, chosenIndex = -1, ranges = [];
  7706. // Find the range kind that best fits the avgHours (search from long to short)
  7707. for (var i = 0; i < timeOptions.length; i++) {
  7708. val = timeHourVals[i];
  7709. opt = timeOptions[i];
  7710. if (avgHours >= val) {
  7711. if (avgHours % val == 0) {
  7712. chosenIndex = i;
  7713. break;
  7714. }
  7715. }
  7716. }
  7717. now = now || this.getNow();
  7718. if (chosenIndex >= 0) {
  7719. for (var i = 1; i <= numRanges; i++) {
  7720. counter = i * avgHours;
  7721. title = i18n[counter == 1 ? opt.replace('s', '') : opt];
  7722. ranges.push({
  7723. option: opt,
  7724. hours: counter,
  7725. title: counter / timeHourVals[chosenIndex] + ' ' + title,
  7726. from: now.clone(),
  7727. to: now.clone().add(counter, 'hours')
  7728. });
  7729. }
  7730. }
  7731. return ranges;
  7732. };
  7733. /**
  7734. * getFriendlyFromTo
  7735. * returns {fromDate:"", fromTime: "", fromText: "", toDate: "", toTime: "", toText: "", text: ""}
  7736. * @param from
  7737. * @param to
  7738. * @param useHours
  7739. * @param now
  7740. * @param separator
  7741. * @param format
  7742. * @returns {}
  7743. */
  7744. DateHelper.prototype.getFriendlyFromTo = function (from, to, useHours, now, separator, format) {
  7745. now = now || this.getNow();
  7746. var sep = separator || ' - ', fromParts = this.getFriendlyDateParts(from, now, format), toParts = this.getFriendlyDateParts(to, now, format), result = {
  7747. dayDiff: from ? from.startOf('day').diff(to, 'days') : -1,
  7748. fromDate: from ? fromParts[0] : 'No from date set',
  7749. fromTime: useHours && from != null ? fromParts[1] : '',
  7750. toDate: to ? toParts[0] : 'No to date set',
  7751. toTime: useHours && to != null ? toParts[1] : ''
  7752. };
  7753. result.fromText = result.fromDate;
  7754. result.toText = result.toDate;
  7755. if (useHours) {
  7756. if (result.fromTime) {
  7757. result.fromText += ' ' + result.fromTime;
  7758. }
  7759. if (result.toTime) {
  7760. result.toText += ' ' + result.toTime;
  7761. }
  7762. }
  7763. // Build a text based on the dates and times we have
  7764. if (result.dayDiff == 0) {
  7765. if (useHours) {
  7766. result.text = result.fromText + sep + result.toTime;
  7767. } else {
  7768. result.text = result.fromText;
  7769. }
  7770. } else {
  7771. result.text = result.fromText + sep + result.toText;
  7772. }
  7773. return result;
  7774. };
  7775. /**
  7776. * @deprecated use getFriendlyFromToInfo
  7777. * [getFriendlyFromToOld]
  7778. * @param fromDate
  7779. * @param toDate
  7780. * @param groupProfile
  7781. * @return {}
  7782. */
  7783. DateHelper.prototype.getFriendlyFromToOld = function (fromDate, toDate, groupProfile) {
  7784. var mFrom = this.roundFromTime(fromDate, groupProfile);
  7785. var mTo = this.roundToTime(toDate, groupProfile);
  7786. return {
  7787. from: mFrom,
  7788. to: mTo,
  7789. daysBetween: mTo.clone().startOf('day').diff(mFrom.clone().startOf('day'), 'days'),
  7790. duration: moment.duration(mFrom - mTo).humanize(),
  7791. fromText: mFrom.calendar().replace(' at ', ' '),
  7792. toText: mTo.calendar().replace(' at ', ' ')
  7793. };
  7794. };
  7795. /**
  7796. * makeStartDate helps making an start date for a transaction, usually a reservation
  7797. * It will do the standard rounding
  7798. * But also, if you're using dates instead of datetimes,
  7799. * it will try to make smart decisions about which hours to use
  7800. * @param m - the Moment date
  7801. * @param useHours - does the profile use hours?
  7802. * @param dayMode - did the selection happen via calendar day selection? (can be true even if useHours is true)
  7803. * @param minDate - passing in a minimum start-date, will be different for reservations compared to orders
  7804. * @param maxDate - passing in a maximum start-date (not used for the moment)
  7805. * @param now - the current moment (just to make testing easier)
  7806. * @returns {moment}
  7807. * @private
  7808. */
  7809. DateHelper.prototype.makeStartDate = function (m, useHours, dayMode, minDate, maxDate, now) {
  7810. useHours = useHours != null ? useHours : true;
  7811. // is the account set up to use hours?
  7812. dayMode = dayMode != null ? dayMode : false;
  7813. // did the selection come from a calendar fullcalendar day selection?
  7814. now = now || moment();
  7815. // the current time (for unit testing)
  7816. if (useHours && !dayMode) {
  7817. // The account is set up to use hours,
  7818. // and the user picked the hours himself (since it's not picked from a dayMode calendar)
  7819. // We'll just round the from date
  7820. // if it's before the minDate, just take the minDate instead
  7821. m = this.roundTimeFrom(m);
  7822. } else {
  7823. // When we get here we know that either:
  7824. // 1) The account is set up to use hours BUT the date came from a calendar selection that just chose the date part
  7825. // or
  7826. // 2) The account is set up to use days instead of hours
  7827. //
  7828. // Which means we still need to see if we can make a smart decision about the hours part
  7829. // we'll base this on typical business hours (usually 9 to 5)
  7830. var isToday = m.isSame(now, 'day'), startOfBusinessDay = this._makeStartOfBusinessDay(m);
  7831. if (isToday) {
  7832. // The start date is today
  7833. // and the current time is still before business hours
  7834. // we can use the start time to start-of-business hours
  7835. if (m.isBefore(startOfBusinessDay)) {
  7836. m = startOfBusinessDay;
  7837. } else {
  7838. // We're already at the beginning of business hours
  7839. // or even already passed it, just try rounding the
  7840. // time and see if its before minDate
  7841. m = this.roundTimeFrom(m);
  7842. }
  7843. } else {
  7844. // The start date is not today, we can just take the business day start from the date that was passed
  7845. m = startOfBusinessDay;
  7846. }
  7847. }
  7848. // Make sure we never return anything before the mindate
  7849. return minDate && m.isBefore(minDate) ? minDate : m;
  7850. };
  7851. /**
  7852. * makeEndDate helps making an end date for a transaction
  7853. * It will do the standard rounding
  7854. * But also, if you're using dates instead of datetimes,
  7855. * it will try to make smart decisions about which hours to use
  7856. * @param m - the Moment date
  7857. * @param useHours - does the profile use hours?
  7858. * @param dayMode - did the selection happen via calendar day selection? (can be true even if useHours is true)
  7859. * @param minDate
  7860. * @param maxDate
  7861. * @param now - the current moment (just to make testing easier)
  7862. * @returns {moment}
  7863. * @private
  7864. */
  7865. DateHelper.prototype.makeEndDate = function (m, useHours, dayMode, minDate, maxDate, now) {
  7866. useHours = useHours != null ? useHours : true;
  7867. dayMode = dayMode != null ? dayMode : false;
  7868. now = now || moment();
  7869. if (useHours && !dayMode) {
  7870. // The account is set up to use hours,
  7871. // and since dayMode is false,
  7872. // we assume the hours are picked by the user himself
  7873. // just do the rounding and we're done
  7874. m = this.roundTimeTo(m);
  7875. } else {
  7876. // When we get here we know that either:
  7877. // 1) The account is set up to use hours BUT the date came from a calendar selection that just chose the date part
  7878. // or
  7879. // 2) The account is set up to use days instead of hours
  7880. //
  7881. // Which means we still need to see if we can make a smart decision about the hours part
  7882. var isToday = m.isSame(now, 'day'), endOfBusinessDay = this._makeEndOfBusinessDay(m), endOfDay = this._makeEndOfDay(m);
  7883. if (isToday) {
  7884. // The end date is today
  7885. // and the current date is before business hours
  7886. // we can use the end time to end-of-business hours
  7887. if (m.isBefore(endOfBusinessDay)) {
  7888. m = endOfBusinessDay;
  7889. } else {
  7890. m = endOfDay;
  7891. }
  7892. } else if (m.isAfter(endOfBusinessDay)) {
  7893. m = endOfDay;
  7894. } else {
  7895. m = endOfBusinessDay;
  7896. }
  7897. }
  7898. // Make sure we never return a date after the max date
  7899. return maxDate && m.isAfter(maxDate) ? maxDate : m;
  7900. };
  7901. /**
  7902. * [getFriendlyDateText]
  7903. * @param date
  7904. * @param useHours
  7905. * @param now
  7906. * @param format
  7907. * @return {string}
  7908. */
  7909. DateHelper.prototype.getFriendlyDateText = function (date, useHours, now, format) {
  7910. if (date == null) {
  7911. return 'Not set';
  7912. }
  7913. var parts = this.getFriendlyDateParts(date, now, format);
  7914. return useHours ? parts.join(' ') : parts[0];
  7915. };
  7916. /**
  7917. * [addAverageDuration]
  7918. * @param m
  7919. * @returns {moment}
  7920. */
  7921. DateHelper.prototype.addAverageDuration = function (m) {
  7922. // TODO: Read the average order duration from the group.profile
  7923. // add it to the date that was passed
  7924. return m.clone().add(1, 'day');
  7925. };
  7926. /**
  7927. * roundTimeFrom uses the time rounding rules to round a begin datetime
  7928. * @name DateHelper#roundTimeFrom
  7929. * @method
  7930. * @param m
  7931. */
  7932. DateHelper.prototype.roundTimeFrom = function (m) {
  7933. return this.roundMinutes <= 1 ? m : this.roundTime(m, this.roundMinutes, this._typeToDirection(this.roundType, 'from'));
  7934. };
  7935. /**
  7936. * roundTimeTo uses the time rounding rules to round an end datetime
  7937. * @name DateHelper#roundTimeTo
  7938. * @method
  7939. * @param m
  7940. */
  7941. DateHelper.prototype.roundTimeTo = function (m) {
  7942. return this.roundMinutes <= 1 ? m : this.roundTime(m, this.roundMinutes, this._typeToDirection(this.roundType, 'to'));
  7943. };
  7944. /**
  7945. * @name DateHelper#roundTime
  7946. * @method
  7947. * @param m
  7948. * @param inc
  7949. * @param direction
  7950. */
  7951. DateHelper.prototype.roundTime = function (m, inc, direction) {
  7952. var mom = moment.isMoment(m) ? m : moment(m);
  7953. mom.seconds(0).milliseconds(0);
  7954. return mom.roundTo('minute', inc || INCREMENT, direction);
  7955. };
  7956. /**
  7957. * @name DateHelper#roundTimeUp
  7958. * @method
  7959. * @param m
  7960. * @param inc
  7961. */
  7962. DateHelper.prototype.roundTimeUp = function (m, inc) {
  7963. var mom = moment.isMoment(m) ? m : moment(m);
  7964. mom.seconds(0).milliseconds(0);
  7965. return mom.roundTo('minute', inc || INCREMENT, 'up');
  7966. };
  7967. /**
  7968. * @name DateHelper#roundTimeDown
  7969. * @method
  7970. * @param m
  7971. * @param inc
  7972. */
  7973. DateHelper.prototype.roundTimeDown = function (m, inc) {
  7974. var mom = moment.isMoment(m) ? m : moment(m);
  7975. mom.seconds(0).milliseconds(0);
  7976. return mom.roundTo('minute', inc || INCREMENT, 'down');
  7977. };
  7978. DateHelper.prototype._typeToDirection = function (type, fromto) {
  7979. switch (type) {
  7980. case 'longer':
  7981. switch (fromto) {
  7982. case 'from':
  7983. return 'down';
  7984. case 'to':
  7985. return 'up';
  7986. default:
  7987. break;
  7988. }
  7989. break;
  7990. case 'shorter':
  7991. switch (fromto) {
  7992. case 'from':
  7993. return 'up';
  7994. case 'to':
  7995. return 'down';
  7996. default:
  7997. break;
  7998. }
  7999. break;
  8000. default:
  8001. break;
  8002. }
  8003. };
  8004. DateHelper.prototype._makeStartOfBusinessDay = function (m) {
  8005. return m.clone().hours(this.startOfDayHours).minutes(0).seconds(0).milliseconds(0);
  8006. };
  8007. DateHelper.prototype._makeEndOfBusinessDay = function (m) {
  8008. return m.clone().hours(this.endOfDayHours).minutes(0).seconds(0).milliseconds(0);
  8009. };
  8010. DateHelper.prototype._makeEndOfDay = function (m) {
  8011. return m.clone().hours(23).minutes(45).seconds(0).milliseconds(0);
  8012. };
  8013. return DateHelper;
  8014. }(jquery, moment);
  8015. Document = function ($, common, api, ColorLabel) {
  8016. // Some constant values
  8017. var DEFAULTS = { id: '' };
  8018. /**
  8019. * @name Document
  8020. * @class
  8021. * @constructor
  8022. * @property {ApiDataSource} ds - The documents primary key
  8023. * @property {array} _fields - The raw, unprocessed json response
  8024. * @property {string} id - The documents primary key
  8025. * @property {string} raw - The raw, unprocessed json response
  8026. */
  8027. var Document = function (spec) {
  8028. this.raw = null;
  8029. // raw json object
  8030. this.id = spec.id || DEFAULTS.id;
  8031. // doc _id
  8032. this.ds = spec.ds;
  8033. // ApiDataSource object
  8034. this._fields = spec._fields; // e.g. [*]
  8035. };
  8036. /**
  8037. * Resets the object
  8038. * @name Document#reset
  8039. * @method
  8040. * @returns {promise}
  8041. */
  8042. Document.prototype.reset = function () {
  8043. // By default, reset just reads from the DEFAULTS dict again
  8044. return this._fromJson(this._getDefaults(), null);
  8045. };
  8046. /**
  8047. * Checks if the document exists in the database
  8048. * @name Document#existsInDb
  8049. * @method
  8050. * @returns {boolean}
  8051. */
  8052. Document.prototype.existsInDb = function () {
  8053. // Check if we have a primary key
  8054. return this.id != null && this.id.length > 0;
  8055. };
  8056. /**
  8057. * Checks if the object is empty
  8058. * @name Document#isEmpty
  8059. * @method
  8060. * @returns {boolean}
  8061. */
  8062. Document.prototype.isEmpty = function () {
  8063. return true;
  8064. };
  8065. /**
  8066. * Checks if the object needs to be saved
  8067. * We don't check any of the keyvalues (or comments, attachments) here
  8068. * @name Document#isDirty
  8069. * @method
  8070. * @returns {boolean}
  8071. */
  8072. Document.prototype.isDirty = function () {
  8073. return false;
  8074. };
  8075. /**
  8076. * Checks if the object is valid
  8077. * @name Document#isValid
  8078. * @method
  8079. * @returns {boolean}
  8080. */
  8081. Document.prototype.isValid = function () {
  8082. return true;
  8083. };
  8084. /**
  8085. * Discards any changes made to the object from the previously loaded raw response
  8086. * or resets it when no old raw response was found
  8087. * @name Document#discardChanges
  8088. * @method
  8089. * @returns {promise}
  8090. */
  8091. Document.prototype.discardChanges = function () {
  8092. return this.raw ? this._fromJson(this.raw, null) : this.reset();
  8093. };
  8094. /**
  8095. * Reloads the object from db
  8096. * @name Document#reload
  8097. * @method
  8098. * @param _fields
  8099. * @returns {promise}
  8100. */
  8101. Document.prototype.reload = function (_fields) {
  8102. if (this.existsInDb()) {
  8103. return this.get(_fields);
  8104. } else {
  8105. return $.Deferred().reject(new api.ApiError('Cannot reload document, id is empty or null'));
  8106. }
  8107. };
  8108. /**
  8109. * Gets an object by the default api.get
  8110. * @name Document#get
  8111. * @method
  8112. * @param _fields
  8113. * @returns {promise}
  8114. */
  8115. Document.prototype.get = function (_fields) {
  8116. if (this.existsInDb()) {
  8117. var that = this;
  8118. return this.ds.get(this.id, _fields || this._fields).then(function (data) {
  8119. return that._fromJson(data);
  8120. });
  8121. } else {
  8122. return $.Deferred().reject(new api.ApiError('Cannot get document, id is empty or null'));
  8123. }
  8124. };
  8125. /**
  8126. * Creates an object by the default api.create
  8127. * @name Document#create
  8128. * @method
  8129. * @param skipRead skips reading the response via _fromJson (false)
  8130. * @returns {promise}
  8131. */
  8132. Document.prototype.create = function (skipRead) {
  8133. if (this.existsInDb()) {
  8134. return $.Deferred().reject(new Error('Cannot create document, already exists in database'));
  8135. }
  8136. if (this.isEmpty()) {
  8137. return $.Deferred().reject(new Error('Cannot create empty document'));
  8138. }
  8139. if (!this.isValid()) {
  8140. return $.Deferred().reject(new Error('Cannot create, invalid document'));
  8141. }
  8142. return this._create(skipRead);
  8143. };
  8144. /**
  8145. * Updates an object by the default api.update
  8146. * @name Document#update
  8147. * @method
  8148. * @param skipRead skips reading the response via _fromJson (false)
  8149. * @returns {promise}
  8150. */
  8151. Document.prototype.update = function (skipRead) {
  8152. if (!this.existsInDb()) {
  8153. return $.Deferred().reject(new Error('Cannot update document without id'));
  8154. }
  8155. if (this.isEmpty()) {
  8156. return $.Deferred().reject(new Error('Cannot update to empty document'));
  8157. }
  8158. if (!this.isValid()) {
  8159. return $.Deferred().reject(new Error('Cannot update, invalid document'));
  8160. }
  8161. return this._update(skipRead);
  8162. };
  8163. /**
  8164. * Deletes an object by the default api.delete
  8165. * @name Document#delete
  8166. * @method
  8167. * @returns {promise}
  8168. */
  8169. Document.prototype.delete = function () {
  8170. // Call the api /delete on this document
  8171. if (this.existsInDb()) {
  8172. return this._delete();
  8173. } else {
  8174. return $.Deferred().reject(new Error('Document does not exist'));
  8175. }
  8176. };
  8177. // toJson, fromJson
  8178. // ----
  8179. Document.prototype._getDefaults = function () {
  8180. return DEFAULTS;
  8181. };
  8182. /**
  8183. * _toJson, makes a dict of this object
  8184. * Possibly inheriting classes will override this method,
  8185. * because not all fields can be set during create / update
  8186. * @method
  8187. * @param options
  8188. * @returns {{}}
  8189. * @private
  8190. */
  8191. Document.prototype._toJson = function (options) {
  8192. return { id: this.id };
  8193. };
  8194. /**
  8195. * _fromJson: in this implementation we'll only read
  8196. * the data.keyValues into: comments, attachments, keyValues
  8197. * @method
  8198. * @param {object} data the json response
  8199. * @param {object} options dict
  8200. * @private
  8201. */
  8202. Document.prototype._fromJson = function (data, options) {
  8203. this.raw = data;
  8204. this.id = data._id || DEFAULTS.id;
  8205. return $.Deferred().resolve(data);
  8206. };
  8207. // Implementation stuff
  8208. // ---
  8209. /**
  8210. * The actual _create implementation (after all the checks are done)
  8211. * @param skipRead
  8212. * @returns {*}
  8213. * @private
  8214. */
  8215. Document.prototype._create = function (skipRead) {
  8216. var that = this;
  8217. var data = this._toJson();
  8218. delete data.id;
  8219. return this.ds.create(data, this._fields).then(function (data) {
  8220. return skipRead == true ? data : that._fromJson(data);
  8221. });
  8222. };
  8223. /**
  8224. * The actual _update implementation (after all the checks are done)
  8225. * @param skipRead
  8226. * @returns {*}
  8227. * @private
  8228. */
  8229. Document.prototype._update = function (skipRead) {
  8230. var that = this;
  8231. var data = this._toJson();
  8232. delete data.id;
  8233. return this.ds.update(this.id, data, this._fields).then(function (data) {
  8234. return skipRead == true ? data : that._fromJson(data);
  8235. });
  8236. };
  8237. /**
  8238. * The actual _delete implementation (after all the checks are done)
  8239. * @returns {*}
  8240. * @private
  8241. */
  8242. Document.prototype._delete = function () {
  8243. var that = this;
  8244. return this.ds.delete(this.id).then(function () {
  8245. return that.reset();
  8246. });
  8247. };
  8248. /**
  8249. * Helper for checking if a simple object property is dirty
  8250. * compared to the original raw result
  8251. * @param prop
  8252. * @returns {boolean}
  8253. * @private
  8254. */
  8255. Document.prototype._isDirtyProperty = function (prop) {
  8256. return this.raw ? this[prop] != this.raw[prop] : false;
  8257. };
  8258. /**
  8259. * Helper for checking if a simple object property is dirty
  8260. * compared to the original raw result
  8261. * Because we know that the API doesn't return empty string properties,
  8262. * we do a special, extra check on that.
  8263. * @param prop
  8264. * @returns {boolean}
  8265. * @private
  8266. */
  8267. Document.prototype._isDirtyStringProperty = function (prop) {
  8268. if (this.raw) {
  8269. var same = this[prop] == this.raw[prop] || this[prop] == '' && this.raw[prop] == null;
  8270. return !same;
  8271. } else {
  8272. return false;
  8273. }
  8274. };
  8275. /**
  8276. * Helper for checking if a simple object property is dirty
  8277. * compared to the original raw result
  8278. * @param prop
  8279. * @returns {boolean}
  8280. * @private
  8281. */
  8282. Document.prototype._isDirtyMomentProperty = function (prop) {
  8283. if (this.raw) {
  8284. var newVal = this[prop], oldVal = this.raw[prop];
  8285. if (newVal == null && oldVal == null) {
  8286. return false;
  8287. } else if (newVal && oldVal) {
  8288. return !newVal.isSame(oldVal);
  8289. } else {
  8290. return true;
  8291. }
  8292. } else {
  8293. return false;
  8294. }
  8295. };
  8296. /**
  8297. * Gets the id of a document
  8298. * @param obj
  8299. * @param prop
  8300. * @returns {string}
  8301. * @private
  8302. */
  8303. Document.prototype._getId = function (obj, prop) {
  8304. return typeof obj === 'string' ? obj : obj[prop || '_id'];
  8305. };
  8306. Document.prototype._getIds = function (objs, prop) {
  8307. return objs.map(function (obj) {
  8308. return typeof obj == 'string' ? obj : obj[prop || '_id'];
  8309. });
  8310. };
  8311. /**
  8312. * Wrapping the this.ds.call method
  8313. * {pk: '', method: '', params: {}, _fields: '', timeOut: null, usePost: null, skipRead: null}
  8314. * @method
  8315. * @param spec
  8316. * @returns {promise}
  8317. * @private
  8318. */
  8319. Document.prototype._doApiCall = function (spec) {
  8320. var that = this;
  8321. return this.ds.call(spec.collectionCall == true ? null : spec.pk || this.id, spec.method, spec.params, spec._fields || this._fields, spec.timeOut, spec.usePost).then(function (data) {
  8322. return spec.skipRead == true ? data : that._fromJson(data);
  8323. });
  8324. };
  8325. /**
  8326. * Wrapping the this.ds.call method with a longer timeout
  8327. * {pk: '', method: '', params: {}, _fields: '', timeOut: null, usePost: null, skipRead: null}
  8328. * @method
  8329. * @param spec
  8330. * @returns {promise}
  8331. * @private
  8332. */
  8333. Document.prototype._doApiLongCall = function (spec) {
  8334. spec.timeOut = spec.timeOut || 30000;
  8335. return this._doApiCall(spec);
  8336. };
  8337. Document.prototype._getColorLabel = function (data, options) {
  8338. var spec = $.extend({}, options || {}, data);
  8339. return new ColorLabel(spec);
  8340. };
  8341. return Document;
  8342. }(jquery, common, api, colorLabel);
  8343. Group = function ($, common, api, Document) {
  8344. // Some constant values
  8345. var DEFAULTS = {
  8346. id: '',
  8347. name: '',
  8348. itemFlags: [],
  8349. kitFlags: [],
  8350. customerFlags: [],
  8351. orderFlags: [],
  8352. reservationFlags: [],
  8353. itemFields: [],
  8354. kitFields: [],
  8355. customerFields: [],
  8356. orderFields: [],
  8357. reservationFields: [],
  8358. itemLabels: [],
  8359. kitLabels: [],
  8360. customerLabels: [],
  8361. reservationLabels: [],
  8362. orderLabels: [],
  8363. cancelled: null
  8364. };
  8365. // Allow overriding the ctor during inheritance
  8366. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  8367. var tmp = function () {
  8368. };
  8369. tmp.prototype = Document.prototype;
  8370. /**
  8371. * Group describes a group which can trigger on certain events (signals)
  8372. * @name Group
  8373. * @class
  8374. * @property {string} name the group name
  8375. * @property {array} itemFlags the groups item flags
  8376. * @property {array} kitFlags the groups kit flags
  8377. * @property {array} customerFlags the groups customer flags
  8378. * @property {array} orderFlags the groups order flags
  8379. * @property {array} reservationFlags the groups reservation flags
  8380. * @property {array} itemFields the groups item fields
  8381. * @property {array} kitFields the groups kit fields
  8382. * @property {array} customerFields the groups customer fields
  8383. * @property {array} reservationFields the groups reservation fields
  8384. * @property {array} orderFields the groups order fields
  8385. * @property {array} itemLabels the groups item labels
  8386. * @property {array} kitLabels the groups kit labels
  8387. * @property {array} customerLabels the groups customer labels
  8388. * @property {array} reservationLabels the groups reservation labels
  8389. * @property {array} orderLabels the groups order labels
  8390. * @constructor
  8391. * @extends Document
  8392. */
  8393. var Group = function (opt) {
  8394. var spec = $.extend({}, opt);
  8395. Document.call(this, spec);
  8396. this.name = spec.name || DEFAULTS.name;
  8397. this.itemFlags = spec.itemFlags || DEFAULTS.itemFlags.slice();
  8398. this.kitFlags = spec.kitFlags || DEFAULTS.kitFlags.slice();
  8399. this.customerFlags = spec.customerFlags || DEFAULTS.customerFlags.slice();
  8400. this.orderFlags = spec.orderFlags || DEFAULTS.orderFlags.slice();
  8401. this.reservationFlags = spec.reservationFlags || DEFAULTS.reservationFlags.slice();
  8402. this.itemFields = spec.itemFields || DEFAULTS.itemFields.slice();
  8403. this.kitFields = spec.kitFields || DEFAULTS.kitFields.slice();
  8404. this.customerFields = spec.customerFields || DEFAULTS.customerFields.slice();
  8405. this.reservationFields = spec.reservationFields || DEFAULTS.reservationFields.slice();
  8406. this.orderFields = spec.orderFields || DEFAULTS.orderFields.slice();
  8407. this.itemLabels = spec.itemLabels || DEFAULTS.itemLabels.slice();
  8408. this.kitLabels = spec.kitLabels || DEFAULTS.kitLabels.slice();
  8409. this.customerLabels = spec.customerLabels || DEFAULTS.customerLabels.slice();
  8410. this.reservationLabels = spec.reservationLabels || DEFAULTS.reservationLabels.slice();
  8411. this.orderLabels = spec.orderLabels || DEFAULTS.orderLabels.slice();
  8412. };
  8413. Group.prototype = new tmp();
  8414. Group.prototype.constructor = Group;
  8415. // Business logic
  8416. // ----
  8417. /**
  8418. * Sets the name for a group
  8419. * @param name
  8420. * @returns {promise}
  8421. */
  8422. Group.prototype.updateName = function (name) {
  8423. return this._doApiCall({
  8424. pk: this.id,
  8425. method: 'updateName',
  8426. location: name
  8427. });
  8428. };
  8429. /**
  8430. * Gets the stats (for a specific location)
  8431. * @param locationId
  8432. * @returns {promise}
  8433. */
  8434. Group.prototype.getStats = function (locationId) {
  8435. return this._doApiCall({
  8436. pk: this.id,
  8437. method: 'getStats',
  8438. location: locationId
  8439. });
  8440. };
  8441. /**
  8442. * Updates the flags for a certain collection of documents
  8443. * @param collection (items, kits, customers, reservations, orders)
  8444. * @param flags
  8445. * @param skipRead
  8446. * @returns {promise}
  8447. */
  8448. Group.prototype.updateFlags = function (collection, flags, skipRead) {
  8449. return this._doApiCall({
  8450. pk: this.id,
  8451. method: 'updateFlags',
  8452. collection: collection,
  8453. flags: flags,
  8454. skipRead: skipRead
  8455. });
  8456. };
  8457. /**
  8458. * Creates a field definition for a certain collection of documents
  8459. * @param collection (items, kits, customers, reservations, orders)
  8460. * @param name
  8461. * @param kind
  8462. * @param required
  8463. * @param form
  8464. * @param unit
  8465. * @param editor
  8466. * @param description
  8467. * @param skipRead
  8468. * @returns {promise}
  8469. */
  8470. Group.prototype.createField = function (collection, name, kind, required, form, unit, editor, description, skipRead) {
  8471. return this._doApiCall({
  8472. pk: this.id,
  8473. method: 'createField',
  8474. skipRead: skipRead,
  8475. params: {
  8476. collection: collection,
  8477. name: name,
  8478. kind: kind,
  8479. required: required,
  8480. form: form,
  8481. unit: unit,
  8482. editor: editor,
  8483. description: description
  8484. }
  8485. });
  8486. };
  8487. /**
  8488. * Updates a field definition for a certain collection of documents
  8489. * Also renames the field key on each of the documents that contain that field
  8490. * @param collection (items, kits, customers, reservations, orders)
  8491. * @param name
  8492. * @param newName
  8493. * @param kind
  8494. * @param required
  8495. * @param form
  8496. * @param unit
  8497. * @param editor
  8498. * @param description
  8499. * @param skipRead
  8500. * @returns {promise}
  8501. */
  8502. Group.prototype.updateField = function (collection, name, newName, kind, required, form, unit, editor, description, skipRead) {
  8503. return this._doApiCall({
  8504. pk: this.id,
  8505. method: 'updateField',
  8506. skipRead: skipRead,
  8507. params: {
  8508. collection: collection,
  8509. name: name,
  8510. newName: newName,
  8511. kind: kind,
  8512. required: required,
  8513. form: form,
  8514. unit: unit,
  8515. editor: editor,
  8516. description: description
  8517. }
  8518. });
  8519. };
  8520. /**
  8521. * Deletes a field definition for a certain collection of documents
  8522. * It will remove the field on all documents of that type
  8523. * @param collection (items, kits, customers, reservations, orders)
  8524. * @param name
  8525. * @param skipRead
  8526. * @returns {promise}
  8527. */
  8528. Group.prototype.deleteField = function (collection, name, skipRead) {
  8529. return this._doApiCall({
  8530. pk: this.id,
  8531. method: 'deleteField',
  8532. skipRead: skipRead,
  8533. params: {
  8534. collection: collection,
  8535. name: name
  8536. }
  8537. });
  8538. };
  8539. /**
  8540. * Moves a field definition for a certain collection of documents
  8541. * @param collection (items, kits, customers, reservations, orders)
  8542. * @param oldPos
  8543. * @param newPos
  8544. * @param skipRead
  8545. * @returns {promise}
  8546. */
  8547. Group.prototype.moveField = function (collection, oldPos, newPos, skipRead) {
  8548. return this._doApiCall({
  8549. pk: this.id,
  8550. method: 'moveField',
  8551. skipRead: skipRead,
  8552. params: {
  8553. collection: collection,
  8554. oldPos: oldPos,
  8555. newPos: newPos
  8556. }
  8557. });
  8558. };
  8559. /**
  8560. * Add document label
  8561. * @param collection (items, kits, customers, reservations, orders)
  8562. * @param labelColor
  8563. * @param labelName
  8564. * @param skipRead
  8565. * @returns {promise}
  8566. */
  8567. Group.prototype.createLabel = function (collection, labelColor, labelName, skipRead) {
  8568. return this._doApiCall({
  8569. pk: this.id,
  8570. method: 'createLabel',
  8571. skipRead: skipRead,
  8572. params: {
  8573. collection: collection,
  8574. labelColor: labelColor,
  8575. labelName: labelName
  8576. }
  8577. });
  8578. };
  8579. /**
  8580. * Updates document label
  8581. * @param collection (items, kits, customers, reservations, orders)
  8582. * @param labelId
  8583. * @param labelColor
  8584. * @param labelName
  8585. * @param skipRead
  8586. * @returns {promise}
  8587. */
  8588. Group.prototype.updateLabel = function (collection, labelId, labelColor, labelName, skipRead) {
  8589. return this._doApiCall({
  8590. pk: this.id,
  8591. method: 'updateLabel',
  8592. skipRead: skipRead,
  8593. params: {
  8594. collection: collection,
  8595. labelId: labelId,
  8596. labelColor: labelColor,
  8597. labelName: labelName
  8598. }
  8599. });
  8600. };
  8601. /**
  8602. * Removes document label
  8603. * @param collection (items, kits, customers, reservations, orders)
  8604. * @param labelId
  8605. * @param labelColor
  8606. * @param labelName
  8607. * @param skipRead
  8608. * @returns {promise}
  8609. */
  8610. Group.prototype.deleteLabel = function (collection, labelId, skipRead) {
  8611. return this._doApiCall({
  8612. pk: this.id,
  8613. method: 'deleteLabel',
  8614. skipRead: skipRead,
  8615. params: {
  8616. collection: collection,
  8617. labelId: labelId
  8618. }
  8619. });
  8620. };
  8621. /**
  8622. * Buys a single product from our in-app store
  8623. * @param productId
  8624. * @param quantity
  8625. * @param shipping
  8626. * @returns {promise}
  8627. */
  8628. Group.prototype.buyProduct = function (productId, quantity, shipping) {
  8629. return this._doApiCall({
  8630. pk: this.id,
  8631. method: 'buyProduct',
  8632. skipRead: true,
  8633. params: {
  8634. productId: productId,
  8635. quantity: quantity,
  8636. shipping: shipping
  8637. }
  8638. });
  8639. };
  8640. /**
  8641. * Buys multiple products from our in-app store
  8642. * @param listOfProductQtyTuples
  8643. * @param shipping
  8644. * @returns {promise}
  8645. */
  8646. Group.prototype.buyProducts = function (listOfProductQtyTuples, shipping) {
  8647. return this._doApiCall({
  8648. pk: this.id,
  8649. method: 'buyProducts',
  8650. skipRead: true,
  8651. params: {
  8652. products: listOfProductQtyTuples,
  8653. shipping: shipping
  8654. }
  8655. });
  8656. };
  8657. /**
  8658. * Add tags
  8659. * @param {Array} tags
  8660. */
  8661. Group.prototype.addTags = function (tags) {
  8662. return this._doApiCall({
  8663. pk: this.id,
  8664. method: 'addTags',
  8665. skipRead: true,
  8666. params: { tags: tags }
  8667. });
  8668. };
  8669. /**
  8670. * Remove tags
  8671. * @param {Array} tags
  8672. */
  8673. Group.prototype.removeTags = function (tags) {
  8674. return this._doApiCall({
  8675. pk: this.id,
  8676. method: 'removeTags',
  8677. skipRead: true,
  8678. params: { tags: tags }
  8679. });
  8680. };
  8681. // Helpers
  8682. // ----
  8683. /**
  8684. * Helper method that gets all known fields for a certain collection of documents
  8685. * @param coll
  8686. * @param form
  8687. * @returns {Array}
  8688. */
  8689. Group.prototype.getFieldsForCollection = function (coll, form) {
  8690. var fields = [];
  8691. switch (coll) {
  8692. case 'items':
  8693. fields = this.itemFields;
  8694. break;
  8695. case 'kits':
  8696. fields = this.kitFields;
  8697. break;
  8698. case 'contacts':
  8699. case 'customers':
  8700. fields = this.customerFields;
  8701. break;
  8702. case 'reservations':
  8703. fields = this.reservationFields;
  8704. break;
  8705. case 'checkouts':
  8706. case 'orders':
  8707. fields = this.orderFields;
  8708. break;
  8709. default:
  8710. break;
  8711. }
  8712. if (form != null) {
  8713. fields = $.grep(fields, function (f, i) {
  8714. return f.form == form;
  8715. });
  8716. }
  8717. return fields;
  8718. };
  8719. /**
  8720. * Helper method that gets all known flags for a certain collection of documents
  8721. * @param coll
  8722. * @returns {Array}
  8723. */
  8724. Group.prototype.getFlagsForCollection = function (coll) {
  8725. switch (coll) {
  8726. case 'items':
  8727. return this.itemFlags;
  8728. case 'kits':
  8729. return this.kitFlags;
  8730. case 'contacts':
  8731. case 'customers':
  8732. return this.customerFlags;
  8733. case 'reservations':
  8734. return this.reservationFlags;
  8735. case 'checkouts':
  8736. case 'orders':
  8737. return this.orderFlags;
  8738. default:
  8739. return [];
  8740. }
  8741. };
  8742. //
  8743. // Specific validators
  8744. /**
  8745. * Checks if name is valid
  8746. * @name Group#isValidName
  8747. * @method
  8748. * @return {Boolean}
  8749. */
  8750. Group.prototype.isValidName = function () {
  8751. this.name = $.trim(this.name);
  8752. return this.name.length >= 3;
  8753. };
  8754. // toJson, fromJson
  8755. // ----
  8756. /**
  8757. * _toJson, makes a dict of params to use during create / update
  8758. * @param options
  8759. * @returns {{}}
  8760. * @private
  8761. */
  8762. Group.prototype._toJson = function (options) {
  8763. var data = Document.prototype._toJson.call(this, options);
  8764. data.name = this.name;
  8765. return data;
  8766. };
  8767. /**
  8768. * _fromJson: read some basic information
  8769. * @method
  8770. * @param {object} data the json response
  8771. * @param {object} options dict
  8772. * @returns {promise}
  8773. * @private
  8774. */
  8775. Group.prototype._fromJson = function (data, options) {
  8776. var that = this;
  8777. return Document.prototype._fromJson.call(this, data, options).then(function () {
  8778. that.name = data.name || DEFAULTS.name;
  8779. that.itemFlags = data.itemFlags || DEFAULTS.itemFlags.slice();
  8780. that.kitFlags = data.kitFlags || DEFAULTS.kitFlags.slice();
  8781. that.customerFlags = data.customerFlags || DEFAULTS.customerFlags.slice();
  8782. that.orderFlags = data.orderFlags || DEFAULTS.orderFlags.slice();
  8783. that.reservationFlags = data.reservationFlags || DEFAULTS.reservationFlags.slice();
  8784. that.itemFields = data.itemFields || DEFAULTS.itemFields.slice();
  8785. that.kitFields = data.kitFields || DEFAULTS.kitFields.slice();
  8786. that.customerFields = data.customerFields || DEFAULTS.customerFields.slice();
  8787. that.reservationFields = data.reservationFields || DEFAULTS.reservationFields.slice();
  8788. that.orderFields = data.orderFields || DEFAULTS.orderFields.slice();
  8789. that.cancelled = data.cancelled || DEFAULTS.cancelled;
  8790. return that._fromColorLabelsJson(data, options);
  8791. });
  8792. };
  8793. /**
  8794. * _fromColorLabelsJson: reads the document labels
  8795. * @param data
  8796. * @param options
  8797. * @returns {*}
  8798. * @private
  8799. */
  8800. Group.prototype._fromColorLabelsJson = function (data, options) {
  8801. var obj = null, that = this;
  8802. $.each([
  8803. 'itemLabels',
  8804. 'kitLabels',
  8805. 'customerLabels',
  8806. 'reservationLabels',
  8807. 'orderLabels'
  8808. ], function (i, labelsKey) {
  8809. that[labelsKey] = DEFAULTS[labelsKey].slice();
  8810. if (labelsKey == 'orderLabels') {
  8811. that[labelsKey].push(that._getColorLabel({
  8812. readonly: true,
  8813. name: 'Unlabeled',
  8814. color: 'SlateGray'
  8815. }, options));
  8816. }
  8817. if (labelsKey == 'reservationLabels') {
  8818. that[labelsKey].push(that._getColorLabel({
  8819. readonly: true,
  8820. name: 'Unlabeled',
  8821. color: 'LimeGreen'
  8822. }, options));
  8823. }
  8824. if (data[labelsKey] && data[labelsKey].length > 0) {
  8825. $.each(data[labelsKey], function (i, label) {
  8826. obj = that._getColorLabel(label, options);
  8827. if (obj) {
  8828. that[labelsKey].push(obj);
  8829. }
  8830. });
  8831. }
  8832. });
  8833. return $.Deferred().resolve(data);
  8834. };
  8835. return Group;
  8836. }(jquery, common, api, document);
  8837. Item = function ($, common, Base) {
  8838. var FLAG = 'cheqroom.prop.Custom', DEFAULT_LAT = 0, DEFAULT_LONG = 0, DEFAULTS = {
  8839. name: '',
  8840. status: '',
  8841. codes: [],
  8842. brand: '',
  8843. model: '',
  8844. warrantyDate: null,
  8845. purchaseDate: null,
  8846. purchasePrice: null,
  8847. residualValue: null,
  8848. location: '',
  8849. category: '',
  8850. geo: [
  8851. DEFAULT_LAT,
  8852. DEFAULT_LONG
  8853. ],
  8854. address: '',
  8855. order: null,
  8856. kit: null,
  8857. custody: null,
  8858. cover: '',
  8859. catalog: null
  8860. };
  8861. // Allow overriding the ctor during inheritance
  8862. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  8863. var tmp = function () {
  8864. };
  8865. tmp.prototype = Base.prototype;
  8866. /**
  8867. * Item represents a single piece of equipment
  8868. * @name Item
  8869. * @class Item
  8870. * @constructor
  8871. * @property {string} name the name of the item
  8872. * @property {string} status the status of the item in an order, or expired
  8873. * @property {string} brand the item brand
  8874. * @property {string} model the item model
  8875. * @property {moment} warrantyDate the item warranty date
  8876. * @property {moment} purchaseDate the item purchase date
  8877. * @property {string} purchasePrice the item purchase price
  8878. * @property {Array} codes the item qr codes
  8879. * @property {string} location the item location primary key (empty if in_custody)
  8880. * @property {string} category the item category primary key
  8881. * @property {Array} geo the item geo position in lat lng array
  8882. * @property {string} address the item geo position address
  8883. * @property {string} order the order pk, if the item is currently in an order
  8884. * @property {string} kit the kit pk, if the item is currently in a kit
  8885. * @property {string} custody the customer pk, if the item is currently in custody of someone
  8886. * @property {string} cover the attachment pk of the main image
  8887. * @property {string} catalog the catalog pk, if the item was made based on a product in the catalog
  8888. * @extends Base
  8889. */
  8890. var Item = function (opt) {
  8891. var spec = $.extend({
  8892. _fields: ['*'],
  8893. crtype: 'cheqroom.types.item'
  8894. }, opt);
  8895. Base.call(this, spec);
  8896. this.name = spec.name || DEFAULTS.name;
  8897. this.status = spec.status || DEFAULTS.status;
  8898. this.brand = spec.brand || DEFAULTS.brand;
  8899. this.model = spec.model || DEFAULTS.model;
  8900. this.warrantyDate = spec.warrantyDate || DEFAULTS.warrantyDate;
  8901. this.purchaseDate = spec.purchaseDate || DEFAULTS.purchaseDate;
  8902. this.purchasePrice = spec.purchasePrice || DEFAULTS.purchasePrice;
  8903. this.residualValue = spec.residualValue || DEFAULTS.residualValue;
  8904. this.codes = spec.codes || DEFAULTS.codes;
  8905. this.location = spec.location || DEFAULTS.location;
  8906. // location._id
  8907. this.category = spec.category || DEFAULTS.category;
  8908. // category._id
  8909. this.geo = spec.geo || DEFAULTS.geo.slice();
  8910. // null or an array with 2 floats
  8911. this.address = spec.address || DEFAULTS.address;
  8912. this.order = spec.order || DEFAULTS.order;
  8913. this.kit = spec.kit || DEFAULTS.kit;
  8914. this.custody = spec.custody || DEFAULTS.custody;
  8915. this.cover = spec.cover || DEFAULTS.cover;
  8916. this.catalog = spec.catalog || DEFAULTS.catalog;
  8917. };
  8918. Item.prototype = new tmp();
  8919. Item.prototype.constructor = Item;
  8920. //
  8921. // Base overrides
  8922. //
  8923. Item.prototype.isValidName = function () {
  8924. this.name = $.trim(this.name);
  8925. return this.name.length >= 3;
  8926. };
  8927. Item.prototype.isValidCategory = function () {
  8928. return $.trim(this.category).length > 0;
  8929. };
  8930. Item.prototype.isValidLocation = function () {
  8931. return $.trim(this.location).length > 0;
  8932. };
  8933. Item.prototype.isValid = function () {
  8934. return this.isValidName() && this.isValidCategory() && (this.status == 'in_custody' ? true : this.isValidLocation());
  8935. };
  8936. /**
  8937. * Checks if the item is empty
  8938. * @name Item#isEmpty
  8939. * @returns {boolean}
  8940. */
  8941. Item.prototype.isEmpty = function () {
  8942. // Checks for: name, status, brand, model, purchaseDate, purchasePrice, codes, location, category
  8943. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.status == DEFAULTS.status && this.brand == DEFAULTS.brand && this.model == DEFAULTS.model && this.warrantyDate == DEFAULTS.warrantyDate && this.purchaseDate == DEFAULTS.purchaseDate && this.purchasePrice == DEFAULTS.purchasePrice && this.residualValue == DEFAULTS.residualValue && this.codes.length == 0 && this.location == DEFAULTS.location && this.category == DEFAULTS.category;
  8944. };
  8945. /**
  8946. * Checks if the item is dirty and needs saving
  8947. * @name Item#isDirty
  8948. * @returns {boolean}
  8949. */
  8950. Item.prototype.isDirty = function () {
  8951. return Base.prototype.isDirty.call(this) || this._isDirtyName() || this._isDirtyBrand() || this._isDirtyModel() || this._isDirtyWarrantyDate() || this._isDirtyPurchaseDate() || this._isDirtyPurchasePrice() || this._isDirtyResidualValue() || this._isDirtyCategory() || this._isDirtyLocation() || this._isDirtyGeo() || this._isDirtyFlag();
  8952. };
  8953. Item.prototype._getDefaults = function () {
  8954. return DEFAULTS;
  8955. };
  8956. Item.prototype._toJson = function (options) {
  8957. // Writes out: id, name,
  8958. // brand, model, purchaseDate, purchasePrice
  8959. // category, location, catalog
  8960. var data = Base.prototype._toJson.call(this, options);
  8961. data.name = this.name || DEFAULTS.name;
  8962. data.brand = this.brand || DEFAULTS.brand;
  8963. data.model = this.model || DEFAULTS.model;
  8964. data.warrantyDate = this.warrantyDate || DEFAULTS.warrantyDate;
  8965. data.purchaseDate = this.purchaseDate || DEFAULTS.purchaseDate;
  8966. data.purchasePrice = this.purchasePrice || DEFAULTS.purchasePrice;
  8967. data.residualValue = this.residualValue || DEFAULTS.residualValue;
  8968. data.category = this.category || DEFAULTS.category;
  8969. data.location = this.location || DEFAULTS.location;
  8970. data.catalog = this.catalog || DEFAULTS.catalog;
  8971. // Remove values of null during create
  8972. // Avoids: 422 Unprocessable Entity
  8973. // ValidationError (Item:TZe33wVKWwkKkpACp6Xy5T) (FloatField only accepts float values: ['purchasePrice'])
  8974. for (var k in data) {
  8975. if (data[k] == null) {
  8976. delete data[k];
  8977. }
  8978. }
  8979. return data;
  8980. };
  8981. Item.prototype._fromJson = function (data, options) {
  8982. var that = this;
  8983. return Base.prototype._fromJson.call(this, data, options).then(function () {
  8984. that.name = data.name || DEFAULTS.name;
  8985. that.status = data.status || DEFAULTS.status;
  8986. that.brand = data.brand || DEFAULTS.brand;
  8987. that.model = data.model || DEFAULTS.model;
  8988. that.warrantyDate = data.warrantyDate || DEFAULTS.warrantyDate;
  8989. that.purchaseDate = data.purchaseDate || DEFAULTS.purchaseDate;
  8990. that.purchasePrice = data.purchasePrice || DEFAULTS.purchasePrice;
  8991. that.residualValue = data.residualValue || DEFAULTS.residualValue;
  8992. that.codes = data.codes || DEFAULTS.codes;
  8993. that.address = data.address || DEFAULTS.address;
  8994. that.geo = data.geo || DEFAULTS.geo.slice();
  8995. that.cover = data.cover || DEFAULTS.cover;
  8996. that.catalog = data.catalog || DEFAULTS.catalog;
  8997. // Depending on the fields we'll need to get the _id directly or from the dicts
  8998. var locId = DEFAULTS.location;
  8999. if (data.location) {
  9000. locId = data.location._id ? data.location._id : data.location;
  9001. }
  9002. that.location = locId;
  9003. var catId = DEFAULTS.category;
  9004. if (data.category) {
  9005. catId = data.category._id ? data.category._id : data.category;
  9006. }
  9007. that.category = catId;
  9008. var orderId = DEFAULTS.order;
  9009. if (data.order) {
  9010. orderId = data.order._id ? data.order._id : data.order;
  9011. }
  9012. that.order = orderId;
  9013. var kitId = DEFAULTS.kit;
  9014. if (data.kit) {
  9015. kitId = data.kit._id ? data.kit._id : data.kit;
  9016. }
  9017. that.kit = kitId;
  9018. var custodyId = DEFAULTS.custody;
  9019. if (data.custody) {
  9020. custodyId = data.custody._id ? data.custody._id : data.custody;
  9021. }
  9022. that.custody = custodyId;
  9023. $.publish('item.fromJson', data);
  9024. return data;
  9025. });
  9026. };
  9027. // Deprecated
  9028. Item.prototype._toJsonKeyValues = function () {
  9029. var that = this;
  9030. var params = {};
  9031. if (this.keyValues != null && this.keyValues.length > 0) {
  9032. $.each(this.keyValues, function (i, kv) {
  9033. var param = 'keyValues__' + kv.key;
  9034. params[param + '__kind'] = kv.kind;
  9035. params[param + '__value'] = kv.value;
  9036. });
  9037. }
  9038. return params;
  9039. };
  9040. // Deprecated
  9041. Item.prototype._isDirtyName = function () {
  9042. return this._isDirtyStringProperty('name');
  9043. };
  9044. Item.prototype._isDirtyBrand = function () {
  9045. return this._isDirtyStringProperty('brand');
  9046. };
  9047. Item.prototype._isDirtyModel = function () {
  9048. return this._isDirtyStringProperty('model');
  9049. };
  9050. Item.prototype._isDirtyWarrantyDate = function () {
  9051. return this._isDirtyMomentProperty('warrantyDate');
  9052. };
  9053. Item.prototype._isDirtyPurchaseDate = function () {
  9054. return this._isDirtyMomentProperty('purchaseDate');
  9055. };
  9056. Item.prototype._isDirtyPurchasePrice = function () {
  9057. return this._isDirtyProperty('purchasePrice');
  9058. };
  9059. Item.prototype._isDirtyResidualValue = function () {
  9060. return this._isDirtyProperty('residualValue');
  9061. };
  9062. Item.prototype._isDirtyLocation = function () {
  9063. if (this.raw && this.status != 'in_custody') {
  9064. var locId = DEFAULTS.location;
  9065. if (this.raw.location) {
  9066. locId = this.raw.location._id ? this.raw.location._id : this.raw.location;
  9067. }
  9068. return this.location != locId;
  9069. } else {
  9070. return false;
  9071. }
  9072. };
  9073. Item.prototype._isDirtyCategory = function () {
  9074. if (this.raw) {
  9075. var catId = DEFAULTS.category;
  9076. if (this.raw.category) {
  9077. catId = this.raw.category._id ? this.raw.category._id : this.raw.category;
  9078. }
  9079. return this.category != catId;
  9080. } else {
  9081. return false;
  9082. }
  9083. };
  9084. Item.prototype._isDirtyGeo = function () {
  9085. if (this.raw) {
  9086. var address = this.raw.address || DEFAULTS.address;
  9087. var geo = this.raw.geo || DEFAULTS.geo.slice();
  9088. return this.address != address || this.geo[0] != geo[0] || this.geo[1] != geo[1];
  9089. } else {
  9090. return false;
  9091. }
  9092. };
  9093. Item.prototype._isDirtyFlag = function () {
  9094. return this._isDirtyStringProperty('flag');
  9095. };
  9096. //
  9097. // Business logic
  9098. //
  9099. /**
  9100. * Checks if the Item is unavailable between from / to dates (optional)
  9101. * @name Item#getAvailabilities
  9102. * @param {Moment} from the from date (optional)
  9103. * @param {Moment} to the to date (optional)
  9104. * @returns {promise}
  9105. */
  9106. Item.prototype.getAvailabilities = function (from, to) {
  9107. return this.ds.call(this.id, 'getAvailability', {
  9108. fromDate: from,
  9109. toDate: to
  9110. });
  9111. };
  9112. /**
  9113. * updates the Item
  9114. * We override because Item.update does not support updating categories
  9115. * @param skipRead
  9116. * @returns {*}
  9117. */
  9118. Item.prototype.update = function (skipRead) {
  9119. if (this.isEmpty()) {
  9120. return $.Deferred().reject(new Error('Cannot update to empty document'));
  9121. }
  9122. if (!this.existsInDb()) {
  9123. return $.Deferred().reject(new Error('Cannot update document without id'));
  9124. }
  9125. if (!this.isValid()) {
  9126. return $.Deferred().reject(new Error('Cannot update, invalid document'));
  9127. }
  9128. var that = this, dfdCheck = $.Deferred(), dfdCategory = $.Deferred(), dfdLocation = $.Deferred(), dfdFields = $.Deferred(), dfdFlags = $.Deferred(), dfdBasic = $.Deferred();
  9129. if (this._isDirtyCategory()) {
  9130. this.canChangeCategory(this.category).done(function (data) {
  9131. if (data.result) {
  9132. dfdCheck.resolve();
  9133. } else {
  9134. dfdCheck.reject(new Error('Unable to change item category'));
  9135. }
  9136. });
  9137. } else {
  9138. dfdCheck.resolve();
  9139. }
  9140. return dfdCheck.then(function () {
  9141. if (that._isDirtyCategory()) {
  9142. dfdCategory = that.changeCategory(that.category);
  9143. } else {
  9144. dfdCategory.resolve();
  9145. }
  9146. // Skip update location if item is in custody
  9147. if (that._isDirtyLocation() && that.status != 'in_custody') {
  9148. dfdLocation = that.changeLocation(that.location);
  9149. } else {
  9150. dfdLocation.resolve();
  9151. }
  9152. if (that._isDirtyFields()) {
  9153. dfdFields = that._updateFields();
  9154. } else {
  9155. dfdFields.resolve();
  9156. }
  9157. if (that._isDirtyFlag()) {
  9158. if (that.flag == '' || that.flag == null) {
  9159. dfdFlags = that.clearFlag();
  9160. } else {
  9161. dfdFlags = that.setFlag(that.flag);
  9162. }
  9163. } else {
  9164. dfdFlags.resolve();
  9165. }
  9166. if (that._isDirtyName() || that._isDirtyBrand() || that._isDirtyModel() || that._isDirtyWarrantyDate() || that._isDirtyPurchaseDate() || that._isDirtyPurchasePrice() || that._isDirtyResidualValue()) {
  9167. dfdBasic = that.updateBasicFields(that.name, that.brand, that.model, that.warrantyDate, that.purchaseDate, that.purchasePrice, that.residualValue);
  9168. } else {
  9169. dfdBasic.resolve();
  9170. }
  9171. return $.when(dfdCategory, dfdLocation, dfdFields, dfdFlags, dfdBasic);
  9172. });
  9173. };
  9174. /**
  9175. * Creates an Item
  9176. * @name Item#create
  9177. * @method
  9178. * @param skipRead skips reading the response via _fromJson (false)
  9179. * @returns {promise}
  9180. */
  9181. Item.prototype.create = function (skipRead) {
  9182. if (this.existsInDb()) {
  9183. return $.Deferred().reject(new Error('Cannot create document, already exists in database'));
  9184. }
  9185. if (this.isEmpty()) {
  9186. return $.Deferred().reject(new Error('Cannot create empty document'));
  9187. }
  9188. if (!this.isValid()) {
  9189. return $.Deferred().reject(new Error('Cannot create, invalid document'));
  9190. }
  9191. var that = this, data = $.extend(this._toJson(), this._toJsonFields());
  9192. delete data.id;
  9193. return this.ds.create(data, this._fields).then(function (data) {
  9194. return skipRead == true ? data : that._fromJson(data);
  9195. });
  9196. };
  9197. /**
  9198. * Creates multiple instances of the same item
  9199. * @name Item#createMultiple
  9200. * @method
  9201. * @param times
  9202. * @param autoNumber
  9203. * @param startFrom
  9204. * @return {promise}
  9205. */
  9206. Item.prototype.createMultiple = function (times, autoNumber, startFrom, skipRead) {
  9207. if (this.existsInDb()) {
  9208. return $.Deferred().reject(new Error('Cannot create document, already exists in database'));
  9209. }
  9210. if (this.isEmpty()) {
  9211. return $.Deferred().reject(new Error('Cannot create empty document'));
  9212. }
  9213. if (!this.isValid()) {
  9214. return $.Deferred().reject(new Error('Cannot create, invalid document'));
  9215. }
  9216. var that = this;
  9217. var data = $.extend(this._toJson(), this._toJsonFields(), {
  9218. times: times || 1,
  9219. autoNumber: autoNumber || false,
  9220. startFrom: startFrom
  9221. });
  9222. delete data.id;
  9223. // BUGFFIX model name clash issue
  9224. // model == Item property
  9225. // model == database model
  9226. if (data.model != null) {
  9227. data.brandModel = data.model;
  9228. delete data.model;
  9229. }
  9230. return this._doApiCall({
  9231. method: 'createMultiple',
  9232. params: data
  9233. }).then(function (data) {
  9234. var dfd = skipRead == true ? $.Deferred().resolve(data[0]) : that._fromJson(data[0]);
  9235. return dfd.then(function () {
  9236. return data;
  9237. });
  9238. });
  9239. };
  9240. /**
  9241. * Duplicates an item a number of times
  9242. * @name Item#duplicate
  9243. * @param times
  9244. * @param location
  9245. * @returns {promise}
  9246. */
  9247. Item.prototype.duplicate = function (times, location, autoNumber, startFrom) {
  9248. return this._doApiCall({
  9249. method: 'duplicate',
  9250. params: {
  9251. times: times,
  9252. location: location,
  9253. autoNumber: autoNumber,
  9254. startFrom: startFrom
  9255. },
  9256. skipRead: true // response is an array of new Item objects!!
  9257. });
  9258. };
  9259. /**
  9260. * Checks if an item can be reserved (based on status)
  9261. * @name Item#canReserve
  9262. * @returns {boolean}
  9263. */
  9264. Item.prototype.canReserve = function () {
  9265. return common.itemCanReserve(this);
  9266. };
  9267. /**
  9268. * Checks if an item can be checked out (based on status)
  9269. * @name Item#canCheckout
  9270. * @returns {boolean}
  9271. */
  9272. Item.prototype.canCheckout = function () {
  9273. return common.itemCanCheckout(this);
  9274. };
  9275. /**
  9276. * Checks if we can go to the checkout of an item (based on status)
  9277. * @name Item#canGoToCheckout
  9278. * @returns {boolean}
  9279. */
  9280. Item.prototype.canGoToCheckout = function () {
  9281. return common.itemCanGoToCheckout(this) && !$.isEmptyObject(this.order);
  9282. };
  9283. /**
  9284. * Checks if an item can be checked in (based on status)
  9285. * @name Item#canCheckin
  9286. * @returns {boolean}
  9287. */
  9288. Item.prototype.canCheckin = function () {
  9289. return common.itemCanCheckin(this);
  9290. };
  9291. /**
  9292. * Checks if an item can be expired (based on status)
  9293. * @name Item#canExpire
  9294. * @returns {boolean}
  9295. */
  9296. Item.prototype.canExpire = function () {
  9297. return common.itemCanExpire(this);
  9298. };
  9299. /**
  9300. * Checks if an item can be made available again (based on status)
  9301. * @name Item#canUndoExpire
  9302. * @returns {boolean}
  9303. */
  9304. Item.prototype.canUndoExpire = function () {
  9305. return common.itemCanUndoExpire(this);
  9306. };
  9307. /**
  9308. * Checks if an item can be deleted
  9309. * @name Item#canDelete
  9310. * @returns {boolean}
  9311. */
  9312. Item.prototype.canDelete = function () {
  9313. var can = Base.prototype.canDelete.call(this);
  9314. return can && common.itemCanDelete(this);
  9315. };
  9316. /**
  9317. * Expires an item, puts it in the *expired* status
  9318. * @name Item#expire
  9319. * @param skipRead
  9320. * @returns {promise}
  9321. */
  9322. Item.prototype.expire = function (skipRead) {
  9323. return this._doApiCall({
  9324. method: 'expire',
  9325. skipRead: skipRead
  9326. });
  9327. };
  9328. /**
  9329. * Un-expires an item, puts it in the *available* status again
  9330. * @name Item#undoExpire
  9331. * @param skipRead
  9332. * @returns {promise}
  9333. */
  9334. Item.prototype.undoExpire = function (skipRead) {
  9335. return this._doApiCall({
  9336. method: 'undoExpire',
  9337. skipRead: skipRead
  9338. });
  9339. };
  9340. /**
  9341. * Change the location of an item
  9342. * @name Item#changeLocation
  9343. * @param skipRead
  9344. * @returns {promise}
  9345. */
  9346. Item.prototype.changeLocation = function (locationId, skipRead) {
  9347. return this._doApiCall({
  9348. method: 'changeLocation',
  9349. params: { location: locationId },
  9350. skipRead: skipRead
  9351. });
  9352. };
  9353. /**
  9354. * Adds a QR code to the item
  9355. * @name Item#addCode
  9356. * @param code
  9357. * @param skipRead
  9358. * @returns {promise}
  9359. */
  9360. Item.prototype.addCode = function (code, skipRead) {
  9361. return this._doApiCall({
  9362. method: 'addCodes',
  9363. params: { codes: [code] },
  9364. skipRead: skipRead
  9365. });
  9366. };
  9367. /**
  9368. * Removes a QR code from the item
  9369. * @name Item#removeCode
  9370. * @param code
  9371. * @param skipRead
  9372. * @returns {promise}
  9373. */
  9374. Item.prototype.removeCode = function (code, skipRead) {
  9375. return this._doApiCall({
  9376. method: 'removeCodes',
  9377. params: { codes: [code] },
  9378. skipRead: skipRead
  9379. });
  9380. };
  9381. /**
  9382. * Updates the geo position of an item
  9383. * @name Item#updateGeo
  9384. * @param lat
  9385. * @param lng
  9386. * @param address
  9387. * @param skipRead
  9388. * @returns {promise}
  9389. */
  9390. Item.prototype.updateGeo = function (lat, lng, address, skipRead) {
  9391. return this._doApiCall({
  9392. method: 'updateGeo',
  9393. params: {
  9394. lat: lat,
  9395. lng: lng,
  9396. address: address
  9397. },
  9398. skipRead: skipRead
  9399. });
  9400. };
  9401. /**
  9402. * Gets the last number for items with this name
  9403. * @name Item#getLastNumber
  9404. * @returns {promise}
  9405. */
  9406. Item.prototype.getLastNumber = function () {
  9407. // Do a collection API call to get the last number for items with this name
  9408. return this.ds.call(null, 'getLastItemNumber', { name: this.name });
  9409. };
  9410. /**
  9411. * Updates the basic fields of an item
  9412. * @name Item#updateBasicFields
  9413. * @param name
  9414. * @param skipRead
  9415. * @returns {promise}
  9416. */
  9417. Item.prototype.updateBasicFields = function (name, brand, model, warrantyDate, purchaseDate, purchasePrice, residualValue, skipRead) {
  9418. var that = this, params = {};
  9419. if (name != null && name != this.raw.name) {
  9420. params['name'] = name;
  9421. }
  9422. if (brand != null && brand != this.raw.brand) {
  9423. params['brand'] = brand;
  9424. }
  9425. if (model != null && model != this.raw.model) {
  9426. params['model'] = model;
  9427. }
  9428. if (warrantyDate != null) {
  9429. // Update date or clear date?
  9430. if (typeof warrantyDate === 'object' && warrantyDate.isValid()) {
  9431. // Only update if date changed
  9432. if (!warrantyDate.isSame(this.raw.warrantyDate)) {
  9433. params['warrantyDate'] = warrantyDate;
  9434. }
  9435. } else {
  9436. params['warrantyDate'] = '';
  9437. }
  9438. }
  9439. if (purchaseDate != null) {
  9440. // Update date or clear date
  9441. if (typeof purchaseDate === 'object' && purchaseDate.isValid()) {
  9442. // Only update if date changed
  9443. if (!purchaseDate.isSame(this.raw.purchaseDate)) {
  9444. params['purchaseDate'] = purchaseDate;
  9445. }
  9446. } else {
  9447. params['purchaseDate'] = '';
  9448. }
  9449. }
  9450. if (purchasePrice != null && purchasePrice != this.raw.purchasePrice) {
  9451. params['purchasePrice'] = purchasePrice;
  9452. }
  9453. if (residualValue != null && residualValue != this.raw.residualValue) {
  9454. params['residualValue'] = residualValue;
  9455. }
  9456. // Remove values of null during create
  9457. // Avoids: 422 Unprocessable Entity
  9458. // ValidationError (Item:TZe33wVKWwkKkpACp6Xy5T) (FloatField only accepts float values: ['purchasePrice'])
  9459. //for (var k in params) {
  9460. // if (params[k] == null) {
  9461. // delete params[k];
  9462. // }
  9463. //}
  9464. return this.ds.update(this.id, params, this._fields).then(function (data) {
  9465. return skipRead == true ? data : that._fromJson(data);
  9466. });
  9467. };
  9468. /**
  9469. * Checks if the item can be moved to another category
  9470. * @name Item#canChangeCategory
  9471. * @param category
  9472. * @returns {promise}
  9473. */
  9474. Item.prototype.canChangeCategory = function (category) {
  9475. return this._doApiCall({
  9476. collectionCall: true,
  9477. // it's a collection call, not an Item call
  9478. method: 'canChangeCategory',
  9479. params: {
  9480. pks: [this.id],
  9481. category: category
  9482. },
  9483. skipRead: true,
  9484. // the response is a hash with results and conflicts
  9485. _fields: '*'
  9486. });
  9487. };
  9488. /**
  9489. * Moves the item to another category
  9490. * @name Item#changeCategory
  9491. * @param category
  9492. * @param skipRead
  9493. * @returns {promise}
  9494. */
  9495. Item.prototype.changeCategory = function (category, skipRead) {
  9496. var that = this;
  9497. return this._doApiCall({
  9498. collectionCall: true,
  9499. // it's a collection call, not an Item call
  9500. method: 'changeCategory',
  9501. params: {
  9502. pks: [this.id],
  9503. category: category
  9504. },
  9505. skipRead: true // the response is a list of changed Items
  9506. }).then(function (data) {
  9507. return skipRead == true ? data : that._fromJson(data[0]);
  9508. });
  9509. };
  9510. /**
  9511. * Checks if custody can be taken for an item (based on status)
  9512. * @name Item#canTakeCustody
  9513. * @returns {boolean}
  9514. */
  9515. Item.prototype.canTakeCustody = function () {
  9516. return common.itemCanTakeCustody(this);
  9517. };
  9518. /**
  9519. * Checks if custody can be released for an item (based on status)
  9520. * @name Item#canReleaseCustody
  9521. * @returns {boolean}
  9522. */
  9523. Item.prototype.canReleaseCustody = function () {
  9524. return common.itemCanReleaseCustody(this);
  9525. };
  9526. /**
  9527. * Checks if custody can be transferred for an item (based on status)
  9528. * @name Item#canTransferCustody
  9529. * @returns {boolean}
  9530. */
  9531. Item.prototype.canTransferCustody = function () {
  9532. return common.itemCanTransferCustody(this);
  9533. };
  9534. /**
  9535. * Takes custody of an item
  9536. * Puts it in the *in_custody* status
  9537. * @name Item#takeCustody
  9538. * @param customerId (when null, we'll take the customer of the user making the API call)
  9539. * @param skipRead
  9540. * @returns {promise}
  9541. */
  9542. Item.prototype.takeCustody = function (customerId, skipRead) {
  9543. return this._doApiCall({
  9544. method: 'takeCustody',
  9545. params: { customer: customerId },
  9546. skipRead: skipRead
  9547. });
  9548. };
  9549. /**
  9550. * Releases custody of an item at a certain location
  9551. * Puts it in the *available* status again
  9552. * @name Item#releaseCustody
  9553. * @param locationId
  9554. * @param skipRead
  9555. * @returns {promise}
  9556. */
  9557. Item.prototype.releaseCustody = function (locationId, skipRead) {
  9558. return this._doApiCall({
  9559. method: 'releaseCustody',
  9560. params: { location: locationId },
  9561. skipRead: skipRead
  9562. });
  9563. };
  9564. /**
  9565. * Transfers custody of an item
  9566. * Keeps it in the *in_custody* status
  9567. * @name Item#transferCustody
  9568. * @param customerId (when null, we'll take the customer of the user making the API call)
  9569. * @param skipRead
  9570. * @returns {promise}
  9571. */
  9572. Item.prototype.transferCustody = function (customerId, skipRead) {
  9573. return this._doApiCall({
  9574. method: 'transferCustody',
  9575. params: { customer: customerId },
  9576. skipRead: skipRead
  9577. });
  9578. };
  9579. /**
  9580. * Get a list depreciations
  9581. *
  9582. * @name Item#getDepreciation
  9583. * @param frequancy
  9584. * @returns {promise}
  9585. */
  9586. Item.prototype.getDepreciation = function (frequency) {
  9587. return this.ds.call(this.id, 'getDepreciation', { frequency: frequency || 'quarterly' });
  9588. };
  9589. return Item;
  9590. }(jquery, common, base);
  9591. Kit = function ($, Base, common) {
  9592. var DEFAULTS = {
  9593. name: '',
  9594. items: [],
  9595. status: 'unknown',
  9596. cover: ''
  9597. };
  9598. // Allow overriding the ctor during inheritance
  9599. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  9600. var tmp = function () {
  9601. };
  9602. tmp.prototype = Base.prototype;
  9603. /**
  9604. * Kit class
  9605. * @name Kit
  9606. * @class
  9607. * @constructor
  9608. * @extends Base
  9609. */
  9610. var Kit = function (opt) {
  9611. var spec = $.extend({
  9612. _fields: ['*'],
  9613. crtype: 'cheqroom.types.kit'
  9614. }, opt);
  9615. Base.call(this, spec);
  9616. this.name = spec.name || DEFAULTS.name;
  9617. this.items = spec.items || DEFAULTS.items.slice();
  9618. this.codes = [];
  9619. this.conflicts = [];
  9620. this.status = spec.status || DEFAULTS.status;
  9621. this.cover = spec.cover || DEFAULTS.cover;
  9622. };
  9623. Kit.prototype = new tmp();
  9624. Kit.prototype.constructor = Kit;
  9625. //
  9626. // Specific validators
  9627. /**
  9628. * Checks if name is valid
  9629. * @name Kit#isValidName
  9630. * @method
  9631. * @return {Boolean}
  9632. */
  9633. Kit.prototype.isValidName = function () {
  9634. this.name = $.trim(this.name);
  9635. return this.name.length >= 3;
  9636. };
  9637. /**
  9638. * Check if name is valid and isn't already used
  9639. * @name Kit#isValidNameAsync
  9640. * @method
  9641. * @return {promise}
  9642. */
  9643. Kit.prototype.isNameAvailableAsync = function () {
  9644. // When existing kit is edited, we don't want
  9645. // to check its current name
  9646. if (this.id != null && this.raw != null && this.name == this.raw.name) {
  9647. return $.Deferred().resolve(true);
  9648. }
  9649. // If a previous name available check is pending, abort it
  9650. if (this._dfdNameAvailable) {
  9651. this._dfdNameAvailable.abort();
  9652. }
  9653. this._dfdNameAvailable = this.ds.search({ name: $.trim(this.name) }, '_id');
  9654. return this._dfdNameAvailable.then(function (resp) {
  9655. return resp.count == 0;
  9656. }, function (error) {
  9657. return false;
  9658. });
  9659. };
  9660. //
  9661. // Base overrides
  9662. //
  9663. /**
  9664. * Checks if the Kit has any validation errors
  9665. * @name Kit#isValid
  9666. * @method
  9667. * @returns {boolean}
  9668. * @override
  9669. */
  9670. Kit.prototype.isValid = function () {
  9671. return this.isValidName();
  9672. };
  9673. /**
  9674. * Checks if the kit is empty
  9675. * @name Kit#isEmpty
  9676. * @returns {boolean}
  9677. */
  9678. Kit.prototype.isEmpty = function () {
  9679. // Checks for: name
  9680. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name;
  9681. };
  9682. /**
  9683. * Checks if the Kits is dirty and needs saving
  9684. * @name Kit#isDirty
  9685. * @returns {boolean}
  9686. * @override
  9687. */
  9688. Kit.prototype.isDirty = function () {
  9689. var isDirty = Base.prototype.isDirty.call(this);
  9690. if (!isDirty && this.raw) {
  9691. isDirty = this._isDirtyStringProperty('name');
  9692. }
  9693. return isDirty;
  9694. };
  9695. //
  9696. // Business logic
  9697. //
  9698. // KIT_STATUS = ( 'available', 'checkedout', 'await_checkout', 'in_transit', 'maintenance', 'repair', 'inspection', 'expired', 'in_custody', 'empty', 'incomplete')
  9699. /**
  9700. * Checks if a Kit can be checked out (based on status)
  9701. * @name Kit#canCheckout
  9702. * @method
  9703. * @returns {boolean}
  9704. */
  9705. Kit.prototype.canCheckout = function () {
  9706. return common.kitCanCheckout(this);
  9707. };
  9708. /**
  9709. * Checks if a Kit can be reserved (based on status)
  9710. * @name Kit#canReserve
  9711. * @method
  9712. * @returns {boolean}
  9713. */
  9714. Kit.prototype.canReserve = function () {
  9715. return common.kitCanReserve(this);
  9716. };
  9717. /**
  9718. * addItems; adds a bunch of Items to the transaction using a list of item ids
  9719. * @name Kit#addItems
  9720. * @method
  9721. * @param items
  9722. * @param skipRead
  9723. * @returns {promise}
  9724. */
  9725. Kit.prototype.addItems = function (items, skipRead) {
  9726. if (!this.existsInDb()) {
  9727. return $.Deferred().reject(new Error('Cannot addItems from document without id'));
  9728. }
  9729. return this._doApiCall({
  9730. method: 'addItems',
  9731. params: { items: items },
  9732. skipRead: skipRead
  9733. });
  9734. };
  9735. /**
  9736. * removeItems; removes a bunch of Items from the transaction using a list of item ids
  9737. * @name Kit#removeItems
  9738. * @method
  9739. * @param items (can be null)
  9740. * @param skipRead
  9741. * @returns {promise}
  9742. */
  9743. Kit.prototype.removeItems = function (items, skipRead) {
  9744. if (!this.existsInDb()) {
  9745. return $.Deferred().reject(new Error('Cannot removeItems from document without id'));
  9746. }
  9747. return this._doApiCall({
  9748. method: 'removeItems',
  9749. params: { items: items },
  9750. skipRead: skipRead
  9751. });
  9752. };
  9753. /**
  9754. * moveItem; moves an Item in a kit to another position
  9755. * @name Kit#moveItem
  9756. * @method
  9757. * @param item
  9758. * @param toPos
  9759. * @param skipRead
  9760. * @returns {promise}
  9761. */
  9762. Kit.prototype.moveItem = function (item, toPos, skipRead) {
  9763. if (!this.existsInDb()) {
  9764. return $.Deferred().reject(new Error('Cannot moveItem from document without id'));
  9765. }
  9766. return this._doApiCall({
  9767. method: 'moveItem',
  9768. params: {
  9769. item: item,
  9770. toPos: toPos
  9771. },
  9772. skipRead: skipRead
  9773. });
  9774. };
  9775. /**
  9776. * Adds a QR code to the kit
  9777. * @name Kit#addCode
  9778. * @param code
  9779. * @param skipRead
  9780. * @returns {promise}
  9781. */
  9782. Kit.prototype.addCode = function (code, skipRead) {
  9783. return this._doApiCall({
  9784. method: 'addCodes',
  9785. params: { codes: [code] },
  9786. skipRead: skipRead
  9787. });
  9788. };
  9789. /**
  9790. * Removes a QR code from the kit
  9791. * @name Kit#removeCode
  9792. * @param code
  9793. * @param skipRead
  9794. * @returns {promise}
  9795. */
  9796. Kit.prototype.removeCode = function (code, skipRead) {
  9797. return this._doApiCall({
  9798. method: 'removeCodes',
  9799. params: { codes: [code] },
  9800. skipRead: skipRead
  9801. });
  9802. };
  9803. /**
  9804. * Duplicates an item a number of times
  9805. * @name Kit#duplicate
  9806. * @param {int} times
  9807. * @return {promise}
  9808. */
  9809. Kit.prototype.duplicate = function (times, location, skipRead) {
  9810. return this._doApiCall({
  9811. method: 'duplicate',
  9812. params: {
  9813. times: times,
  9814. location: location
  9815. },
  9816. skipRead: skipRead || true
  9817. });
  9818. };
  9819. /**
  9820. * Checks if custody can be taken for a kit (based on status)
  9821. * @name Kit#canTakeCustody
  9822. * @returns {boolean}
  9823. */
  9824. Kit.prototype.canTakeCustody = function () {
  9825. return common.kitCanTakeCustody(this);
  9826. };
  9827. /**
  9828. * Checks if custody can be released for a kit (based on status)
  9829. * @name Kit#canReleaseCustody
  9830. * @returns {boolean}
  9831. */
  9832. Kit.prototype.canReleaseCustody = function () {
  9833. return common.kitCanReleaseCustody(this);
  9834. };
  9835. /**
  9836. * Checks if custody can be transferred for a kit (based on status)
  9837. * @name Kit#canTransferCustody
  9838. * @returns {boolean}
  9839. */
  9840. Kit.prototype.canTransferCustody = function () {
  9841. return common.kitCanTransferCustody(this);
  9842. };
  9843. /**
  9844. * Takes custody of a kit (and all items in it)
  9845. * Puts it in the *in_custody* status
  9846. * @name Kit#takeCustody
  9847. * @param customerId (when null, we'll take the customer of the user making the API call)
  9848. * @param skipRead
  9849. * @returns {promise}
  9850. */
  9851. Kit.prototype.takeCustody = function (customerId, skipRead) {
  9852. return this._doApiCall({
  9853. method: 'takeCustody',
  9854. params: { customer: customerId },
  9855. skipRead: skipRead
  9856. });
  9857. };
  9858. /**
  9859. * Releases custody of a kit (and all items in it) at a certain location
  9860. * Puts it in the *available* status again
  9861. * @name Kit#releaseCustody
  9862. * @param locationId
  9863. * @param skipRead
  9864. * @returns {promise}
  9865. */
  9866. Kit.prototype.releaseCustody = function (locationId, skipRead) {
  9867. return this._doApiCall({
  9868. method: 'releaseCustody',
  9869. params: { location: locationId },
  9870. skipRead: skipRead
  9871. });
  9872. };
  9873. /**
  9874. * Transfers custody of a kit (and all items in it)
  9875. * Keeps it in the *in_custody* status
  9876. * @name Kit#transferCustody
  9877. * @param customerId (when null, we'll take the customer of the user making the API call)
  9878. * @param skipRead
  9879. * @returns {promise}
  9880. */
  9881. Kit.prototype.transferCustody = function (customerId, skipRead) {
  9882. return this._doApiCall({
  9883. method: 'transferCustody',
  9884. params: { customer: customerId },
  9885. skipRead: skipRead
  9886. });
  9887. };
  9888. //
  9889. // Implementation stuff
  9890. //
  9891. Kit.prototype._getDefaults = function () {
  9892. return DEFAULTS;
  9893. };
  9894. Kit.prototype._toJson = function (options) {
  9895. var data = Base.prototype._toJson.call(this, options);
  9896. data.name = this.name || DEFAULTS.name;
  9897. //data.items --> not via update
  9898. return data;
  9899. };
  9900. Kit.prototype._fromJson = function (data, options) {
  9901. var that = this;
  9902. return Base.prototype._fromJson.call(this, data, options).then(function (data) {
  9903. that.name = data.name || DEFAULTS.name;
  9904. that.items = data.items || DEFAULTS.items.slice();
  9905. that.codes = data.codes || [];
  9906. that.status = data.status || DEFAULTS.status;
  9907. that.cover = data.cover || DEFAULTS.cover;
  9908. that._loadConflicts(that.items);
  9909. $.publish('Kit.fromJson', data);
  9910. return data;
  9911. });
  9912. };
  9913. // Override create method so we can pass items
  9914. // We don't override _toJson to include items, because this would
  9915. // mean that on an update items would also be passed
  9916. Kit.prototype.create = function (skipRead) {
  9917. if (this.existsInDb()) {
  9918. return $.Deferred().reject(new Error('Cannot create document, already exists in database'));
  9919. }
  9920. // Don't check for isEmpty/isValid, if no name is given,
  9921. // that we automatically generate a name on the server
  9922. //if (this.isEmpty()) {
  9923. // return $.Deferred().reject(new Error("Cannot create empty document"));
  9924. //}
  9925. //if (!this.isValid()) {
  9926. // return $.Deferred().reject(new Error("Cannot create, invalid document"));
  9927. //}
  9928. var that = this, data = {
  9929. name: this.name,
  9930. items: this._getIds(this.items)
  9931. };
  9932. // Also add any possible fields we need during `create`
  9933. $.extend(data, this._toJsonFields());
  9934. delete data.id;
  9935. return this.ds.create(data, this._fields).then(function (data) {
  9936. return skipRead == true ? data : that._fromJson(data);
  9937. });
  9938. };
  9939. Kit.prototype._loadConflicts = function (items) {
  9940. var conflicts = [];
  9941. var kitStatus = common.getKitStatus(items);
  9942. // Kit has only conflicts when it's status is incomplete
  9943. if (kitStatus == 'incomplete') {
  9944. $.each(items, function (i, item) {
  9945. switch (item.status) {
  9946. case 'await_checkout':
  9947. conflicts.push({
  9948. kind: 'status',
  9949. item: item._id,
  9950. itemName: item.name,
  9951. itemStatus: item.status,
  9952. order: item.order
  9953. });
  9954. break;
  9955. case 'checkedout':
  9956. conflicts.push({
  9957. kind: 'order',
  9958. item: item._id,
  9959. itemName: item.name,
  9960. itemStatus: item.status,
  9961. order: item.order
  9962. });
  9963. break;
  9964. case 'expired':
  9965. conflicts.push({
  9966. kind: 'status',
  9967. item: item._id,
  9968. itemName: item.name,
  9969. itemStatus: item.status
  9970. });
  9971. break;
  9972. case 'in_custody':
  9973. conflicts.push({
  9974. kind: 'custody',
  9975. item: item._id,
  9976. itemName: item.name,
  9977. itemStatus: item.status
  9978. });
  9979. break;
  9980. }
  9981. });
  9982. }
  9983. this.conflicts = conflicts;
  9984. };
  9985. return Kit;
  9986. }(jquery, base, common);
  9987. Location = function ($, Base) {
  9988. var DEFAULTS = {
  9989. name: '',
  9990. address: ''
  9991. };
  9992. // Allow overriding the ctor during inheritance
  9993. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  9994. var tmp = function () {
  9995. };
  9996. tmp.prototype = Base.prototype;
  9997. /**
  9998. * @name Location
  9999. * @class Location
  10000. * @constructor
  10001. * @extends Base
  10002. * @property {string} name - the location name
  10003. * @property {string} address - the location address
  10004. * @example
  10005. * var loc = new cr.api.Location({ds: dsLocations});
  10006. * loc.name = "Headquarters";
  10007. * loc.address = "4280 Express Road, Sarasota, FL 34238";
  10008. * loc.create()
  10009. * .done(function() {
  10010. * console.log(loc);
  10011. * });
  10012. */
  10013. var Location = function (opt) {
  10014. var spec = $.extend({ _fields: ['*'] }, opt);
  10015. Base.call(this, spec);
  10016. this.name = spec.name || DEFAULTS.name;
  10017. this.address = spec.address || DEFAULTS.address;
  10018. };
  10019. Location.prototype = new tmp();
  10020. Location.prototype.constructor = Location;
  10021. //
  10022. // Document overrides
  10023. //
  10024. /**
  10025. * Checks if the location is empty
  10026. * @method
  10027. * @name Location#isEmpty
  10028. * @returns {boolean}
  10029. */
  10030. Location.prototype.isEmpty = function () {
  10031. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.address == DEFAULTS.address;
  10032. };
  10033. /**
  10034. * Checks if the location is dirty and needs saving
  10035. * @method
  10036. * @name Location#isDirty
  10037. * @returns {boolean}
  10038. */
  10039. Location.prototype.isDirty = function () {
  10040. var isDirty = Base.prototype.isDirty.call(this);
  10041. if (!isDirty && this.raw) {
  10042. var name = this.raw.name || DEFAULTS.name;
  10043. var address = this.raw.address || DEFAULTS.address;
  10044. return this.name != name || this.address != address;
  10045. }
  10046. return isDirty;
  10047. };
  10048. Location.prototype._getDefaults = function () {
  10049. return DEFAULTS;
  10050. };
  10051. Location.prototype._toJson = function (options) {
  10052. var data = Base.prototype._toJson.call(this, options);
  10053. data.name = this.name || DEFAULTS.name;
  10054. data.address = this.address || DEFAULTS.address;
  10055. return data;
  10056. };
  10057. Location.prototype._fromJson = function (data, options) {
  10058. var that = this;
  10059. return Base.prototype._fromJson.call(this, data, options).then(function () {
  10060. that.name = data.name || DEFAULTS.name;
  10061. that.address = data.address || DEFAULTS.address;
  10062. $.publish('location.fromJson', data);
  10063. return data;
  10064. });
  10065. };
  10066. return Location;
  10067. }(jquery, base);
  10068. location = function ($, Base) {
  10069. var DEFAULTS = {
  10070. name: '',
  10071. address: ''
  10072. };
  10073. // Allow overriding the ctor during inheritance
  10074. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  10075. var tmp = function () {
  10076. };
  10077. tmp.prototype = Base.prototype;
  10078. /**
  10079. * @name Location
  10080. * @class Location
  10081. * @constructor
  10082. * @extends Base
  10083. * @property {string} name - the location name
  10084. * @property {string} address - the location address
  10085. * @example
  10086. * var loc = new cr.api.Location({ds: dsLocations});
  10087. * loc.name = "Headquarters";
  10088. * loc.address = "4280 Express Road, Sarasota, FL 34238";
  10089. * loc.create()
  10090. * .done(function() {
  10091. * console.log(loc);
  10092. * });
  10093. */
  10094. var Location = function (opt) {
  10095. var spec = $.extend({ _fields: ['*'] }, opt);
  10096. Base.call(this, spec);
  10097. this.name = spec.name || DEFAULTS.name;
  10098. this.address = spec.address || DEFAULTS.address;
  10099. };
  10100. Location.prototype = new tmp();
  10101. Location.prototype.constructor = Location;
  10102. //
  10103. // Document overrides
  10104. //
  10105. /**
  10106. * Checks if the location is empty
  10107. * @method
  10108. * @name Location#isEmpty
  10109. * @returns {boolean}
  10110. */
  10111. Location.prototype.isEmpty = function () {
  10112. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.address == DEFAULTS.address;
  10113. };
  10114. /**
  10115. * Checks if the location is dirty and needs saving
  10116. * @method
  10117. * @name Location#isDirty
  10118. * @returns {boolean}
  10119. */
  10120. Location.prototype.isDirty = function () {
  10121. var isDirty = Base.prototype.isDirty.call(this);
  10122. if (!isDirty && this.raw) {
  10123. var name = this.raw.name || DEFAULTS.name;
  10124. var address = this.raw.address || DEFAULTS.address;
  10125. return this.name != name || this.address != address;
  10126. }
  10127. return isDirty;
  10128. };
  10129. Location.prototype._getDefaults = function () {
  10130. return DEFAULTS;
  10131. };
  10132. Location.prototype._toJson = function (options) {
  10133. var data = Base.prototype._toJson.call(this, options);
  10134. data.name = this.name || DEFAULTS.name;
  10135. data.address = this.address || DEFAULTS.address;
  10136. return data;
  10137. };
  10138. Location.prototype._fromJson = function (data, options) {
  10139. var that = this;
  10140. return Base.prototype._fromJson.call(this, data, options).then(function () {
  10141. that.name = data.name || DEFAULTS.name;
  10142. that.address = data.address || DEFAULTS.address;
  10143. $.publish('location.fromJson', data);
  10144. return data;
  10145. });
  10146. };
  10147. return Location;
  10148. }(jquery, base);
  10149. dateHelper = function ($, moment) {
  10150. // Add a new function to moment
  10151. moment.fn.toJSONDate = function () {
  10152. // toISOString gives the time in Zulu timezone
  10153. // we want the local timezone but in ISO formatting
  10154. return this.format('YYYY-MM-DD[T]HH:mm:ss.000[Z]');
  10155. };
  10156. // https://github.com/moment/moment/pull/1595
  10157. //m.roundTo('minute', 15); // Round the moment to the nearest 15 minutes.
  10158. //m.roundTo('minute', 15, 'up'); // Round the moment up to the nearest 15 minutes.
  10159. //m.roundTo('minute', 15, 'down'); // Round the moment down to the nearest 15 minutes.
  10160. moment.fn.roundTo = function (units, offset, midpoint) {
  10161. units = moment.normalizeUnits(units);
  10162. offset = offset || 1;
  10163. var roundUnit = function (unit) {
  10164. switch (midpoint) {
  10165. case 'up':
  10166. unit = Math.ceil(unit / offset);
  10167. break;
  10168. case 'down':
  10169. unit = Math.floor(unit / offset);
  10170. break;
  10171. default:
  10172. unit = Math.round(unit / offset);
  10173. break;
  10174. }
  10175. return unit * offset;
  10176. };
  10177. switch (units) {
  10178. case 'year':
  10179. this.year(roundUnit(this.year()));
  10180. break;
  10181. case 'month':
  10182. this.month(roundUnit(this.month()));
  10183. break;
  10184. case 'week':
  10185. this.weekday(roundUnit(this.weekday()));
  10186. break;
  10187. case 'isoWeek':
  10188. this.isoWeekday(roundUnit(this.isoWeekday()));
  10189. break;
  10190. case 'day':
  10191. this.day(roundUnit(this.day()));
  10192. break;
  10193. case 'hour':
  10194. this.hour(roundUnit(this.hour()));
  10195. break;
  10196. case 'minute':
  10197. this.minute(roundUnit(this.minute()));
  10198. break;
  10199. case 'second':
  10200. this.second(roundUnit(this.second()));
  10201. break;
  10202. default:
  10203. this.millisecond(roundUnit(this.millisecond()));
  10204. break;
  10205. }
  10206. return this;
  10207. };
  10208. /*
  10209. useHours = BooleanField(default=True)
  10210. avgCheckoutHours = IntField(default=4)
  10211. roundMinutes = IntField(default=15)
  10212. roundType = StringField(default="nearest", choices=ROUND_TYPE) # nearest, longer, shorter
  10213. */
  10214. var INCREMENT = 15, START_OF_DAY_HRS = 9, END_OF_DAY_HRS = 17;
  10215. /**
  10216. * @name DateHelper
  10217. * @class
  10218. * @constructor
  10219. */
  10220. var DateHelper = function (spec) {
  10221. spec = spec || {};
  10222. this.roundType = spec.roundType || 'nearest';
  10223. this.roundMinutes = spec.roundMinutes || INCREMENT;
  10224. this.timeFormat24 = spec.timeFormat24 ? spec.timeFormat24 : false;
  10225. this._momentFormat = this.timeFormat24 ? 'MMM D [at] H:mm' : 'MMM D [at] h:mm a';
  10226. this.startOfDayHours = spec.startOfDayHours != null ? startOfDayHours : START_OF_DAY_HRS;
  10227. this.endOfDayHours = spec.endOfDayHours != null ? endOfDayHours : END_OF_DAY_HRS;
  10228. };
  10229. /**
  10230. * @name parseDate
  10231. * @method
  10232. * @param data
  10233. * @returns {moment}
  10234. */
  10235. DateHelper.prototype.parseDate = function (data) {
  10236. if (typeof data == 'string' || data instanceof String) {
  10237. // "2014-04-03T12:15:00+00:00" (length 25)
  10238. // "2014-04-03T09:32:43.841000+00:00" (length 32)
  10239. if (data.endsWith('+00:00')) {
  10240. var len = data.length;
  10241. if (len == 25) {
  10242. return moment(data.substring(0, len - 6));
  10243. } else if (len == 32) {
  10244. return moment(data.substring(0, len - 6).split('.')[0]);
  10245. }
  10246. }
  10247. }
  10248. };
  10249. /**
  10250. * @name DateHelper#getNow
  10251. * @method
  10252. * @return {moment}
  10253. */
  10254. DateHelper.prototype.getNow = function () {
  10255. // TODO: Use the right MomentJS constructor
  10256. // This one will be deprecated in the next version
  10257. return moment();
  10258. };
  10259. /**
  10260. * @name DateHelper#getFriendlyDuration
  10261. * @method
  10262. * @param duration
  10263. * @return {}
  10264. */
  10265. DateHelper.prototype.getFriendlyDuration = function (duration) {
  10266. return duration.humanize();
  10267. };
  10268. /**
  10269. * @name DateHelper#getFriendlyDateParts
  10270. * @param date
  10271. * @param now (optional)
  10272. * @param format (optional)
  10273. * @returns [date string,time string]
  10274. */
  10275. DateHelper.prototype.getFriendlyDateParts = function (date, now, format) {
  10276. /*
  10277. moment().calendar() shows friendlier dates
  10278. - when the date is <=7d away:
  10279. - Today at 4:15 PM
  10280. - Yesterday at 4:15 PM
  10281. - Last Monday at 4:15 PM
  10282. - Wednesday at 4:15 PM
  10283. - when the date is >7d away:
  10284. - 07/25/2015
  10285. */
  10286. if (!moment.isMoment(date)) {
  10287. date = moment(date);
  10288. }
  10289. now = now || this.getNow();
  10290. return date.calendar().replace('AM', 'am').replace('PM', 'pm').split(' at ');
  10291. };
  10292. /**
  10293. * Returns a number of friendly date ranges with a name
  10294. * Each date range is a standard transaction duration
  10295. * @name getDateRanges
  10296. * @method
  10297. * @param avgHours
  10298. * @param numRanges
  10299. * @param now
  10300. * @param i18n
  10301. * @returns {Array} [{counter: 1, from: m(), to: m(), hours: 24, option: 'days', title: '1 Day'}, {...}]
  10302. */
  10303. DateHelper.prototype.getDateRanges = function (avgHours, numRanges, now, i18n) {
  10304. if (now && !moment.isMoment(now)) {
  10305. now = moment(now);
  10306. }
  10307. i18n = i18n || {
  10308. year: 'year',
  10309. years: 'years',
  10310. month: 'month',
  10311. months: 'months',
  10312. week: 'week',
  10313. weeks: 'weeks',
  10314. day: 'day',
  10315. days: 'days',
  10316. hour: 'hour',
  10317. hours: 'hours'
  10318. };
  10319. var timeOptions = [
  10320. 'years',
  10321. 'months',
  10322. 'weeks',
  10323. 'days',
  10324. 'hours'
  10325. ], timeHourVals = [
  10326. 365 * 24,
  10327. 30 * 24,
  10328. 7 * 24,
  10329. 24,
  10330. 1
  10331. ], opt = null, val = null, title = null, counter = 0, chosenIndex = -1, ranges = [];
  10332. // Find the range kind that best fits the avgHours (search from long to short)
  10333. for (var i = 0; i < timeOptions.length; i++) {
  10334. val = timeHourVals[i];
  10335. opt = timeOptions[i];
  10336. if (avgHours >= val) {
  10337. if (avgHours % val == 0) {
  10338. chosenIndex = i;
  10339. break;
  10340. }
  10341. }
  10342. }
  10343. now = now || this.getNow();
  10344. if (chosenIndex >= 0) {
  10345. for (var i = 1; i <= numRanges; i++) {
  10346. counter = i * avgHours;
  10347. title = i18n[counter == 1 ? opt.replace('s', '') : opt];
  10348. ranges.push({
  10349. option: opt,
  10350. hours: counter,
  10351. title: counter / timeHourVals[chosenIndex] + ' ' + title,
  10352. from: now.clone(),
  10353. to: now.clone().add(counter, 'hours')
  10354. });
  10355. }
  10356. }
  10357. return ranges;
  10358. };
  10359. /**
  10360. * getFriendlyFromTo
  10361. * returns {fromDate:"", fromTime: "", fromText: "", toDate: "", toTime: "", toText: "", text: ""}
  10362. * @param from
  10363. * @param to
  10364. * @param useHours
  10365. * @param now
  10366. * @param separator
  10367. * @param format
  10368. * @returns {}
  10369. */
  10370. DateHelper.prototype.getFriendlyFromTo = function (from, to, useHours, now, separator, format) {
  10371. now = now || this.getNow();
  10372. var sep = separator || ' - ', fromParts = this.getFriendlyDateParts(from, now, format), toParts = this.getFriendlyDateParts(to, now, format), result = {
  10373. dayDiff: from ? from.startOf('day').diff(to, 'days') : -1,
  10374. fromDate: from ? fromParts[0] : 'No from date set',
  10375. fromTime: useHours && from != null ? fromParts[1] : '',
  10376. toDate: to ? toParts[0] : 'No to date set',
  10377. toTime: useHours && to != null ? toParts[1] : ''
  10378. };
  10379. result.fromText = result.fromDate;
  10380. result.toText = result.toDate;
  10381. if (useHours) {
  10382. if (result.fromTime) {
  10383. result.fromText += ' ' + result.fromTime;
  10384. }
  10385. if (result.toTime) {
  10386. result.toText += ' ' + result.toTime;
  10387. }
  10388. }
  10389. // Build a text based on the dates and times we have
  10390. if (result.dayDiff == 0) {
  10391. if (useHours) {
  10392. result.text = result.fromText + sep + result.toTime;
  10393. } else {
  10394. result.text = result.fromText;
  10395. }
  10396. } else {
  10397. result.text = result.fromText + sep + result.toText;
  10398. }
  10399. return result;
  10400. };
  10401. /**
  10402. * @deprecated use getFriendlyFromToInfo
  10403. * [getFriendlyFromToOld]
  10404. * @param fromDate
  10405. * @param toDate
  10406. * @param groupProfile
  10407. * @return {}
  10408. */
  10409. DateHelper.prototype.getFriendlyFromToOld = function (fromDate, toDate, groupProfile) {
  10410. var mFrom = this.roundFromTime(fromDate, groupProfile);
  10411. var mTo = this.roundToTime(toDate, groupProfile);
  10412. return {
  10413. from: mFrom,
  10414. to: mTo,
  10415. daysBetween: mTo.clone().startOf('day').diff(mFrom.clone().startOf('day'), 'days'),
  10416. duration: moment.duration(mFrom - mTo).humanize(),
  10417. fromText: mFrom.calendar().replace(' at ', ' '),
  10418. toText: mTo.calendar().replace(' at ', ' ')
  10419. };
  10420. };
  10421. /**
  10422. * makeStartDate helps making an start date for a transaction, usually a reservation
  10423. * It will do the standard rounding
  10424. * But also, if you're using dates instead of datetimes,
  10425. * it will try to make smart decisions about which hours to use
  10426. * @param m - the Moment date
  10427. * @param useHours - does the profile use hours?
  10428. * @param dayMode - did the selection happen via calendar day selection? (can be true even if useHours is true)
  10429. * @param minDate - passing in a minimum start-date, will be different for reservations compared to orders
  10430. * @param maxDate - passing in a maximum start-date (not used for the moment)
  10431. * @param now - the current moment (just to make testing easier)
  10432. * @returns {moment}
  10433. * @private
  10434. */
  10435. DateHelper.prototype.makeStartDate = function (m, useHours, dayMode, minDate, maxDate, now) {
  10436. useHours = useHours != null ? useHours : true;
  10437. // is the account set up to use hours?
  10438. dayMode = dayMode != null ? dayMode : false;
  10439. // did the selection come from a calendar fullcalendar day selection?
  10440. now = now || moment();
  10441. // the current time (for unit testing)
  10442. if (useHours && !dayMode) {
  10443. // The account is set up to use hours,
  10444. // and the user picked the hours himself (since it's not picked from a dayMode calendar)
  10445. // We'll just round the from date
  10446. // if it's before the minDate, just take the minDate instead
  10447. m = this.roundTimeFrom(m);
  10448. } else {
  10449. // When we get here we know that either:
  10450. // 1) The account is set up to use hours BUT the date came from a calendar selection that just chose the date part
  10451. // or
  10452. // 2) The account is set up to use days instead of hours
  10453. //
  10454. // Which means we still need to see if we can make a smart decision about the hours part
  10455. // we'll base this on typical business hours (usually 9 to 5)
  10456. var isToday = m.isSame(now, 'day'), startOfBusinessDay = this._makeStartOfBusinessDay(m);
  10457. if (isToday) {
  10458. // The start date is today
  10459. // and the current time is still before business hours
  10460. // we can use the start time to start-of-business hours
  10461. if (m.isBefore(startOfBusinessDay)) {
  10462. m = startOfBusinessDay;
  10463. } else {
  10464. // We're already at the beginning of business hours
  10465. // or even already passed it, just try rounding the
  10466. // time and see if its before minDate
  10467. m = this.roundTimeFrom(m);
  10468. }
  10469. } else {
  10470. // The start date is not today, we can just take the business day start from the date that was passed
  10471. m = startOfBusinessDay;
  10472. }
  10473. }
  10474. // Make sure we never return anything before the mindate
  10475. return minDate && m.isBefore(minDate) ? minDate : m;
  10476. };
  10477. /**
  10478. * makeEndDate helps making an end date for a transaction
  10479. * It will do the standard rounding
  10480. * But also, if you're using dates instead of datetimes,
  10481. * it will try to make smart decisions about which hours to use
  10482. * @param m - the Moment date
  10483. * @param useHours - does the profile use hours?
  10484. * @param dayMode - did the selection happen via calendar day selection? (can be true even if useHours is true)
  10485. * @param minDate
  10486. * @param maxDate
  10487. * @param now - the current moment (just to make testing easier)
  10488. * @returns {moment}
  10489. * @private
  10490. */
  10491. DateHelper.prototype.makeEndDate = function (m, useHours, dayMode, minDate, maxDate, now) {
  10492. useHours = useHours != null ? useHours : true;
  10493. dayMode = dayMode != null ? dayMode : false;
  10494. now = now || moment();
  10495. if (useHours && !dayMode) {
  10496. // The account is set up to use hours,
  10497. // and since dayMode is false,
  10498. // we assume the hours are picked by the user himself
  10499. // just do the rounding and we're done
  10500. m = this.roundTimeTo(m);
  10501. } else {
  10502. // When we get here we know that either:
  10503. // 1) The account is set up to use hours BUT the date came from a calendar selection that just chose the date part
  10504. // or
  10505. // 2) The account is set up to use days instead of hours
  10506. //
  10507. // Which means we still need to see if we can make a smart decision about the hours part
  10508. var isToday = m.isSame(now, 'day'), endOfBusinessDay = this._makeEndOfBusinessDay(m), endOfDay = this._makeEndOfDay(m);
  10509. if (isToday) {
  10510. // The end date is today
  10511. // and the current date is before business hours
  10512. // we can use the end time to end-of-business hours
  10513. if (m.isBefore(endOfBusinessDay)) {
  10514. m = endOfBusinessDay;
  10515. } else {
  10516. m = endOfDay;
  10517. }
  10518. } else if (m.isAfter(endOfBusinessDay)) {
  10519. m = endOfDay;
  10520. } else {
  10521. m = endOfBusinessDay;
  10522. }
  10523. }
  10524. // Make sure we never return a date after the max date
  10525. return maxDate && m.isAfter(maxDate) ? maxDate : m;
  10526. };
  10527. /**
  10528. * [getFriendlyDateText]
  10529. * @param date
  10530. * @param useHours
  10531. * @param now
  10532. * @param format
  10533. * @return {string}
  10534. */
  10535. DateHelper.prototype.getFriendlyDateText = function (date, useHours, now, format) {
  10536. if (date == null) {
  10537. return 'Not set';
  10538. }
  10539. var parts = this.getFriendlyDateParts(date, now, format);
  10540. return useHours ? parts.join(' ') : parts[0];
  10541. };
  10542. /**
  10543. * [addAverageDuration]
  10544. * @param m
  10545. * @returns {moment}
  10546. */
  10547. DateHelper.prototype.addAverageDuration = function (m) {
  10548. // TODO: Read the average order duration from the group.profile
  10549. // add it to the date that was passed
  10550. return m.clone().add(1, 'day');
  10551. };
  10552. /**
  10553. * roundTimeFrom uses the time rounding rules to round a begin datetime
  10554. * @name DateHelper#roundTimeFrom
  10555. * @method
  10556. * @param m
  10557. */
  10558. DateHelper.prototype.roundTimeFrom = function (m) {
  10559. return this.roundMinutes <= 1 ? m : this.roundTime(m, this.roundMinutes, this._typeToDirection(this.roundType, 'from'));
  10560. };
  10561. /**
  10562. * roundTimeTo uses the time rounding rules to round an end datetime
  10563. * @name DateHelper#roundTimeTo
  10564. * @method
  10565. * @param m
  10566. */
  10567. DateHelper.prototype.roundTimeTo = function (m) {
  10568. return this.roundMinutes <= 1 ? m : this.roundTime(m, this.roundMinutes, this._typeToDirection(this.roundType, 'to'));
  10569. };
  10570. /**
  10571. * @name DateHelper#roundTime
  10572. * @method
  10573. * @param m
  10574. * @param inc
  10575. * @param direction
  10576. */
  10577. DateHelper.prototype.roundTime = function (m, inc, direction) {
  10578. var mom = moment.isMoment(m) ? m : moment(m);
  10579. mom.seconds(0).milliseconds(0);
  10580. return mom.roundTo('minute', inc || INCREMENT, direction);
  10581. };
  10582. /**
  10583. * @name DateHelper#roundTimeUp
  10584. * @method
  10585. * @param m
  10586. * @param inc
  10587. */
  10588. DateHelper.prototype.roundTimeUp = function (m, inc) {
  10589. var mom = moment.isMoment(m) ? m : moment(m);
  10590. mom.seconds(0).milliseconds(0);
  10591. return mom.roundTo('minute', inc || INCREMENT, 'up');
  10592. };
  10593. /**
  10594. * @name DateHelper#roundTimeDown
  10595. * @method
  10596. * @param m
  10597. * @param inc
  10598. */
  10599. DateHelper.prototype.roundTimeDown = function (m, inc) {
  10600. var mom = moment.isMoment(m) ? m : moment(m);
  10601. mom.seconds(0).milliseconds(0);
  10602. return mom.roundTo('minute', inc || INCREMENT, 'down');
  10603. };
  10604. DateHelper.prototype._typeToDirection = function (type, fromto) {
  10605. switch (type) {
  10606. case 'longer':
  10607. switch (fromto) {
  10608. case 'from':
  10609. return 'down';
  10610. case 'to':
  10611. return 'up';
  10612. default:
  10613. break;
  10614. }
  10615. break;
  10616. case 'shorter':
  10617. switch (fromto) {
  10618. case 'from':
  10619. return 'up';
  10620. case 'to':
  10621. return 'down';
  10622. default:
  10623. break;
  10624. }
  10625. break;
  10626. default:
  10627. break;
  10628. }
  10629. };
  10630. DateHelper.prototype._makeStartOfBusinessDay = function (m) {
  10631. return m.clone().hours(this.startOfDayHours).minutes(0).seconds(0).milliseconds(0);
  10632. };
  10633. DateHelper.prototype._makeEndOfBusinessDay = function (m) {
  10634. return m.clone().hours(this.endOfDayHours).minutes(0).seconds(0).milliseconds(0);
  10635. };
  10636. DateHelper.prototype._makeEndOfDay = function (m) {
  10637. return m.clone().hours(23).minutes(45).seconds(0).milliseconds(0);
  10638. };
  10639. return DateHelper;
  10640. }(jquery, moment);
  10641. transaction = function ($, api, Base, Location, DateHelper, Helper) {
  10642. var DEFAULTS = {
  10643. status: 'creating',
  10644. from: null,
  10645. to: null,
  10646. due: null,
  10647. contact: null,
  10648. location: null,
  10649. number: '',
  10650. items: [],
  10651. conflicts: [],
  10652. by: null,
  10653. archived: null,
  10654. itemSummary: null,
  10655. name: null
  10656. };
  10657. // Allow overriding the ctor during inheritance
  10658. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  10659. var tmp = function () {
  10660. };
  10661. tmp.prototype = Base.prototype;
  10662. /**
  10663. * @name Transaction
  10664. * @class Transaction
  10665. * @constructor
  10666. * @extends Base
  10667. * @property {boolean} autoCleanup - Automatically cleanup the transaction if it becomes empty?
  10668. * @property {DateHelper} dateHelper - A DateHelper object ref
  10669. * @property {string} status - The transaction status
  10670. * @property {moment} from - The transaction from date
  10671. * @property {moment} to - The transaction to date
  10672. * @property {moment} due - The transaction due date
  10673. * @property {string} number - The booking number
  10674. * @property {string} contact - The Contact.id for this transaction
  10675. * @property {string} location - The Location.id for this transaction
  10676. * @property {Array} items - A list of Item.id strings
  10677. * @property {Array} conflicts - A list of conflict hashes
  10678. */
  10679. var Transaction = function (opt) {
  10680. var spec = $.extend({}, opt);
  10681. Base.call(this, spec);
  10682. this.dsItems = spec.dsItems;
  10683. // we'll also access the /items collection
  10684. // should we automatically delete the transaction from the database?
  10685. this.autoCleanup = spec.autoCleanup != null ? spec.autoCleanup : false;
  10686. this.dateHelper = spec.dateHelper || new DateHelper();
  10687. this.helper = spec.helper || new Helper();
  10688. this.status = spec.status || DEFAULTS.status;
  10689. // the status of the order or reservation
  10690. this.from = spec.from || DEFAULTS.from;
  10691. // a date in the future
  10692. this.to = spec.to || DEFAULTS.to;
  10693. // a date in the future
  10694. this.due = spec.due || DEFAULTS.due;
  10695. // a date even further in the future, we suggest some standard avg durations
  10696. this.number = spec.number || DEFAULTS.number;
  10697. // a booking number
  10698. this.contact = spec.contact || DEFAULTS.contact;
  10699. // a contact id
  10700. this.location = spec.location || DEFAULTS.location;
  10701. // a location id
  10702. this.items = spec.items || DEFAULTS.items.slice();
  10703. // an array of item ids
  10704. this.conflicts = spec.conflicts || DEFAULTS.conflicts.slice();
  10705. // an array of Conflict objects
  10706. this.by = spec.by || DEFAULTS.by;
  10707. this.itemSummary = spec.itemSummary || DEFAULTS.itemSummary;
  10708. this.name = spec.name || DEFAULTS.name;
  10709. };
  10710. Transaction.prototype = new tmp();
  10711. Transaction.prototype.constructor = Base;
  10712. //
  10713. // Date helpers (possibly overwritten)
  10714. //
  10715. /**
  10716. * Gets the now time
  10717. * @returns {Moment}
  10718. */
  10719. Transaction.prototype.getNow = function () {
  10720. return this._getDateHelper().getNow();
  10721. };
  10722. /**
  10723. * Gets the now time rounded
  10724. * @returns {Moment}
  10725. */
  10726. Transaction.prototype.getNowRounded = function () {
  10727. return this._getDateHelper().roundTimeFrom(this.getNow());
  10728. };
  10729. /**
  10730. * Gets the next time slot after a date, by default after now
  10731. * @returns {Moment}
  10732. */
  10733. Transaction.prototype.getNextTimeSlot = function (d) {
  10734. d = d || this.getNowRounded();
  10735. var next = moment(d).add(this._getDateHelper().roundMinutes, 'minutes');
  10736. if (next.isSame(d)) {
  10737. next = next.add(this._getDateHelper().roundMinutes, 'minutes');
  10738. }
  10739. return next;
  10740. };
  10741. /**
  10742. * Gets the lowest possible from date, by default now
  10743. * @method
  10744. * @name Transaction#getMinDateFrom
  10745. * @returns {Moment}
  10746. */
  10747. Transaction.prototype.getMinDateFrom = function () {
  10748. return this.getMinDate();
  10749. };
  10750. /**
  10751. * Gets the highest possible from date, by default years from now
  10752. * @method
  10753. * @name Transaction#getMaxDateFrom
  10754. * @returns {Moment}
  10755. */
  10756. Transaction.prototype.getMaxDateFrom = function () {
  10757. return this.getMaxDate();
  10758. };
  10759. /**
  10760. * Gets the lowest possible to date, by default from +1 timeslot
  10761. * @method
  10762. * @name Transaction#getMinDateTo
  10763. * @returns {Moment}
  10764. */
  10765. Transaction.prototype.getMinDateTo = function () {
  10766. // to can only be one timeslot after the min from date
  10767. return this.getNextTimeSlot(this.getMinDateFrom());
  10768. };
  10769. /**
  10770. * Gets the highest possible to date, by default years from now
  10771. * @method
  10772. * @name Transaction#getMaxDateTo
  10773. * @returns {Moment}
  10774. */
  10775. Transaction.prototype.getMaxDateTo = function () {
  10776. return this.getMaxDate();
  10777. };
  10778. /**
  10779. * Gets the lowest possible due date, by default same as getMinDateTo
  10780. * @method
  10781. * @name Transaction#getMinDateDue
  10782. * @returns {Moment}
  10783. */
  10784. Transaction.prototype.getMinDateDue = function () {
  10785. return this.getMinDateTo();
  10786. };
  10787. /**
  10788. * Gets the highest possible due date, by default same as getMaxDateDue
  10789. * @method
  10790. * @name Transaction#getMaxDateDue
  10791. * @returns {Moment}
  10792. */
  10793. Transaction.prototype.getMaxDateDue = function () {
  10794. return this.getMaxDateTo();
  10795. };
  10796. /**
  10797. * DEPRECATED
  10798. * Gets the lowest possible date to start this transaction
  10799. * @method
  10800. * @name Transaction#getMinDate
  10801. * @returns {Moment} min date
  10802. */
  10803. Transaction.prototype.getMinDate = function () {
  10804. return this.getNow();
  10805. };
  10806. /**
  10807. * DEPRECATED
  10808. * Gets the latest possible date to end this transaction
  10809. * @method
  10810. * @name Transaction#getMaxDate
  10811. * @returns {Moment} max date
  10812. */
  10813. Transaction.prototype.getMaxDate = function () {
  10814. var dateHelper = this._getDateHelper();
  10815. var now = dateHelper.getNow();
  10816. var next = dateHelper.roundTimeTo(now);
  10817. return next.add(2, 'years');
  10818. };
  10819. /**
  10820. * suggestEndDate, makes a new moment() object with a suggested end date,
  10821. * already rounded up according to the group.profile settings
  10822. * @method suggestEndDate
  10823. * @name Transaction#suggestEndDate
  10824. * @param {Moment} m a suggested end date for this transaction
  10825. * @returns {*}
  10826. */
  10827. Transaction.prototype.suggestEndDate = function (m) {
  10828. var dateHelper = this._getDateHelper();
  10829. var end = dateHelper.addAverageDuration(m || dateHelper.getNow());
  10830. return dateHelper.roundTimeTo(end);
  10831. };
  10832. //
  10833. // Base overrides
  10834. //
  10835. /**
  10836. * Checks if the transaction is empty
  10837. * @method isEmpty
  10838. * @name Transaction#isEmpty
  10839. * @returns {boolean}
  10840. */
  10841. Transaction.prototype.isEmpty = function () {
  10842. return Base.prototype.isEmpty.call(this) && this.status == DEFAULTS.status && (this.crtype == 'cheqroom.types.order' ? true : this.from == DEFAULTS.from) && this.to == DEFAULTS.to && this.due == DEFAULTS.due && this.number == DEFAULTS.number && this.contact == DEFAULTS.contact && this.location == DEFAULTS.location && this.items.length == 0 // not DEFAULTS.items? :)
  10843. ;
  10844. };
  10845. /**
  10846. * Checks if the transaction is dirty and needs saving
  10847. * @method
  10848. * @name Transaction#isDirty
  10849. * @returns {boolean}
  10850. */
  10851. Transaction.prototype.isDirty = function () {
  10852. return Base.prototype.isDirty.call(this) || this._isDirtyBasic() || this._isDirtyDates() || this._isDirtyLocation() || this._isDirtyContact() || this._isDirtyItems();
  10853. };
  10854. Transaction.prototype._isDirtyBasic = function () {
  10855. if (this.raw) {
  10856. var status = this.raw.status || DEFAULTS.status;
  10857. return this.status != status;
  10858. } else {
  10859. return false;
  10860. }
  10861. };
  10862. Transaction.prototype._isDirtyDates = function () {
  10863. if (this.raw) {
  10864. var from = this.raw.from || DEFAULTS.from;
  10865. var to = this.raw.to || DEFAULTS.to;
  10866. var due = this.raw.due || DEFAULTS.due;
  10867. return this.from != from || this.to != to || this.due != due;
  10868. } else {
  10869. return false;
  10870. }
  10871. };
  10872. Transaction.prototype._isDirtyLocation = function () {
  10873. if (this.raw) {
  10874. var location = DEFAULTS.location;
  10875. if (this.raw.location) {
  10876. location = this.raw.location._id ? this.raw.location._id : this.raw.location;
  10877. }
  10878. return this.location != location;
  10879. } else {
  10880. return false;
  10881. }
  10882. };
  10883. Transaction.prototype._isDirtyContact = function () {
  10884. if (this.raw) {
  10885. var contact = DEFAULTS.contact;
  10886. if (this.raw.customer) {
  10887. contact = this.raw.customer._id ? this.raw.customer._id : this.raw.customer;
  10888. }
  10889. return this.contact != contact;
  10890. } else {
  10891. return false;
  10892. }
  10893. };
  10894. Transaction.prototype._isDirtyItems = function () {
  10895. if (this.raw) {
  10896. var items = DEFAULTS.items.slice();
  10897. if (this.raw.items) {
  10898. }
  10899. return false;
  10900. } else {
  10901. return false;
  10902. }
  10903. };
  10904. Transaction.prototype._getDefaults = function () {
  10905. return DEFAULTS;
  10906. };
  10907. /**
  10908. * Writes out some shared fields for all transactions
  10909. * Inheriting classes will probably add more to this
  10910. * @param options
  10911. * @returns {object}
  10912. * @private
  10913. */
  10914. Transaction.prototype._toJson = function (options) {
  10915. var data = Base.prototype._toJson.call(this, options);
  10916. //data.started = this.from; // VT: Will be set during checkout
  10917. //data.finished = this.to; // VT: Will be set during final checkin
  10918. data.due = this.due;
  10919. if (this.location) {
  10920. // Make sure we send the location as id, not the entire object
  10921. data.location = this._getId(this.location);
  10922. }
  10923. if (this.contact) {
  10924. // Make sure we send the contact as id, not the entire object
  10925. // VT: It's still called the "customer" field on the backend!
  10926. data.customer = this._getId(this.contact);
  10927. }
  10928. return data;
  10929. };
  10930. /**
  10931. * Reads the transaction from a json object
  10932. * @param data
  10933. * @param options
  10934. * @returns {promise}
  10935. * @private
  10936. */
  10937. Transaction.prototype._fromJson = function (data, options) {
  10938. var that = this;
  10939. return Base.prototype._fromJson.call(this, data, options).then(function () {
  10940. that.cover = null;
  10941. // don't read cover property for Transactions
  10942. that.status = data.status || DEFAULTS.status;
  10943. that.number = data.number || DEFAULTS.number;
  10944. that.location = data.location || DEFAULTS.location;
  10945. that.contact = data.customer || DEFAULTS.contact;
  10946. that.items = data.items || DEFAULTS.items.slice();
  10947. that.by = data.by || DEFAULTS.by;
  10948. that.archived = data.archived || DEFAULTS.archived;
  10949. that.itemSummary = data.itemSummary || DEFAULTS.itemSummary;
  10950. that.name = data.name || DEFAULTS.name;
  10951. return that._getConflicts().then(function (conflicts) {
  10952. that.conflicts = conflicts;
  10953. });
  10954. });
  10955. };
  10956. Transaction.prototype._toLog = function (options) {
  10957. var obj = this._toJson(options);
  10958. obj.minDateFrom = this.getMinDateFrom().toJSONDate();
  10959. obj.maxDateFrom = this.getMaxDateFrom().toJSONDate();
  10960. obj.minDateDue = this.getMinDateDue().toJSONDate();
  10961. obj.maxDateDue = this.getMaxDateDue().toJSONDate();
  10962. obj.minDateTo = this.getMinDateTo().toJSONDate();
  10963. obj.maxDateTo = this.getMaxDateTo().toJSONDate();
  10964. console.log(obj);
  10965. };
  10966. Transaction.prototype._checkFromDateBetweenMinMax = function (d) {
  10967. return this._checkDateBetweenMinMax(d, this.getMinDateFrom(), this.getMaxDateFrom());
  10968. };
  10969. Transaction.prototype._checkDueDateBetweenMinMax = function (d) {
  10970. return this._checkDateBetweenMinMax(d, this.getMinDateDue(), this.getMaxDateDue());
  10971. };
  10972. Transaction.prototype._checkToDateBetweenMinMax = function (d) {
  10973. return this._checkDateBetweenMinMax(d, this.getMinDateTo(), this.getMaxDateTo());
  10974. };
  10975. Transaction.prototype._getUniqueItemIds = function (ids) {
  10976. ids = ids || [];
  10977. //https://stackoverflow.com/questions/38373364/the-best-way-to-remove-duplicate-strings-in-an-array
  10978. return ids.reduce(function (p, c, i, a) {
  10979. if (p.indexOf(c) == -1)
  10980. p.push(c);
  10981. return p;
  10982. }, []);
  10983. };
  10984. // Setters
  10985. // ----
  10986. // From date setters
  10987. /**
  10988. * Clear the transaction from date
  10989. * @method
  10990. * @name Transaction#clearFromDate
  10991. * @param skipRead
  10992. * @returns {promise}
  10993. */
  10994. Transaction.prototype.clearFromDate = function (skipRead) {
  10995. this.from = DEFAULTS.from;
  10996. return this._handleTransaction(skipRead);
  10997. };
  10998. /**
  10999. * Sets the transaction from date
  11000. * @method
  11001. * @name Transaction#setFromDate
  11002. * @param date
  11003. * @param skipRead
  11004. * @returns {promise}
  11005. */
  11006. Transaction.prototype.setFromDate = function (date, skipRead) {
  11007. this.from = this._getDateHelper().roundTimeFrom(date);
  11008. return this._handleTransaction(skipRead);
  11009. };
  11010. // To date setters
  11011. /**
  11012. * Clear the transaction to date
  11013. * @method
  11014. * @name Transaction#clearToDate
  11015. * @param skipRead
  11016. * @returns {promise}
  11017. */
  11018. Transaction.prototype.clearToDate = function (skipRead) {
  11019. this.to = DEFAULTS.to;
  11020. return this._handleTransaction(skipRead);
  11021. };
  11022. /**
  11023. * Sets the transaction to date
  11024. * @method
  11025. * @name Transaction#setToDate
  11026. * @param date
  11027. * @param skipRead
  11028. * @returns {promise}
  11029. */
  11030. Transaction.prototype.setToDate = function (date, skipRead) {
  11031. this.to = this._getDateHelper().roundTimeTo(date);
  11032. return this._handleTransaction(skipRead);
  11033. };
  11034. // Due date setters
  11035. /**
  11036. * Clear the transaction due date
  11037. * @method
  11038. * @name Transaction#clearDueDate
  11039. * @param skipRead
  11040. * @returns {promise}
  11041. */
  11042. Transaction.prototype.clearDueDate = function (skipRead) {
  11043. this.due = DEFAULTS.due;
  11044. return this._handleTransaction(skipRead);
  11045. };
  11046. /**
  11047. * Set the transaction due date
  11048. * @method
  11049. * @name Transaction#setDueDate
  11050. * @param date
  11051. * @param skipRead
  11052. * @returns {promise}
  11053. */
  11054. Transaction.prototype.setDueDate = function (date, skipRead) {
  11055. this.due = this._getDateHelper().roundTimeTo(date);
  11056. return this._handleTransaction(skipRead);
  11057. };
  11058. // Location setters
  11059. /**
  11060. * Sets the location for this transaction
  11061. * @method
  11062. * @name Transaction#setLocation
  11063. * @param locationId
  11064. * @param skipRead skip parsing the returned json response into the transaction
  11065. * @returns {promise}
  11066. */
  11067. Transaction.prototype.setLocation = function (locationId, skipRead) {
  11068. this.location = locationId;
  11069. if (this.existsInDb()) {
  11070. return this._doApiCall({
  11071. method: 'setLocation',
  11072. params: { location: locationId },
  11073. skipRead: skipRead
  11074. });
  11075. } else {
  11076. return this._createTransaction(skipRead);
  11077. }
  11078. };
  11079. /**
  11080. * Clears the location for this transaction
  11081. * @method
  11082. * @name Transaction#clearLocation
  11083. * @param skipRead skip parsing the returned json response into the transaction
  11084. * @returns {promise}
  11085. */
  11086. Transaction.prototype.clearLocation = function (skipRead) {
  11087. var that = this;
  11088. this.location = DEFAULTS.location;
  11089. return this._doApiCall({
  11090. method: 'clearLocation',
  11091. skipRead: skipRead
  11092. }).then(function () {
  11093. return that._ensureTransactionDeleted();
  11094. });
  11095. };
  11096. // Contact setters
  11097. /**
  11098. * Sets the contact for this transaction
  11099. * @method
  11100. * @name Transaction#setContact
  11101. * @param contactId
  11102. * @param skipRead skip parsing the returned json response into the transaction
  11103. * @returns {promise}
  11104. */
  11105. Transaction.prototype.setContact = function (contactId, skipRead) {
  11106. this.contact = contactId;
  11107. if (this.existsInDb()) {
  11108. return this._doApiCall({
  11109. method: 'setCustomer',
  11110. params: { customer: contactId },
  11111. skipRead: skipRead
  11112. });
  11113. } else {
  11114. return this._createTransaction(skipRead);
  11115. }
  11116. };
  11117. /**
  11118. * Clears the contact for this transaction
  11119. * @method
  11120. * @name Transaction#clearContact
  11121. * @param skipRead skip parsing the returned json response into the transaction
  11122. * @returns {promise}
  11123. */
  11124. Transaction.prototype.clearContact = function (skipRead) {
  11125. var that = this;
  11126. this.contact = DEFAULTS.contact;
  11127. return this._doApiCall({
  11128. method: 'clearCustomer',
  11129. skipRead: skipRead
  11130. }).then(function () {
  11131. return that._ensureTransactionDeleted();
  11132. });
  11133. };
  11134. /**
  11135. * Sets transaction name
  11136. * @method
  11137. * @name Transaction#setName
  11138. * @param name
  11139. * @param skipRead skip parsing the returned json response into the transaction
  11140. * @returns {promise}
  11141. */
  11142. Transaction.prototype.setName = function (name, skipRead) {
  11143. return this._doApiCall({
  11144. method: 'setName',
  11145. params: { name: name },
  11146. skipRead: skipRead
  11147. });
  11148. };
  11149. /**
  11150. * Clears transaction name
  11151. * @method
  11152. * @name Transaction#clearName
  11153. * @param skipRead skip parsing the returned json response into the transaction
  11154. * @returns {promise}
  11155. */
  11156. Transaction.prototype.clearName = function (skipRead) {
  11157. return this._doApiCall({
  11158. method: 'clearName',
  11159. skipRead: skipRead
  11160. });
  11161. };
  11162. // Business logic
  11163. // ----
  11164. // Inheriting classes will use the setter functions below to update the object in memory
  11165. // the _handleTransaction will create, update or delete the actual document via the API
  11166. /**
  11167. * addItems; adds a bunch of Items to the transaction using a list of item ids
  11168. * It creates the transaction if it doesn't exist yet
  11169. * @name Transaction#addItems
  11170. * @method
  11171. * @param items
  11172. * @param skipRead
  11173. * @returns {promise}
  11174. */
  11175. Transaction.prototype.addItems = function (items, skipRead) {
  11176. var that = this;
  11177. //Remove duplicate item ids
  11178. items = that._getUniqueItemIds(items);
  11179. return this._ensureTransactionExists(skipRead).then(function () {
  11180. return that._doApiCall({
  11181. method: 'addItems',
  11182. params: { items: items },
  11183. skipRead: skipRead
  11184. });
  11185. });
  11186. };
  11187. /**
  11188. * removeItems; removes a bunch of Items from the transaction using a list of item ids
  11189. * It deletes the transaction if it's empty afterwards and autoCleanup is true
  11190. * @name Transaction#removeItems
  11191. * @method
  11192. * @param items
  11193. * @param skipRead
  11194. * @returns {promise}
  11195. */
  11196. Transaction.prototype.removeItems = function (items, skipRead) {
  11197. var that = this;
  11198. if (!this.existsInDb()) {
  11199. return $.Deferred().reject(new Error('Cannot removeItems from document without id'));
  11200. }
  11201. //Remove duplicate item ids
  11202. items = that._getUniqueItemIds(items);
  11203. return this._doApiCall({
  11204. method: 'removeItems',
  11205. params: { items: items },
  11206. skipRead: skipRead
  11207. }).then(function (data) {
  11208. return that._ensureTransactionDeleted().then(function () {
  11209. return data;
  11210. });
  11211. });
  11212. };
  11213. /**
  11214. * clearItems; removes all Items from the transaction
  11215. * It deletes the transaction if it's empty afterwards and autoCleanup is true
  11216. * @name Transaction#clearItems
  11217. * @method
  11218. * @param skipRead
  11219. * @returns {promise}
  11220. */
  11221. Transaction.prototype.clearItems = function (skipRead) {
  11222. if (!this.existsInDb()) {
  11223. return $.Deferred().reject(new Error('Cannot clearItems from document without id'));
  11224. }
  11225. var that = this;
  11226. return this._doApiCall({
  11227. method: 'clearItems',
  11228. skipRead: skipRead
  11229. }).then(function (data) {
  11230. return that._ensureTransactionDeleted().then(function () {
  11231. return data;
  11232. });
  11233. });
  11234. };
  11235. /**
  11236. * swapItem; swaps one item for another in a transaction
  11237. * @name Transaction#swapItem
  11238. * @method
  11239. * @param fromItem
  11240. * @param toItem
  11241. * @param skipRead
  11242. * @returns {promise}
  11243. */
  11244. Transaction.prototype.swapItem = function (fromItem, toItem, skipRead) {
  11245. if (!this.existsInDb()) {
  11246. return $.Deferred().reject(new Error('Cannot swapItem from document without id'));
  11247. }
  11248. // swapItem cannot create or delete a transaction
  11249. return this._doApiCall({
  11250. method: 'swapItem',
  11251. params: {
  11252. fromItem: fromItem,
  11253. toItem: toItem
  11254. },
  11255. skipRead: skipRead
  11256. });
  11257. };
  11258. /**
  11259. * hasItems; Gets a list of items that are already part of the transaction
  11260. * @name Transaction#hasItems
  11261. * @method
  11262. * @param itemIds array of string values
  11263. * @returns {Array}
  11264. */
  11265. Transaction.prototype.hasItems = function (itemIds) {
  11266. var allItems = this.items || [];
  11267. var duplicates = [];
  11268. var found = null;
  11269. $.each(itemIds, function (i, itemId) {
  11270. $.each(allItems, function (i, it) {
  11271. if (it._id == itemId) {
  11272. found = itemId;
  11273. return false;
  11274. }
  11275. });
  11276. if (found != null) {
  11277. duplicates.push(found);
  11278. }
  11279. });
  11280. return duplicates;
  11281. };
  11282. /**
  11283. * Archive a transaction
  11284. * @name Transaction#archive
  11285. * @param skipRead
  11286. * @returns {promise}
  11287. */
  11288. Transaction.prototype.archive = function (skipRead) {
  11289. if (!this.canArchive()) {
  11290. return $.Deferred().reject(new Error('Cannot archive document'));
  11291. }
  11292. return this._doApiCall({
  11293. method: 'archive',
  11294. params: {},
  11295. skipRead: skipRead
  11296. });
  11297. };
  11298. /**
  11299. * Undo archive of a transaction
  11300. * @name Transaction#undoArchive
  11301. * @param skipRead
  11302. * @returns {promise}
  11303. */
  11304. Transaction.prototype.undoArchive = function (skipRead) {
  11305. if (!this.canUndoArchive()) {
  11306. return $.Deferred().reject(new Error('Cannot unarchive document'));
  11307. }
  11308. return this._doApiCall({
  11309. method: 'undoArchive',
  11310. params: {},
  11311. skipRead: skipRead
  11312. });
  11313. };
  11314. /**
  11315. * Checks if we can archive a transaction (based on status)
  11316. * @name Transaction#canArchive
  11317. * @returns {boolean}
  11318. */
  11319. Transaction.prototype.canArchive = function () {
  11320. return this.archived == null && (this.status == 'cancelled' || this.status == 'closed');
  11321. };
  11322. /**
  11323. * Checks if we can unarchive a transaction (based on status)
  11324. * @name Transaction#canUndoArchive
  11325. * @returns {boolean}
  11326. */
  11327. Transaction.prototype.canUndoArchive = function () {
  11328. return this.archived != null && (this.status == 'cancelled' || this.status == 'closed');
  11329. };
  11330. Transaction.prototype.setField = function (field, value, skipRead) {
  11331. var that = this;
  11332. return this._ensureTransactionExists(skipRead).then(function () {
  11333. return that._doApiCall({
  11334. method: 'setField',
  11335. params: {
  11336. field: field,
  11337. value: value
  11338. },
  11339. skipRead: skipRead
  11340. });
  11341. });
  11342. };
  11343. //
  11344. // Implementation stuff
  11345. //
  11346. /**
  11347. * Gets a list of Conflict objects for this transaction
  11348. * Will be overriden by inheriting classes
  11349. * @returns {promise}
  11350. * @private
  11351. */
  11352. Transaction.prototype._getConflicts = function () {
  11353. return $.Deferred().resolve([]);
  11354. };
  11355. Transaction.prototype._getDateHelper = function () {
  11356. return this.dateHelper;
  11357. };
  11358. /**
  11359. * Searches for Items that are available for this transaction
  11360. * @param params: a dict with params, just like items/search
  11361. * @param listName: restrict search to a certain list
  11362. * @param useAvailabilities (uses items/searchAvailable instead of items/search)
  11363. * @param onlyUnbooked (true by default, only used when useAvailabilities=true)
  11364. * @param skipItems array of item ids that should be skipped
  11365. * @private
  11366. * @returns {*}
  11367. */
  11368. Transaction.prototype._searchItems = function (params, listName, useAvailabilities, onlyUnbooked, skipItems) {
  11369. if (this.dsItems == null) {
  11370. return $.Deferred().reject(new api.ApiBadRequest(this.crtype + ' has no DataSource for items'));
  11371. }
  11372. // Restrict the search to just the Items that are:
  11373. // - at this location
  11374. // - in the specified list (if any)
  11375. params = params || {};
  11376. params.location = this._getId(this.location);
  11377. if (listName != null && listName.length > 0) {
  11378. params.listName = listName;
  11379. }
  11380. // Make sure we only pass the item ids,
  11381. // and not the entire items
  11382. var that = this;
  11383. var skipList = null;
  11384. if (skipItems && skipItems.length) {
  11385. skipList = skipItems.slice(0);
  11386. $.each(skipList, function (i, item) {
  11387. skipList[i] = that._getId(item);
  11388. });
  11389. }
  11390. if (useAvailabilities == true) {
  11391. // We'll use a more advanced API call /items/searchAvailable
  11392. // It's a bit slower and the .count result is not usable
  11393. // It requires some more parameters to be set
  11394. params.onlyUnbooked = onlyUnbooked != null ? onlyUnbooked : true;
  11395. params.fromDate = this.from;
  11396. params.toDate = this.to || this.due;
  11397. //need due date for orders!!!!!
  11398. params._limit = params._limit || 20;
  11399. params._skip = params._skip || 0;
  11400. if (skipList && skipList.length) {
  11401. params.skipItems = skipList;
  11402. }
  11403. return this.dsItems.call(null, 'searchAvailable', params);
  11404. } else {
  11405. // We don't need to use availabilities,
  11406. // we should better use the regular /search
  11407. // it's faster and has better paging :)
  11408. if (skipList && skipList.length) {
  11409. params.pk__nin = skipList;
  11410. }
  11411. return this.dsItems.search(params);
  11412. }
  11413. };
  11414. /**
  11415. * Returns a rejected promise when a date is not between min and max date
  11416. * Otherwise the deferred just resolves to the date
  11417. * It's used to do some quick checks of transaction dates
  11418. * @param date
  11419. * @returns {*}
  11420. * @private
  11421. */
  11422. Transaction.prototype._checkDateBetweenMinMax = function (date, minDate, maxDate) {
  11423. minDate = minDate || this.getMinDate();
  11424. maxDate = maxDate || this.getMaxDate();
  11425. if (date < minDate || date > maxDate) {
  11426. var msg = 'date ' + date.toJSONDate() + ' is outside of min max range ' + minDate.toJSONDate() + '->' + maxDate.toJSONDate();
  11427. return $.Deferred().reject(new api.ApiUnprocessableEntity(msg));
  11428. } else {
  11429. return $.Deferred().resolve(date);
  11430. }
  11431. };
  11432. /**
  11433. * _handleTransaction: creates, updates or deletes a transaction document
  11434. * @returns {*}
  11435. * @private
  11436. */
  11437. Transaction.prototype._handleTransaction = function (skipRead) {
  11438. var isEmpty = this.isEmpty();
  11439. if (this.existsInDb()) {
  11440. if (isEmpty) {
  11441. if (this.autoCleanup) {
  11442. return this._deleteTransaction();
  11443. } else {
  11444. return $.Deferred().resolve();
  11445. }
  11446. } else {
  11447. return this._updateTransaction(skipRead);
  11448. }
  11449. } else if (!isEmpty) {
  11450. return this._createTransaction(skipRead);
  11451. } else {
  11452. return $.Deferred().resolve();
  11453. }
  11454. };
  11455. Transaction.prototype._deleteTransaction = function () {
  11456. return this.delete();
  11457. };
  11458. Transaction.prototype._updateTransaction = function (skipRead) {
  11459. return this.update(skipRead);
  11460. };
  11461. Transaction.prototype._createTransaction = function (skipRead) {
  11462. return this.create(skipRead);
  11463. };
  11464. Transaction.prototype._ensureTransactionExists = function (skipRead) {
  11465. return !this.existsInDb() ? this._createTransaction(skipRead) : $.Deferred().resolve();
  11466. };
  11467. Transaction.prototype._ensureTransactionDeleted = function () {
  11468. return this.isEmpty() && this.autoCleanup ? this._deleteTransaction() : $.Deferred().resolve();
  11469. };
  11470. return Transaction;
  11471. }(jquery, api, base, location, dateHelper, helper);
  11472. conflict = function ($) {
  11473. var DEFAULTS = {
  11474. kind: '',
  11475. doc: '',
  11476. item: '',
  11477. itemName: '',
  11478. locationCurrent: '',
  11479. locationDesired: '',
  11480. fromDate: null,
  11481. toDate: null
  11482. };
  11483. /**
  11484. * Conflict class
  11485. * @name Conflict
  11486. * @class
  11487. * @constructor
  11488. *
  11489. * @param spec
  11490. * @property {string} kind - The conflict kind (status, order, reservation, location)
  11491. * @property {string} doc - The id of the document with which it conflicts
  11492. * @property {string} item - The Item id for this conflict
  11493. * @property {string} itemName - The Item name for this conflict
  11494. * @property {string} locationCurrent - The Location the item is now
  11495. * @property {string} locationDesired - The Location where the item should be
  11496. * @property {moment} fromDate - From when does the conflict start
  11497. * @property {moment} toDate - Until when does the conflict end
  11498. */
  11499. var Conflict = function (spec) {
  11500. this.ds = spec.ds;
  11501. this._fields = spec._fields;
  11502. this.raw = null;
  11503. // the raw json object
  11504. this.kind = spec.kind || DEFAULTS.kind;
  11505. this.doc = spec.doc || DEFAULTS.doc;
  11506. this.item = spec.item || DEFAULTS.item;
  11507. this.itemName = spec.itemName || DEFAULTS.itemName;
  11508. this.locationCurrent = spec.locationCurrent || DEFAULTS.locationCurrent;
  11509. this.locationDesired = spec.locationDesired || DEFAULTS.locationDesired;
  11510. this.fromDate = spec.fromDate || DEFAULTS.fromDate;
  11511. this.toDate = spec.toDate || DEFAULTS.toDate;
  11512. };
  11513. /**
  11514. * _toJson, makes a dict of the object
  11515. * @method
  11516. * @param {object} opt dict
  11517. * @returns {object}
  11518. * @private
  11519. */
  11520. Conflict.prototype._toJson = function (opt) {
  11521. return {
  11522. kind: this.kind,
  11523. doc: this.doc,
  11524. item: this.item,
  11525. itemName: this.itemName,
  11526. locationCurrent: this.locationCurrent,
  11527. locationDesired: this.locationDesired,
  11528. fromDate: this.fromDate,
  11529. toDate: this.toDate
  11530. };
  11531. };
  11532. /**
  11533. * _fromJson
  11534. * @method
  11535. * @param {object} data the json response
  11536. * @param {object} opt dict
  11537. * @returns promise
  11538. * @private
  11539. */
  11540. Conflict.prototype._fromJson = function (data, opt) {
  11541. this.raw = data;
  11542. this.kind = data.kind || DEFAULTS.kind;
  11543. this.item = data.item || DEFAULTS.item;
  11544. this.itemName = data.itemName || DEFAULTS.itemName;
  11545. this.fromDate = data.fromDate || DEFAULTS.fromDate;
  11546. this.toDate = data.toDate || DEFAULTS.toDate;
  11547. return $.Deferred().resolve(data);
  11548. };
  11549. return Conflict;
  11550. }(jquery);
  11551. Order = function ($, api, Transaction, Conflict, common) {
  11552. // Allow overriding the ctor during inheritance
  11553. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  11554. var tmp = function () {
  11555. };
  11556. tmp.prototype = Transaction.prototype;
  11557. /**
  11558. * @name Order
  11559. * @class Order
  11560. * @constructor
  11561. * @extends Transaction
  11562. */
  11563. var Order = function (opt) {
  11564. var spec = $.extend({
  11565. crtype: 'cheqroom.types.order',
  11566. _fields: ['*']
  11567. }, opt);
  11568. Transaction.call(this, spec);
  11569. this.dsReservations = spec.dsReservations;
  11570. };
  11571. Order.prototype = new tmp();
  11572. Order.prototype.constructor = Order;
  11573. //
  11574. // Date helpers; we'll need these for sliding from / to dates during a long user session
  11575. //
  11576. // getMinDateFrom (overwritten)
  11577. // getMaxDateFrom (default)
  11578. // getMinDateDue (default, same as getMinDateTo)
  11579. // getMaxDateDue (default, same as getMinDateTo)
  11580. /**
  11581. * Overwrite min date for order so it is rounded by default
  11582. * Although it's really the server who sets the actual date
  11583. * While an order is creating, we'll always overwrite its from date
  11584. */
  11585. Order.prototype.getMinDateFrom = function () {
  11586. return this.getNowRounded();
  11587. };
  11588. /**
  11589. * Overwrite how the Order.due min date works
  11590. * We want "open" orders to be set due at least 1 timeslot from now
  11591. */
  11592. Order.prototype.getMinDateDue = function () {
  11593. if (this.status == 'open') {
  11594. // Open orders can set their date to be due
  11595. // at least 1 timeslot from now,
  11596. // we can just call the default getMinDateTo function
  11597. return this.getNextTimeSlot();
  11598. } else {
  11599. return Transaction.prototype.getMinDateDue.call(this);
  11600. }
  11601. };
  11602. //
  11603. // Document overrides
  11604. //
  11605. Order.prototype._toJson = function (options) {
  11606. // Should only be used during create
  11607. // and never be called on order update
  11608. // since most updates are done via setter methods
  11609. var data = Transaction.prototype._toJson.call(this, options);
  11610. data.fromDate = this.fromDate != null ? this.fromDate.toJSONDate() : 'null';
  11611. data.toDate = this.toDate != null ? this.toDate.toJSONDate() : 'null';
  11612. data.due = this.due != null ? this.due.toJSONDate() : 'null';
  11613. return data;
  11614. };
  11615. Order.prototype._fromJson = function (data, options) {
  11616. var that = this;
  11617. // Already set the from, to and due dates
  11618. // Transaction._fromJson might need it during _getConflicts
  11619. that.from = data.started == null || data.started == 'null' ? null : data.started;
  11620. that.to = data.finished == null || data.finished == 'null' ? null : data.finished;
  11621. that.due = data.due == null || data.due == 'null' ? null : data.due;
  11622. that.reservation = data.reservation || null;
  11623. return Transaction.prototype._fromJson.call(this, data, options).then(function () {
  11624. $.publish('order.fromJson', data);
  11625. return data;
  11626. });
  11627. };
  11628. //
  11629. // Helpers
  11630. //
  11631. /**
  11632. * Gets a moment duration object
  11633. * @method
  11634. * @name Order#getDuration
  11635. * @returns {duration}
  11636. */
  11637. Order.prototype.getDuration = function () {
  11638. return common.getOrderDuration(this.raw);
  11639. };
  11640. /**
  11641. * Gets a friendly order duration or empty string
  11642. * @method
  11643. * @name Order#getFriendlyDuration
  11644. * @returns {string}
  11645. */
  11646. Order.prototype.getFriendlyDuration = function () {
  11647. return common.getFriendlyOrderDuration(this.raw, this._getDateHelper());
  11648. };
  11649. /**
  11650. * Checks if a PDF document can be generated
  11651. * @method
  11652. * @name Order#canGenerateAgreement
  11653. * @returns {boolean}
  11654. */
  11655. Order.prototype.canGenerateAgreement = function () {
  11656. return this.status == 'open' || this.status == 'closed';
  11657. };
  11658. /**
  11659. * Checks if order can be checked in
  11660. * @method
  11661. * @name Order#canCheckin
  11662. * @returns {boolean}
  11663. */
  11664. Order.prototype.canCheckin = function () {
  11665. return this.status == 'open';
  11666. };
  11667. /**
  11668. * Checks if the order has an reservation linked to it
  11669. * @method
  11670. * @name Order#canGoToReservation
  11671. * @returns {boolean}
  11672. */
  11673. Order.prototype.canGoToReservation = function () {
  11674. return this.reservation != null;
  11675. };
  11676. /**
  11677. * Checks if due date is valid for an creating order
  11678. * oterwise return true
  11679. *
  11680. * @name Order#isValidDueDate
  11681. * @return {Boolean}
  11682. */
  11683. Order.prototype.isValidDueDate = function () {
  11684. var due = this.due, status = this.status, nextTimeSlot = this.getNextTimeSlot(), maxDueDate = this.getMaxDateDue();
  11685. if (status == 'creating' || status == 'open') {
  11686. return due != null && (due.isSame(nextTimeSlot) || due.isAfter(nextTimeSlot));
  11687. }
  11688. return true;
  11689. };
  11690. /**
  11691. * Checks if order can be checked out
  11692. * @method
  11693. * @name Order#canCheckout
  11694. * @returns {boolean}
  11695. */
  11696. Order.prototype.canCheckout = function () {
  11697. var that = this;
  11698. return this.status == 'creating' && this.location != null && (this.contact != null && this.contact.status == 'active') && this.isValidDueDate() && (this.items && this.items.length > 0 && this.items.filter(function (item) {
  11699. return that.id == that.helper.ensureId(item.order);
  11700. }).length > 0);
  11701. };
  11702. /**
  11703. * Checks if order can undo checkout
  11704. * @method
  11705. * @name Order#canUndoCheckout
  11706. * @returns {boolean}
  11707. */
  11708. Order.prototype.canUndoCheckout = function () {
  11709. return this.status == 'open';
  11710. };
  11711. /**
  11712. * Checks if the order can be deleted (based on status)
  11713. * @method
  11714. * @name Order#canDelete
  11715. * @returns {boolean}
  11716. */
  11717. Order.prototype.canDelete = function () {
  11718. return this.status == 'creating';
  11719. };
  11720. /**
  11721. * Checks if items can be added to the checkout (based on status)
  11722. * @method
  11723. * @name Order#canAddItems
  11724. * @returns {boolean}
  11725. */
  11726. Order.prototype.canAddItems = function () {
  11727. return this.status == 'creating';
  11728. };
  11729. /**
  11730. * Checks if items can be removed from the checkout (based on status)
  11731. * @method
  11732. * @name Order#canRemoveItems
  11733. * @returns {boolean}
  11734. */
  11735. Order.prototype.canRemoveItems = function () {
  11736. return this.status == 'creating';
  11737. };
  11738. /**
  11739. * Checks if items can be swapped in the checkout (based on status)
  11740. * @method
  11741. * @name Order#canSwapItems
  11742. * @returns {boolean}
  11743. */
  11744. Order.prototype.canSwapItems = function () {
  11745. return this.status == 'creating';
  11746. };
  11747. /**
  11748. * Checks if we can generate a document for this order (based on status)
  11749. * @name Order#canGenerateDocument
  11750. * @returns {boolean}
  11751. */
  11752. Order.prototype.canGenerateDocument = function () {
  11753. return this.status == 'open' || this.status == 'closed';
  11754. };
  11755. //
  11756. // Base overrides
  11757. //
  11758. //
  11759. // Transaction overrides
  11760. //
  11761. Order.prototype._getConflictsForExtend = function () {
  11762. var conflicts = [];
  11763. // Only orders which are incomplete,
  11764. // but have items and / or due date can have conflicts
  11765. if (this.status == 'open') {
  11766. // Only check for new conflicts on the items
  11767. // that are still checked out under this order
  11768. var items = [], that = this;
  11769. $.each(this.items, function (i, item) {
  11770. if (item.status == 'checkedout' && item.order == that.id) {
  11771. items.push(item);
  11772. }
  11773. });
  11774. // If we have a due date,
  11775. // check if it conflicts with any reservations
  11776. if (this.due) {
  11777. return this._getServerConflicts(this.items, this.from, this.due, this.id, // orderId
  11778. this.helper.ensureId(this.reservation)) // reservationId
  11779. .then(function (serverConflicts) {
  11780. return conflicts.concat(serverConflicts);
  11781. });
  11782. }
  11783. }
  11784. return $.Deferred().resolve(conflicts);
  11785. };
  11786. /**
  11787. * Gets a list of Conflict objects
  11788. * used during Transaction._fromJson
  11789. * @returns {promise}
  11790. * @private
  11791. */
  11792. Order.prototype._getConflicts = function () {
  11793. var conflicts = [];
  11794. // Only orders which are incomplete,
  11795. // but have items and / or due date can have conflicts
  11796. if (this.status == 'creating' && this.items.length > 0) {
  11797. // Get some conflicts we can already calculate on the client side
  11798. conflicts = this._getClientConflicts();
  11799. // If we have a due date,
  11800. // check if it conflicts with any reservations
  11801. if (this.due) {
  11802. return this._getServerConflicts(this.items, this.from, this.due, this.id, // orderId
  11803. this.helper.ensureId(this.reservation)) // reservationId
  11804. .then(function (serverConflicts) {
  11805. return conflicts.concat(serverConflicts);
  11806. });
  11807. }
  11808. }
  11809. return $.Deferred().resolve(conflicts);
  11810. };
  11811. /**
  11812. * Get server side conflicts for items between two dates
  11813. * Also pass extra info like own order and reservation
  11814. * so we can avoid listing conflicts with ourselves
  11815. * @param items array of item objects (not just the ids)
  11816. * @param fromDate
  11817. * @param dueDate
  11818. * @param orderId
  11819. * @param reservationId
  11820. * @returns {*}
  11821. * @private
  11822. */
  11823. Order.prototype._getServerConflicts = function (items, fromDate, dueDate, orderId, reservationId) {
  11824. var conflicts = [], kind = '', transItem = null, itemIds = common.getItemIds(items);
  11825. // Get the availabilities for these items
  11826. return this.dsItems.call(null, 'getAvailabilities', {
  11827. items: itemIds,
  11828. fromDate: fromDate,
  11829. toDate: dueDate
  11830. }).then(function (data) {
  11831. // Run over unavailabilties for these items
  11832. $.each(data, function (i, av) {
  11833. // Find back the more complete item object via the `items` param
  11834. // It has useful info like item.name we can use in the conflict message
  11835. // $.grep returns an array with 1 item,
  11836. // we need reference to the 1st item for transItem
  11837. transItem = $.grep(items, function (item) {
  11838. return item._id == av.item;
  11839. });
  11840. if (transItem && transItem.length > 0) {
  11841. transItem = transItem[0];
  11842. }
  11843. if (transItem != null && transItem.status != 'expired') {
  11844. // Order cannot conflict with itself
  11845. // or with the Reservation from which it was created
  11846. if (av.order != orderId && av.reservation != reservationId) {
  11847. kind = '';
  11848. kind = kind || (av.order ? 'order' : '');
  11849. kind = kind || (av.reservation ? 'reservation' : '');
  11850. conflicts.push(new Conflict({
  11851. kind: kind,
  11852. item: transItem._id,
  11853. itemName: transItem.name,
  11854. fromDate: av.fromDate,
  11855. toDate: av.toDate,
  11856. doc: av.order || av.reservation
  11857. }));
  11858. }
  11859. }
  11860. });
  11861. return conflicts;
  11862. });
  11863. };
  11864. Order.prototype._getClientConflicts = function () {
  11865. // Some conflicts can be checked already on the client
  11866. // We can check if all the items are:
  11867. // - at the right location
  11868. // - not expired
  11869. var conflicts = [], locId = this.helper.ensureId(this.location || '');
  11870. $.each(this.items, function (i, item) {
  11871. if (item.status == 'expired') {
  11872. conflicts.push(new Conflict({
  11873. kind: 'expired',
  11874. item: item._id,
  11875. itemName: item.name,
  11876. locationCurrent: item.location,
  11877. locationDesired: locId
  11878. })); // If order location is defined, check if item
  11879. // is at the right location
  11880. } else if (locId && item.location != locId) {
  11881. conflicts.push(new Conflict({
  11882. kind: 'location',
  11883. item: item._id,
  11884. itemName: item.name,
  11885. locationCurrent: item.location,
  11886. locationDesired: locId
  11887. }));
  11888. }
  11889. });
  11890. return conflicts;
  11891. };
  11892. /**
  11893. * Sets the Order from and due date in a single call
  11894. * _checkFromDueDate will handle the special check for when the order is open
  11895. * @method
  11896. * @name Order#setFromDueDate
  11897. * @param from
  11898. * @param due (optional) if null, we'll take the default average checkout duration as due date
  11899. * @param skipRead
  11900. * @returns {promise}
  11901. */
  11902. Order.prototype.setFromDueDate = function (from, due, skipRead) {
  11903. if (this.status != 'creating') {
  11904. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order from / due date, status is ' + this.status));
  11905. }
  11906. var that = this;
  11907. var roundedFromDate = this.getMinDateFrom();
  11908. var roundedDueDate = due ? this._getDateHelper().roundTimeTo(due) : this._getDateHelper().addAverageDuration(roundedFromDate);
  11909. return this._checkFromDueDate(roundedFromDate, roundedDueDate).then(function () {
  11910. that.from = roundedFromDate;
  11911. that.due = roundedDueDate;
  11912. return that._handleTransaction(skipRead);
  11913. });
  11914. };
  11915. /**
  11916. * Sets the Order from date
  11917. * @method
  11918. * @name Order#setFromDate
  11919. * @param date
  11920. * @param skipRead
  11921. * @returns {promise}
  11922. */
  11923. Order.prototype.setFromDate = function (date, skipRead) {
  11924. if (this.status != 'creating') {
  11925. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order from date, status is ' + this.status));
  11926. }
  11927. var that = this;
  11928. var roundedFromDate = this._getDateHelper().roundTimeFrom(date);
  11929. return this._checkFromDueDate(roundedFromDate, this.due).then(function () {
  11930. that.from = roundedFromDate;
  11931. return that._handleTransaction(skipRead);
  11932. });
  11933. };
  11934. /**
  11935. * Clears the order from date
  11936. * @method
  11937. * @name Order#clearFromDate
  11938. * @param skipRead
  11939. * @returns {promise}
  11940. */
  11941. Order.prototype.clearFromDate = function (skipRead) {
  11942. if (this.status != 'creating') {
  11943. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot clear order from date, status is ' + this.status));
  11944. }
  11945. this.from = null;
  11946. return this._handleTransaction(skipRead);
  11947. };
  11948. /**
  11949. * Sets the order due date
  11950. * _checkFromDueDate will handle the special check for when the order is open
  11951. * @method
  11952. * @name Order#setDueDate
  11953. * @param due
  11954. * @param skipRead
  11955. * @returns {promise}
  11956. */
  11957. Order.prototype.setDueDate = function (due, skipRead) {
  11958. // Cannot change the to-date of a reservation that is not in status "creating"
  11959. if (this.status != 'creating' && this.status != 'open') {
  11960. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order due date, status is ' + this.status));
  11961. }
  11962. // The to date must be:
  11963. // 1) at least 30 minutes into the future
  11964. // 2) at least 15 minutes after the from date (if set)
  11965. var that = this;
  11966. var roundedDueDate = this._getDateHelper().roundTimeTo(due);
  11967. this.from = this.getMinDateFrom();
  11968. return this._checkDueDateBetweenMinMax(roundedDueDate).then(function () {
  11969. that.due = roundedDueDate;
  11970. //If order doesn't exist yet, we set due date in create call
  11971. //otherwise use setDueDate to update transaction
  11972. if (!that.existsInDb()) {
  11973. return that._createTransaction(skipRead);
  11974. } else {
  11975. // If status is open when due date is changed,
  11976. // we need to check for conflicts
  11977. if (that.status == 'open') {
  11978. return that.canExtend(roundedDueDate).then(function (resp) {
  11979. if (resp && resp.result == true) {
  11980. return that.extend(roundedDueDate, skipRead);
  11981. } else {
  11982. return $.Deferred().reject('Cannot extend order to given date because it has conflicts.', resp);
  11983. }
  11984. });
  11985. } else {
  11986. return that._doApiCall({
  11987. method: 'setDueDate',
  11988. params: { due: roundedDueDate },
  11989. skipRead: skipRead
  11990. });
  11991. }
  11992. }
  11993. });
  11994. };
  11995. /**
  11996. * Clears the order due date
  11997. * @method
  11998. * @name Order#clearDueDate
  11999. * @param skipRead
  12000. * @returns {*}
  12001. */
  12002. Order.prototype.clearDueDate = function (skipRead) {
  12003. if (this.status != 'creating') {
  12004. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot clear order due date, status is ' + this.status));
  12005. }
  12006. this.due = null;
  12007. return this._doApiCall({
  12008. method: 'clearDueDate',
  12009. skipRead: skipRead
  12010. });
  12011. };
  12012. Order.prototype.setToDate = function (date, skipRead) {
  12013. throw 'Order.setToDate not implemented, it is set during order close';
  12014. };
  12015. Order.prototype.clearToDate = function (date, skipRead) {
  12016. throw 'Order.clearToDate not implemented, it is set during order close';
  12017. };
  12018. //
  12019. // Business logic calls
  12020. //
  12021. /**
  12022. * Searches for items that could match this order
  12023. * @method
  12024. * @name Order#searchItems
  12025. * @param params
  12026. * @param useAvailabilies
  12027. * @param onlyUnbooked
  12028. * @param skipItems
  12029. * @returns {promise}
  12030. */
  12031. Order.prototype.searchItems = function (params, useAvailabilies, onlyUnbooked, skipItems, listName) {
  12032. return this._searchItems(params, listName != null ? listName : 'available', useAvailabilies, onlyUnbooked, skipItems || this.items);
  12033. };
  12034. /**
  12035. * Checks in the order
  12036. * @method
  12037. * @name Order#checkin
  12038. * @param itemIds
  12039. * @param location
  12040. * @param skipRead
  12041. * @param skipErrorHandling
  12042. * @returns {promise}
  12043. */
  12044. Order.prototype.checkin = function (itemIds, location, skipRead, skipErrorHandling) {
  12045. var that = this;
  12046. return this._doApiCall({
  12047. method: 'checkin',
  12048. params: {
  12049. items: itemIds,
  12050. location: location
  12051. },
  12052. skipRead: skipRead
  12053. }).then(function (resp) {
  12054. return resp;
  12055. }, function (err) {
  12056. if (!skipErrorHandling) {
  12057. if (err && err.code == 422) {
  12058. if (err.opt && err.opt.detail.indexOf('order has status closed') != -1) {
  12059. return that.get();
  12060. } else if (err.opt && err.opt.detail.indexOf('already checked in or used somewhere else') != -1) {
  12061. return that.get();
  12062. }
  12063. }
  12064. }
  12065. //IMPORTANT
  12066. //Need to return a new deferred reject because otherwise
  12067. //done would be triggered in parent deferred
  12068. return $.Deferred().reject(err);
  12069. });
  12070. };
  12071. /**
  12072. * Checks out the order
  12073. * @method
  12074. * @name Order#checkout
  12075. * @param skipRead
  12076. * @param skipErrorHandling
  12077. * @returns {promise}
  12078. */
  12079. Order.prototype.checkout = function (skipRead, skipErrorHandling) {
  12080. var that = this;
  12081. return this._doApiCall({
  12082. method: 'checkout',
  12083. skipRead: skipRead
  12084. }).then(function (resp) {
  12085. return resp;
  12086. }, function (err) {
  12087. if (!skipErrorHandling) {
  12088. if (err && err.code == 422) {
  12089. if (err.opt && err.opt.detail.indexOf('order has status open') != -1) {
  12090. return that.get();
  12091. }
  12092. }
  12093. }
  12094. //IMPORTANT
  12095. //Need to return a new deferred reject because otherwise
  12096. //done would be triggered in parent deferred
  12097. return $.Deferred().reject(err);
  12098. });
  12099. };
  12100. /**
  12101. * Undoes the order checkout
  12102. * @method
  12103. * @name Order#undoCheckout
  12104. * @param skipRead
  12105. * @returns {promise}
  12106. */
  12107. Order.prototype.undoCheckout = function (skipRead, skipErrorHandling) {
  12108. var that = this;
  12109. return this._doApiCall({
  12110. method: 'undoCheckout',
  12111. skipRead: skipRead
  12112. }).then(function (resp) {
  12113. return resp;
  12114. }, function (err) {
  12115. if (!skipErrorHandling) {
  12116. if (err && err.code == 422) {
  12117. if (err.opt && err.opt.detail.indexOf('order has status creating') != -1) {
  12118. return that.get();
  12119. }
  12120. }
  12121. }
  12122. //IMPORTANT
  12123. //Need to return a new deferred reject because otherwise
  12124. //done would be triggered in parent deferred
  12125. return $.Deferred().reject(err);
  12126. });
  12127. };
  12128. /**
  12129. * Checks of order due date can be extended to given date
  12130. * @param {moment} due
  12131. * @param {bool} skipRead
  12132. * @return {promise}
  12133. */
  12134. Order.prototype.canExtend = function (due) {
  12135. // We can only extend orders which are open
  12136. // and for which their due date will be
  12137. // at least 1 timeslot from now
  12138. var can = true;
  12139. if (this.status != 'open' || due.isBefore(this.getNextTimeSlot())) {
  12140. can = false;
  12141. }
  12142. return $.Deferred().resolve({ result: can });
  12143. };
  12144. /**
  12145. * Extends order due date
  12146. * @param {moment} due
  12147. * @param {bool} skipRead
  12148. * @return {promise}
  12149. */
  12150. Order.prototype.extend = function (due, skipRead) {
  12151. var that = this;
  12152. return this.canExtend(due).then(function (resp) {
  12153. if (resp && resp.result == true) {
  12154. return that._doApiCall({
  12155. method: 'extend',
  12156. params: { due: due },
  12157. skipRead: skipRead
  12158. });
  12159. } else {
  12160. return $.Deferred().reject('Cannot extend order to given date because it has conflicts.', resp);
  12161. }
  12162. });
  12163. };
  12164. /**
  12165. * Generates a PDF document for the order
  12166. * @method
  12167. * @name Order#generateDocument
  12168. * @param {string} template id
  12169. * @param {string} signature (base64)
  12170. * @param {bool} skipRead
  12171. * @returns {promise}
  12172. */
  12173. Order.prototype.generateDocument = function (template, signature, skipRead) {
  12174. return this._doApiLongCall({
  12175. method: 'generateDocument',
  12176. params: {
  12177. template: template,
  12178. signature: signature
  12179. },
  12180. skipRead: skipRead
  12181. });
  12182. };
  12183. /**
  12184. * Override _fromCommentsJson to also include linked reservation comments
  12185. * @param data
  12186. * @param options
  12187. * @returns {*}
  12188. * @private
  12189. */
  12190. Order.prototype._fromCommentsJson = function (data, options) {
  12191. var that = this;
  12192. // Also parse reservation comments?
  12193. if (that.dsReservations && data.reservation && data.reservation.comments && data.reservation.comments.length > 0) {
  12194. // Parse Reservation keyValues
  12195. return Base.prototype._fromCommentsJson.call(that, data.reservation, $.extend(options, {
  12196. ds: that.dsReservations,
  12197. fromReservation: true
  12198. })).then(function () {
  12199. var reservationComments = that.comments;
  12200. return Base.prototype._fromCommentsJson.call(that, data, options).then(function () {
  12201. // Add reservation comments
  12202. that.comments = that.comments.concat(reservationComments).sort(function (a, b) {
  12203. return b.modified > a.modified;
  12204. });
  12205. });
  12206. });
  12207. }
  12208. // Use Default comments parser
  12209. return Base.prototype._fromCommentsJson.call(that, data, options);
  12210. };
  12211. /**
  12212. * Override _fromAttachmentsJson to also include linked reservation attachments
  12213. * @param data
  12214. * @param options
  12215. * @returns {*}
  12216. * @private
  12217. */
  12218. Order.prototype._fromAttachmentsJson = function (data, options) {
  12219. var that = this;
  12220. // Also parse reservation comments?
  12221. if (that.dsReservations && data.reservation && data.reservation.comments && data.reservation.comments.length > 0) {
  12222. // Parse Reservation keyValues
  12223. return Base.prototype._fromAttachmentsJson.call(that, data.reservation, $.extend(options, {
  12224. ds: that.dsReservations,
  12225. fromReservation: true
  12226. })).then(function () {
  12227. var reservationAttachments = that.attachments;
  12228. return Base.prototype._fromAttachmentsJson.call(that, data, options).then(function () {
  12229. // Add reservation attachments
  12230. that.attachments = that.attachments.concat(reservationAttachments).sort(function (a, b) {
  12231. return b.modified > a.modified;
  12232. });
  12233. });
  12234. });
  12235. }
  12236. // Use Default attachments parser
  12237. return Base.prototype._fromAttachmentsJson.call(that, data, options);
  12238. };
  12239. //
  12240. // Implementation
  12241. //
  12242. Order.prototype._checkFromDueDate = function (from, due) {
  12243. var dateHelper = this._getDateHelper();
  12244. var roundedFromDate = from;
  12245. //(from) ? this._getHelper().roundTimeFrom(from) : null;
  12246. var roundedDueDate = due;
  12247. //(due) ? this._getHelper().roundTimeTo(due) : null;
  12248. if (roundedFromDate && roundedDueDate) {
  12249. return $.when(this._checkDateBetweenMinMax(roundedFromDate), this._checkDateBetweenMinMax(roundedDueDate)).then(function (fromRes, dueRes) {
  12250. var interval = dateHelper.roundMinutes;
  12251. if (roundedDueDate.diff(roundedFromDate, 'minutes') < interval) {
  12252. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order from date, after (or too close to) to date ' + roundedDueDate.toJSONDate()));
  12253. }
  12254. if (roundedFromDate.diff(roundedDueDate, 'minutes') > interval) {
  12255. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order due date, before (or too close to) from date ' + roundedFromDate.toJSONDate()));
  12256. }
  12257. });
  12258. } else if (roundedFromDate) {
  12259. return this._checkDateBetweenMinMax(roundedFromDate);
  12260. } else if (roundedDueDate) {
  12261. return this._checkDateBetweenMinMax(roundedDueDate);
  12262. } else {
  12263. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot from/due date, both are null'));
  12264. }
  12265. };
  12266. return Order;
  12267. }(jquery, api, transaction, conflict, common);
  12268. PermissionHandler = function () {
  12269. /**
  12270. * PermissionHandler
  12271. * @name PermissionHandler
  12272. * @class PermissionHandler
  12273. * @param user - a user dict
  12274. * @param profile - a group profile dict
  12275. * @param limits - a group limits dict
  12276. * @constructor
  12277. */
  12278. var PermissionHandler = function (user, profile, limits) {
  12279. this.user = user;
  12280. this.profile = profile;
  12281. this.limits = limits;
  12282. // Helper booleans that mix a bunch of role stuff and profile / limits stuff
  12283. this._isOwner = user.isOwner;
  12284. this._isRootOrAdmin = user.role == 'root' || user.role == 'admin';
  12285. this._isRootOrAdminOrUser = user.role == 'root' || user.role == 'admin' || user.role == 'user';
  12286. this._isSelfService = user.role == 'selfservice';
  12287. this._useWebHooks = limits.allowWebHooks;
  12288. this._useOrders = limits.allowOrders && profile.useOrders;
  12289. this._useReservations = limits.allowReservations && profile.useReservations;
  12290. this._usePdf = limits.allowGeneratePdf;
  12291. this._useKits = limits.allowKits && profile.useKits;
  12292. this._useCustody = limits.allowCustody && profile.useCustody;
  12293. this._useGeo = profile.useGeo;
  12294. this._useSelfService = limits.allowSelfService && profile.useSelfService;
  12295. this._useCheckinLocation = this._useOrders && profile.orderCheckinLocation;
  12296. this._usePublicSelfService = limits.allowSelfService && profile.usePublicSelfService;
  12297. this._useOrderTransfers = limits.allowOrderTransfers && profile.useOrderTransfers;
  12298. this._useSendMessage = limits.allowSendMessage && profile.useSendMessage;
  12299. this._useUserSync = limits.allowUserSync && profile.useUserSync;
  12300. this._useFlags = profile.useFlags;
  12301. this._useGeo = profile.useGeo;
  12302. this._useRestrictLocations = limits.allowRestrictLocations && profile.useRestrictLocations;
  12303. this._useReporting = limits.allowReporting && profile.useReporting;
  12304. this._useDepreciations = limits.allowDepreciations && profile.useDepreciations;
  12305. this._canSetFlag = false;
  12306. this._canClearFlag = false;
  12307. switch (user.role) {
  12308. case 'selfservice':
  12309. this._canSetFlag = profile.selfServiceCanSetFlag;
  12310. this._canClearFlag = profile.selfServiceCanClearFlag;
  12311. this._canSetLabel = profile.selfServiceCanSetLabel;
  12312. this._canClearLabel = profile.selfServiceCanClearLabel;
  12313. this._canReadOrders = this._useOrders && profile.selfServiceCanSeeOwnOrders;
  12314. this._canCreateOrders = this._useOrders && profile.selfServiceCanOrder;
  12315. break;
  12316. case 'user':
  12317. this._canSetFlag = profile.userCanSetFlag;
  12318. this._canClearFlag = profile.userCanClearFlag;
  12319. this._canSetLabel = profile.userCanSetLabel;
  12320. this._canClearLabel = profile.userCanClearLabel;
  12321. this._canReadOrders = this._useOrders;
  12322. this._canCreateOrders = this._useOrders;
  12323. break;
  12324. default:
  12325. this._canSetFlag = true;
  12326. this._canClearFlag = true;
  12327. this._canSetLabel = true;
  12328. this._canClearLabel = true;
  12329. this._canReadOrders = this._useOrders;
  12330. this._canCreateOrders = this._useOrders;
  12331. break;
  12332. }
  12333. if (this._isSelfService) {
  12334. // Override some permissions for selfservice users
  12335. this._useReservations = this._useReservations && this._useSelfService && profile.selfServiceCanReserve;
  12336. this._useCustody = this._useCustody && this._useSelfService && profile.selfServiceCanCustody;
  12337. }
  12338. };
  12339. PermissionHandler.prototype.hasAnyAdminPermission = function () {
  12340. return this.hasPermission('create', 'locations') || this.hasPermission('create', 'categories') || this.hasPermission('create', 'webhooks') || this.hasPermission('create', 'users') || this.hasPermission('create', 'templates') || this.hasPermission('create', 'syncs');
  12341. };
  12342. PermissionHandler.prototype.hasDashboardPermission = function (action, data, location) {
  12343. // Selfservice cannot see dashboard if it doesn't has reservation or checkout permissions
  12344. if (this._isSelfService) {
  12345. return this.hasReservationPermission('read') || this.hasCheckoutPermission('read');
  12346. }
  12347. return true;
  12348. };
  12349. PermissionHandler.prototype.hasCalendarPermission = function (action, data, location) {
  12350. // Calendar permission depends on reservation or checkout permission
  12351. return this.hasReservationPermission('read') || this.hasCheckoutPermission('read');
  12352. };
  12353. PermissionHandler.prototype.hasItemPermission = function (action, data, location) {
  12354. return this.hasPermission(action || 'read', 'items', data, location);
  12355. };
  12356. PermissionHandler.prototype.hasItemCustodyPermission = function () {
  12357. return this._useCustody;
  12358. };
  12359. PermissionHandler.prototype.hasItemFlagPermission = function () {
  12360. return this._useFlags;
  12361. };
  12362. PermissionHandler.prototype.hasItemGeoPermission = function () {
  12363. return this._useGeo;
  12364. };
  12365. PermissionHandler.prototype.hasItemDepreciationPermission = function () {
  12366. return this._isRootOrAdmin && this._useDepreciations;
  12367. };
  12368. PermissionHandler.prototype.hasUserSyncPermission = function () {
  12369. return this.hasAccountUserSyncPermission('read');
  12370. };
  12371. PermissionHandler.prototype.hasSelfservicePermission = function () {
  12372. return this._useSelfService;
  12373. };
  12374. PermissionHandler.prototype.hasReportingPermission = function () {
  12375. return this._isRootOrAdmin && this._useReporting;
  12376. };
  12377. PermissionHandler.prototype.hasLabelPermission = function () {
  12378. return this._canSetLabel && this._canClearLabel;
  12379. };
  12380. PermissionHandler.prototype.hasKitPermission = function (action, data, location) {
  12381. return this.hasPermission(action || 'read', 'kits', data, location);
  12382. };
  12383. PermissionHandler.prototype.hasContactPermission = function (action, data, location) {
  12384. return this.hasPermission(action || 'read', 'contacts', data, location);
  12385. };
  12386. PermissionHandler.prototype.hasContactReadOtherPermission = function (action, data, location) {
  12387. return !this._isSelfService;
  12388. };
  12389. PermissionHandler.prototype.hasCheckoutPermission = function (action, data, location) {
  12390. return this.hasPermission(action || 'read', 'orders', data, location);
  12391. };
  12392. PermissionHandler.prototype.hasReservationPermission = function (action, data, location) {
  12393. return this.hasPermission(action || 'read', 'reservations', data, location);
  12394. };
  12395. PermissionHandler.prototype.hasCategoriesPermission = function (action, data, location) {
  12396. return this.hasPermission(action, 'categories', data, location);
  12397. };
  12398. PermissionHandler.prototype.hasNotificationPermission = function (action, data, location) {
  12399. return this.hasPermission(action, 'notifications', data, location);
  12400. };
  12401. PermissionHandler.prototype.hasUserPermission = function (action, data, location) {
  12402. return this.hasPermission(action, 'users', data, location);
  12403. };
  12404. PermissionHandler.prototype.hasLocationPermission = function (action, data, location) {
  12405. return this.hasPermission(action, 'locations', data, location);
  12406. };
  12407. PermissionHandler.prototype.hasRestrictLocationPermission = function () {
  12408. return this._useRestrictLocations;
  12409. };
  12410. PermissionHandler.prototype.hasWebhookPermission = function (action, data, location) {
  12411. return this.hasPermission(action, 'webhooks', data, location);
  12412. };
  12413. PermissionHandler.prototype.hasAccountPermission = function (action, data, location) {
  12414. return this.hasPermission(action, 'account', data, location);
  12415. };
  12416. PermissionHandler.prototype.hasAccountInvoicesPermission = function (action, data, location) {
  12417. return this.hasPermission(action, 'invoices', data, location);
  12418. };
  12419. PermissionHandler.prototype.hasAccountSubscriptionPermission = function (action, data, location) {
  12420. return this.hasPermission(action, 'subscription', data, location);
  12421. };
  12422. PermissionHandler.prototype.hasAccountBillingPermission = function (action, data, location) {
  12423. return this.hasPermission(action, 'billing', data, location);
  12424. };
  12425. PermissionHandler.prototype.hasAccountTemplatePermission = function (action, data, location) {
  12426. return this.hasPermission(action, 'templates', data, location);
  12427. };
  12428. PermissionHandler.prototype.hasAccountUserSyncPermission = function (action, data, location) {
  12429. return this.hasPermission(action, 'syncs', data, location);
  12430. };
  12431. PermissionHandler.prototype.hasAssetTagsPermission = function (action, data, location) {
  12432. //return this.hasPermission(action, "asset-tags", data, location);
  12433. return this.hasAnyAdminPermission();
  12434. };
  12435. PermissionHandler.prototype.hasPermission = function (action, collection, data, location) {
  12436. if (this._isSelfService && !this._useSelfService) {
  12437. return false;
  12438. }
  12439. switch (collection) {
  12440. default:
  12441. return false;
  12442. case 'items':
  12443. switch (action) {
  12444. default:
  12445. return false;
  12446. case 'read':
  12447. return true;
  12448. case 'create':
  12449. case 'duplicate':
  12450. case 'update':
  12451. case 'delete':
  12452. case 'expire':
  12453. case 'undoExpire':
  12454. case 'setFields':
  12455. case 'setField':
  12456. case 'clearField':
  12457. case 'addAttachment':
  12458. case 'addComment':
  12459. case 'updateComment':
  12460. case 'removeComment':
  12461. case 'import':
  12462. case 'export':
  12463. case 'updateGeo':
  12464. case 'changeLocation':
  12465. case 'changeCategory':
  12466. return this._isRootOrAdmin;
  12467. // Permissings for asset labels
  12468. case 'printLabel':
  12469. return this._isRootOrAdmin;
  12470. // Permissions for flags
  12471. case 'setFlag':
  12472. return this._useFlags && this._canSetFlag;
  12473. case 'clearFlag':
  12474. return this._useFlags && this._canClearFlag;
  12475. // Modules
  12476. case 'reserve':
  12477. return this._useReservations;
  12478. case 'checkout':
  12479. return this._canCreateOrders;
  12480. case 'takeCustody':
  12481. case 'releaseCustody':
  12482. return this._useCustody;
  12483. case 'transferCustody':
  12484. return this._useCustody && this._isRootOrAdmin;
  12485. }
  12486. break;
  12487. case 'kits':
  12488. switch (action) {
  12489. default:
  12490. return false;
  12491. case 'read':
  12492. return this._useKits;
  12493. case 'create':
  12494. case 'duplicate':
  12495. case 'update':
  12496. case 'delete':
  12497. case 'setFields':
  12498. case 'setField':
  12499. case 'clearField':
  12500. case 'addAttachment':
  12501. case 'addComment':
  12502. case 'updateComment':
  12503. case 'removeComment':
  12504. case 'addItems':
  12505. case 'removeItems':
  12506. case 'moveItem':
  12507. case 'export':
  12508. return this._useKits && this._isRootOrAdmin;
  12509. // Permissings for asset labels
  12510. case 'printLabel':
  12511. return this._isRootOrAdmin;
  12512. // Permissions for flags
  12513. case 'setFlag':
  12514. return this._useFlags && this._canSetFlag;
  12515. case 'clearFlag':
  12516. return this._useFlags && this._canClearFlag;
  12517. // Other
  12518. case 'takeApart':
  12519. return this.profile.canTakeApartKits;
  12520. // Modules
  12521. // Modules
  12522. case 'reserve':
  12523. return this._useReservations;
  12524. case 'checkout':
  12525. return this._canCreateOrders;
  12526. case 'takeCustody':
  12527. case 'releaseCustody':
  12528. return this._useCustody;
  12529. case 'transferCustody':
  12530. case 'giveCustody':
  12531. return this._useCustody && this._isRootOrAdmin;
  12532. }
  12533. break;
  12534. case 'orders':
  12535. case 'checkouts':
  12536. switch (action) {
  12537. default:
  12538. return false;
  12539. // TODO: Checkin at location
  12540. // TODO: Add items to open check-out
  12541. // CRUD
  12542. case 'create':
  12543. case 'update':
  12544. case 'delete':
  12545. return this._canCreateOrders;
  12546. case 'read':
  12547. return this._canReadOrders;
  12548. // Order specific actions
  12549. case 'setCustomer':
  12550. case 'clearCustomer':
  12551. case 'setLocation':
  12552. case 'clearLocation':
  12553. case 'addItems':
  12554. case 'removeItems':
  12555. case 'swapItems':
  12556. case 'undoCheckout':
  12557. case 'checkout':
  12558. case 'checkin':
  12559. case 'setFields':
  12560. case 'setField':
  12561. case 'clearField':
  12562. return this._canCreateOrders;
  12563. // Generic actions
  12564. case 'addAttachment':
  12565. case 'addComment':
  12566. case 'updateComment':
  12567. case 'removeComment':
  12568. case 'export':
  12569. return this._useOrders;
  12570. case 'archive':
  12571. case 'undoArchive':
  12572. return this._useOrders && this._isRootOrAdmin;
  12573. // Permissions for flags
  12574. case 'setFlag':
  12575. return this._useFlags && this._canSetFlag;
  12576. case 'clearFlag':
  12577. return this._useFlags && this._canClearFlag;
  12578. // Other
  12579. case 'generateDocument':
  12580. return this._usePdf && this._isRootOrAdminOrUser;
  12581. case 'checkinAt':
  12582. return this._canCreateOrders && this._useCheckinLocation;
  12583. case 'forceCheckListCheckin':
  12584. return this.profile.forceCheckListCheckin;
  12585. case 'forceConflictResolving':
  12586. return false; // this.profile.forceConflictResolving;
  12587. }
  12588. break;
  12589. case 'reservations':
  12590. switch (action) {
  12591. default:
  12592. return false;
  12593. // TODO: Add items to open reservation
  12594. // CRUD
  12595. case 'create':
  12596. case 'read':
  12597. case 'update':
  12598. case 'delete':
  12599. // Reservation specific actions
  12600. case 'setFromToDate':
  12601. case 'setCustomer':
  12602. case 'clearCustomer':
  12603. case 'setLocation':
  12604. case 'clearLocation':
  12605. case 'addItems':
  12606. case 'removeItems':
  12607. case 'swapItems':
  12608. case 'reserve':
  12609. case 'undoReserve':
  12610. case 'cancel':
  12611. case 'undoCancel':
  12612. case 'switchToOrder':
  12613. case 'reserveAgain':
  12614. case 'reserveRepeat':
  12615. // Generic actions
  12616. case 'setFields':
  12617. case 'setField':
  12618. case 'clearField':
  12619. case 'addAttachment':
  12620. case 'addComment':
  12621. case 'updateComment':
  12622. case 'removeComment':
  12623. case 'export':
  12624. return this._useReservations;
  12625. case 'makeOrder':
  12626. return this._canCreateOrders;
  12627. case 'archive':
  12628. case 'undoArchive':
  12629. return this._useReservations && this._isRootOrAdmin;
  12630. // Permissions for flags
  12631. case 'setFlag':
  12632. return this._useFlags && this._canSetFlag;
  12633. case 'clearFlag':
  12634. return this._useFlags && this._canClearFlag;
  12635. // Other
  12636. case 'generateDocument':
  12637. return this._usePdf && this._isRootOrAdminOrUser;
  12638. }
  12639. break;
  12640. case 'customers':
  12641. case 'contacts':
  12642. switch (action) {
  12643. default:
  12644. return false;
  12645. case 'read':
  12646. case 'create':
  12647. case 'update':
  12648. case 'delete':
  12649. case 'archive':
  12650. case 'undoArchive':
  12651. case 'setFields':
  12652. case 'setField':
  12653. case 'clearField':
  12654. case 'addAttachment':
  12655. case 'addComment':
  12656. case 'updateComment':
  12657. case 'removeComment':
  12658. case 'import':
  12659. case 'export':
  12660. return this._isRootOrAdminOrUser;
  12661. // Permissions for flags
  12662. case 'setFlag':
  12663. return this._useFlags && this._canSetFlag;
  12664. case 'clearFlag':
  12665. return this._useFlags && this._canClearFlag;
  12666. // Other
  12667. case 'generateDocument':
  12668. return this._usePdf && this._isRootOrAdminOrUser;
  12669. }
  12670. break;
  12671. case 'users':
  12672. switch (action) {
  12673. default:
  12674. return false;
  12675. case 'read':
  12676. return true;
  12677. case 'create':
  12678. case 'update':
  12679. case 'delete':
  12680. case 'linkNewCustomer':
  12681. case 'linkCustomer':
  12682. case 'unLinkCustomer':
  12683. case 'inviteUser':
  12684. case 'archive':
  12685. case 'undoArchive':
  12686. case 'activate':
  12687. case 'deactivate':
  12688. return this._isRootOrAdmin;
  12689. case 'changeAccountOwner':
  12690. return this._isOwner;
  12691. }
  12692. break;
  12693. case 'categories':
  12694. case 'locations':
  12695. switch (action) {
  12696. default:
  12697. return false;
  12698. case 'read':
  12699. return true;
  12700. case 'create':
  12701. case 'update':
  12702. case 'delete':
  12703. case 'archive':
  12704. return this._isRootOrAdmin;
  12705. }
  12706. break;
  12707. case 'syncs':
  12708. switch (action) {
  12709. default:
  12710. return false;
  12711. case 'read':
  12712. case 'create':
  12713. case 'update':
  12714. case 'delete':
  12715. case 'clone':
  12716. case 'testConnection':
  12717. case 'syncUsers':
  12718. return this._useUserSync && this._isRootOrAdmin;
  12719. }
  12720. break;
  12721. case 'notifications':
  12722. switch (action) {
  12723. default:
  12724. return false;
  12725. case 'read':
  12726. case 'create':
  12727. case 'update':
  12728. case 'delete':
  12729. return this._isRootOrAdmin;
  12730. }
  12731. break;
  12732. case 'webhooks':
  12733. switch (action) {
  12734. default:
  12735. return false;
  12736. case 'read':
  12737. case 'create':
  12738. case 'update':
  12739. case 'delete':
  12740. return this._useWebHooks && this._isRootOrAdmin;
  12741. }
  12742. break;
  12743. case 'account':
  12744. switch (action) {
  12745. default:
  12746. return this._isRootOrAdmin;
  12747. case 'reset':
  12748. case 'cancelPlan':
  12749. case 'changePlan':
  12750. return this._isOwner;
  12751. }
  12752. break;
  12753. case 'subscription':
  12754. case 'invoices':
  12755. case 'billing':
  12756. case 'templates':
  12757. switch (action) {
  12758. default:
  12759. return false;
  12760. case 'read':
  12761. case 'create':
  12762. case 'update':
  12763. case 'delete':
  12764. case 'archive':
  12765. case 'undoArchive':
  12766. case 'activate':
  12767. case 'deactivate':
  12768. case 'clone':
  12769. return this._isRootOrAdmin;
  12770. }
  12771. break;
  12772. case 'asset-tags':
  12773. switch (action) {
  12774. default:
  12775. return this._isRootOrAdmin;
  12776. }
  12777. break;
  12778. }
  12779. };
  12780. return PermissionHandler;
  12781. }();
  12782. Reservation = function ($, api, Transaction, Conflict) {
  12783. // Allow overriding the ctor during inheritance
  12784. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  12785. var tmp = function () {
  12786. };
  12787. tmp.prototype = Transaction.prototype;
  12788. /**
  12789. * @name Reservation
  12790. * @class Reservation
  12791. * @constructor
  12792. * @extends Transaction
  12793. * @propery {Array} conflicts - The reservation conflicts
  12794. */
  12795. var Reservation = function (opt) {
  12796. var spec = $.extend({
  12797. crtype: 'cheqroom.types.reservation',
  12798. _fields: ['*']
  12799. }, opt);
  12800. Transaction.call(this, spec);
  12801. this.conflicts = [];
  12802. this.order = null;
  12803. };
  12804. Reservation.prototype = new tmp();
  12805. Reservation.prototype.constructor = Reservation;
  12806. //
  12807. // Date helpers; we'll need these for sliding from / to dates during a long user session
  12808. //
  12809. // getMinDateFrom (overwritten)
  12810. // getMaxDateFrom (default)
  12811. // getMinDateTo (overwritten)
  12812. // getMaxDateTo (default)
  12813. /**
  12814. * Overwrite how we get a min date for reservation
  12815. * Min date is a timeslot after now
  12816. */
  12817. Reservation.prototype.getMinDateFrom = function () {
  12818. return this.getNextTimeSlot();
  12819. };
  12820. Reservation.prototype.getMinDateTo = function () {
  12821. return this.getNextTimeSlot(this.from);
  12822. };
  12823. //
  12824. // Helpers
  12825. //
  12826. /**
  12827. * Gets a moment duration object
  12828. * @method
  12829. * @name Reservation#getDuration
  12830. * @returns {duration}
  12831. */
  12832. Reservation.prototype.getDuration = function () {
  12833. return common.getReservationDuration(this.raw);
  12834. };
  12835. /**
  12836. * Gets a friendly order duration or empty string
  12837. * @method
  12838. * @name Reservation#getFriendlyDuration
  12839. * @returns {string}
  12840. */
  12841. Reservation.prototype.getFriendlyDuration = function () {
  12842. return common.getFriendlyReservationDuration(this.raw, this._getDateHelper());
  12843. };
  12844. /**
  12845. * Checks if from date is valid for open/creating reservation
  12846. * otherwise return always true
  12847. *
  12848. * @return {Boolean}
  12849. */
  12850. Reservation.prototype.isValidFromDate = function () {
  12851. var from = this.from, status = this.status, now = this.getNow();
  12852. if (status == 'creating' || status == 'open') {
  12853. return from != null && from.isAfter(now);
  12854. }
  12855. return true;
  12856. };
  12857. /**
  12858. * Checks if to date is valid for open/creating reservation
  12859. * otherwise return always true
  12860. *
  12861. * @return {Boolean}
  12862. */
  12863. Reservation.prototype.isValidToDate = function () {
  12864. var from = this.from, to = this.to, status = this.status, now = this.getNow();
  12865. if (status == 'creating' || status == 'open') {
  12866. return to != null && to.isAfter(from) && to.isAfter(now);
  12867. }
  12868. return true;
  12869. };
  12870. /**
  12871. * Checks if the reservation can be booked
  12872. * @method
  12873. * @name Reservation#canReserve
  12874. * @returns {boolean}
  12875. */
  12876. Reservation.prototype.canReserve = function () {
  12877. return this.status == 'creating' && this.location && (this.contact && this.contact.status == 'active') && this.isValidFromDate() && this.isValidToDate() && this.items && this.items.length;
  12878. };
  12879. /**
  12880. * Checks if the reservation can be undone (based on status)
  12881. * @method
  12882. * @name Reservation#canUndoReserve
  12883. * @returns {boolean}
  12884. */
  12885. Reservation.prototype.canUndoReserve = function () {
  12886. return this.status == 'open';
  12887. };
  12888. /**
  12889. * Checks if the reservation can be cancelled
  12890. * @method
  12891. * @name Reservation#canCancel
  12892. * @returns {boolean}
  12893. */
  12894. Reservation.prototype.canCancel = function () {
  12895. return this.status == 'open';
  12896. };
  12897. /**
  12898. * Checks if the reservation can be edited
  12899. * @method
  12900. * @name Reservation#canEdit
  12901. * @returns {boolean}
  12902. */
  12903. Reservation.prototype.canEdit = function () {
  12904. return this.status == 'creating';
  12905. };
  12906. /**
  12907. * Checks if the reservation can be deleted
  12908. * @method
  12909. * @name Reservation#canDelete
  12910. * @returns {boolean}
  12911. */
  12912. Reservation.prototype.canDelete = function () {
  12913. return this.status == 'creating';
  12914. };
  12915. /**
  12916. * Checks if items can be added to the reservation (based on status)
  12917. * @method
  12918. * @name Reservation#canAddItems
  12919. * @returns {boolean}
  12920. */
  12921. Reservation.prototype.canAddItems = function () {
  12922. return this.status == 'creating';
  12923. };
  12924. /**
  12925. * Checks if items can be removed from the reservation (based on status)
  12926. * @method
  12927. * @name Reservation#canRemoveItems
  12928. * @returns {boolean}
  12929. */
  12930. Reservation.prototype.canRemoveItems = function () {
  12931. return this.status == 'creating';
  12932. };
  12933. /**
  12934. * Checks if items can be swapped in the reservation (based on status)
  12935. * @method
  12936. * @name Reservation#canSwapItems
  12937. * @returns {boolean}
  12938. */
  12939. Reservation.prototype.canSwapItems = function () {
  12940. return this.status == 'creating' || this.status == 'open';
  12941. };
  12942. /**
  12943. * Checks if the reservation can be turned into an order
  12944. * @method
  12945. * @name Reservation#canMakeOrder
  12946. * @returns {boolean}
  12947. */
  12948. Reservation.prototype.canMakeOrder = function () {
  12949. // Only reservations that meet the following conditions can be made into an order
  12950. // - status: open
  12951. // - to date: is in the future
  12952. // - items: all are available
  12953. if (this.status == 'open' && (this.contact && this.contact.status == 'active') && this.to != null && this.to.isAfter(this.getNow())) {
  12954. var unavailable = this._getUnavailableItems();
  12955. var len = $.map(unavailable, function (n, i) {
  12956. return i;
  12957. }).length;
  12958. // TODO: Why do we need this?
  12959. return len == 0;
  12960. } else {
  12961. return false;
  12962. }
  12963. };
  12964. /**
  12965. * Checks if the reservation has an order linked to it
  12966. * @method
  12967. * @name Reservation#canGoToOrder
  12968. * @returns {boolean}
  12969. */
  12970. Reservation.prototype.canGoToOrder = function () {
  12971. return this.order != null;
  12972. };
  12973. /**
  12974. * Checks if the reservation can be reserved again (based on status)
  12975. * @method
  12976. * @name Reservation#canReserveAgain
  12977. * @returns {boolean}
  12978. */
  12979. Reservation.prototype.canReserveAgain = function () {
  12980. return this.status == 'open' || (this.status == 'closed' || this.status == 'cancelled');
  12981. };
  12982. /**
  12983. * Checks if the reservation can be into recurring reservations (based on status)
  12984. * @method
  12985. * @name Reservation#canReserveRepeat
  12986. * @returns {boolean}
  12987. */
  12988. Reservation.prototype.canReserveRepeat = function () {
  12989. return this.status == 'open' || this.status == 'closed';
  12990. };
  12991. /**
  12992. * Checks if we can generate a document for this reservation (based on status)
  12993. * @name Reservation#canGenerateDocument
  12994. * @returns {boolean}
  12995. */
  12996. Reservation.prototype.canGenerateDocument = function () {
  12997. return this.status == 'open' || this.status == 'closed';
  12998. };
  12999. //
  13000. // Document overrides
  13001. //
  13002. Reservation.prototype._toJson = function (options) {
  13003. var data = Transaction.prototype._toJson.call(this, options);
  13004. data.fromDate = this.from != null ? this.from.toJSONDate() : 'null';
  13005. data.toDate = this.to != null ? this.to.toJSONDate() : 'null';
  13006. return data;
  13007. };
  13008. Reservation.prototype._fromJson = function (data, options) {
  13009. var that = this;
  13010. // Already set the from, to and due dates
  13011. // Transaction._fromJson might need it during _getConflicts
  13012. that.from = data.fromDate == null || data.fromDate == 'null' ? null : data.fromDate;
  13013. that.to = data.toDate == null || data.toDate == 'null' ? null : data.toDate;
  13014. that.due = null;
  13015. that.order = data.order || null;
  13016. that.repeatId = data.repeatId || null;
  13017. that.repeatFrequency = data.repeatFrequency || '';
  13018. return Transaction.prototype._fromJson.call(this, data, options).then(function () {
  13019. $.publish('reservation.fromJson', data);
  13020. return data;
  13021. });
  13022. };
  13023. //
  13024. // Base overrides
  13025. //
  13026. //
  13027. // Transaction overrides
  13028. //
  13029. /**
  13030. * Gets a list of Conflict objects
  13031. * used during Transaction._fromJson
  13032. * @returns {promise}
  13033. * @private
  13034. */
  13035. Reservation.prototype._getConflicts = function () {
  13036. var that = this, conflicts = [], conflict = null;
  13037. // Reservations can only have conflicts
  13038. // when status open OR creating and we have a (location OR (from AND to)) AND at least 1 item
  13039. // So we'll only hit the server if there are possible conflicts.
  13040. //
  13041. // However, some conflicts only start making sense when the reservation fields filled in
  13042. // When you don't have any dates set yet, it makes no sense to show "checked out" conflict
  13043. if ([
  13044. 'creating',
  13045. 'open'
  13046. ].indexOf(this.status) != -1 && this.items && this.items.length && (this.location || this.from && this.to)) {
  13047. var locId = this.location ? this._getId(this.location) : null;
  13048. var showOrderConflicts = this.from && this.to && this.status == 'open';
  13049. var showLocationConflicts = locId != null;
  13050. var showStatusConflicts = true;
  13051. // always show conflicts for expired, custody
  13052. return this.ds.call(this.id, 'getConflicts').then(function (cnflcts) {
  13053. cnflcts = cnflcts || [];
  13054. // Now we have 0 or more conflicts for this reservation
  13055. // run over the items again and find the conflict for each item
  13056. $.each(that.items, function (i, item) {
  13057. conflict = cnflcts.find(function (conflictObj) {
  13058. return conflictObj.item == item._id;
  13059. });
  13060. // Does this item have a server-side conflict?
  13061. if (conflict) {
  13062. var kind = conflict.kind || '';
  13063. kind = kind || (conflict.order ? 'order' : '');
  13064. kind = kind || (conflict.reservation ? 'reservation' : '');
  13065. conflicts.push(new Conflict({
  13066. kind: kind,
  13067. item: item._id,
  13068. itemName: item.name,
  13069. doc: conflict.conflictsWith,
  13070. fromDate: conflict.fromDate,
  13071. toDate: conflict.toDate
  13072. }));
  13073. } else {
  13074. if (showStatusConflicts && item.status == 'expired') {
  13075. conflicts.push(new Conflict({
  13076. kind: 'expired',
  13077. item: item._id,
  13078. itemName: item.name,
  13079. doc: item.order
  13080. }));
  13081. } else if (showStatusConflicts && item.status == 'in_custody') {
  13082. conflicts.push(new Conflict({
  13083. kind: 'custody',
  13084. item: item._id,
  13085. itemName: item.name,
  13086. doc: item.order
  13087. }));
  13088. } else if (showOrderConflicts && item.status != 'available') {
  13089. conflicts.push(new Conflict({
  13090. kind: 'order',
  13091. item: item._id,
  13092. itemName: item.name,
  13093. doc: item.order
  13094. }));
  13095. } else if (showLocationConflicts && item.location != locId) {
  13096. conflicts.push(new Conflict({
  13097. kind: 'location',
  13098. item: item._id,
  13099. itemName: item.name,
  13100. locationCurrent: item.location,
  13101. locationDesired: locId,
  13102. doc: item.order
  13103. }));
  13104. }
  13105. }
  13106. });
  13107. return conflicts;
  13108. });
  13109. }
  13110. return $.Deferred().resolve(conflicts);
  13111. };
  13112. /**
  13113. * Sets the reservation from / to dates in a single call
  13114. * @method
  13115. * @name Reservation#setFromToDate
  13116. * @param from
  13117. * @param to (optional) if null, we'll take the default average checkout duration as due date
  13118. * @param skipRead
  13119. * @returns {*}
  13120. */
  13121. Reservation.prototype.setFromToDate = function (from, to, skipRead) {
  13122. if (this.status != 'creating') {
  13123. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set reservation from / to date, status is ' + this.status));
  13124. }
  13125. var that = this;
  13126. var roundedFromDate = this._getDateHelper().roundTimeFrom(from);
  13127. var roundedToDate = to ? this._getDateHelper().roundTimeTo(to) : this._getDateHelper().addAverageDuration(roundedFromDate);
  13128. return this._checkFromToDate(roundedFromDate, roundedToDate).then(function () {
  13129. that.from = roundedFromDate;
  13130. that.to = roundedToDate;
  13131. return that._doApiCall({
  13132. method: 'setFromToDate',
  13133. params: {
  13134. fromDate: roundedFromDate,
  13135. toDate: roundedToDate
  13136. },
  13137. skipRead: skipRead
  13138. });
  13139. });
  13140. };
  13141. /**
  13142. * setFromDate
  13143. * The from date must be:
  13144. * - bigger than minDate
  13145. * - smaller than maxDate
  13146. * - at least one interval before .to date (if set)
  13147. * @method
  13148. * @name Reservation#setFromDate
  13149. * @param date
  13150. * @param skipRead
  13151. * @returns {*}
  13152. */
  13153. Reservation.prototype.setFromDate = function (date, skipRead) {
  13154. if (this.status != 'creating') {
  13155. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set reservation from date, status is ' + this.status));
  13156. }
  13157. var that = this;
  13158. var dateHelper = this._getDateHelper();
  13159. var interval = dateHelper.roundMinutes;
  13160. var roundedFromDate = dateHelper.roundTimeFrom(date);
  13161. return this._checkFromDateBetweenMinMax(roundedFromDate).then(function () {
  13162. // TODO: Should never get here
  13163. // Must be at least 1 interval before to date, if it's already set
  13164. if (that.to && that.to.diff(roundedFromDate, 'minutes') < interval) {
  13165. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set reservation from date, after (or too close to) to date ' + that.to.toJSONDate()));
  13166. }
  13167. that.from = roundedFromDate;
  13168. //If reservation doesn't exist yet, we set from date in create call
  13169. //otherwise use setFromDate to update transaction
  13170. if (!that.existsInDb()) {
  13171. return that._createTransaction(skipRead);
  13172. } else {
  13173. return that._doApiCall({
  13174. method: 'setFromDate',
  13175. params: { fromDate: roundedFromDate },
  13176. skipRead: skipRead
  13177. });
  13178. }
  13179. });
  13180. };
  13181. /**
  13182. * Clear the reservation from date
  13183. * @method
  13184. * @name Reservation#clearFromDate
  13185. * @param skipRead
  13186. * @returns {*}
  13187. */
  13188. Reservation.prototype.clearFromDate = function (skipRead) {
  13189. if (this.status != 'creating') {
  13190. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot clear reservation from date, status is ' + this.status));
  13191. }
  13192. this.from = null;
  13193. return this._doApiCall({
  13194. method: 'clearFromDate',
  13195. skipRead: skipRead
  13196. });
  13197. };
  13198. /**
  13199. * setToDate
  13200. * The to date must be:
  13201. * - bigger than minDate
  13202. * - smaller than maxDate
  13203. * - at least one interval after the .from date (if set)
  13204. * @method
  13205. * @name Reservation#setToDate
  13206. * @param date
  13207. * @param skipRead
  13208. * @returns {*}
  13209. */
  13210. Reservation.prototype.setToDate = function (date, skipRead) {
  13211. // Cannot change the to-date of a reservation that is not in status "creating"
  13212. if (this.status != 'creating') {
  13213. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set reservation to date, status is ' + this.status));
  13214. }
  13215. // The to date must be:
  13216. // 1) at least 30 minutes into the feature
  13217. // 2) at least 15 minutes after the from date (if set)
  13218. var that = this;
  13219. var dateHelper = this._getDateHelper();
  13220. var interval = dateHelper.roundMinutes;
  13221. var roundedToDate = dateHelper.roundTimeTo(date);
  13222. return this._checkToDateBetweenMinMax(roundedToDate).then(function () {
  13223. if (that.from && that.from.diff(roundedToDate, 'minutes') > interval) {
  13224. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set reservation to date, before (or too close to) to date ' + that.from.toJSONDate()));
  13225. }
  13226. that.to = roundedToDate;
  13227. //If reservation doesn't exist yet, we set to date in create call
  13228. //otherwise use setToDate to update transaction
  13229. if (!that.existsInDb()) {
  13230. return that._createTransaction(skipRead);
  13231. } else {
  13232. return that._doApiCall({
  13233. method: 'setToDate',
  13234. params: { toDate: roundedToDate },
  13235. skipRead: skipRead
  13236. });
  13237. }
  13238. });
  13239. };
  13240. /**
  13241. * Clears the reservation to date
  13242. * @method
  13243. * @name Reservation#clearToDate
  13244. * @param skipRead
  13245. * @returns {*}
  13246. */
  13247. Reservation.prototype.clearToDate = function (skipRead) {
  13248. if (this.status != 'creating') {
  13249. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot clear reservation to date, status is ' + this.status));
  13250. }
  13251. this.to = null;
  13252. return this._doApiCall({
  13253. method: 'clearToDate',
  13254. skipRead: skipRead
  13255. });
  13256. };
  13257. // Reservation does not use due dates
  13258. Reservation.prototype.clearDueDate = function (skipRead) {
  13259. throw 'Reservation.clearDueDate not implemented';
  13260. };
  13261. Reservation.prototype.setDueDate = function (date, skipRead) {
  13262. throw 'Reservation.setDueDate not implemented';
  13263. };
  13264. //
  13265. // Business logic calls
  13266. //
  13267. /**
  13268. * Searches for Items that are available for this reservation
  13269. * @method
  13270. * @name Reservation#searchItems
  13271. * @param params
  13272. * @param useAvailabilies (should always be true, we only use this flag for Order objects)
  13273. * @param onlyUnbooked
  13274. * @returns {*}
  13275. */
  13276. Reservation.prototype.searchItems = function (params, useAvailabilies, onlyUnbooked, skipItems) {
  13277. return this._searchItems(params, null, true, onlyUnbooked, skipItems || this.items);
  13278. };
  13279. /**
  13280. * Books the reservation and sets the status to `open`
  13281. * @method
  13282. * @name Reservation#reserve
  13283. * @param skipRead
  13284. * @param skipErrorHandling
  13285. * @returns {*}
  13286. */
  13287. Reservation.prototype.reserve = function (skipRead, skipErrorHandling) {
  13288. var that = this;
  13289. return this._doApiCall({
  13290. method: 'reserve',
  13291. skipRead: skipRead
  13292. }).then(function (resp) {
  13293. return resp;
  13294. }, function (err) {
  13295. if (!skipErrorHandling) {
  13296. if (err && err.code == 422 && (err.opt && err.opt.detail.indexOf('reservation has status open') != -1)) {
  13297. return that.get();
  13298. }
  13299. }
  13300. //IMPORTANT
  13301. //Need to return a new deferred reject because otherwise
  13302. //done would be triggered in parent deferred
  13303. return $.Deferred().reject(err);
  13304. });
  13305. };
  13306. /**
  13307. * Unbooks the reservation and sets the status to `creating` again
  13308. * @method
  13309. * @name Reservation#undoReserve
  13310. * @param skipRead
  13311. * @param skipErrorHandling
  13312. * @returns {*}
  13313. */
  13314. Reservation.prototype.undoReserve = function (skipRead, skipErrorHandling) {
  13315. var that = this;
  13316. return this._doApiCall({
  13317. method: 'undoReserve',
  13318. skipRead: skipRead
  13319. }).then(function (resp) {
  13320. return resp;
  13321. }, function (err) {
  13322. if (!skipErrorHandling) {
  13323. if (err && err.code == 422 && (err.opt && err.opt.detail.indexOf('reservation has status creating') != -1)) {
  13324. return that.get();
  13325. }
  13326. }
  13327. //IMPORTANT
  13328. //Need to return a new deferred reject because otherwise
  13329. //done would be triggered in parent deferred
  13330. return $.Deferred().reject(err);
  13331. });
  13332. };
  13333. /**
  13334. * Cancels the booked reservation and sets the status to `cancelled`
  13335. * @method
  13336. * @name Reservation#cancel
  13337. * @param message
  13338. * @param skipRead
  13339. * @param skipErrorHandling
  13340. * @returns {*}
  13341. */
  13342. Reservation.prototype.cancel = function (message, skipRead, skipErrorHandling) {
  13343. var that = this;
  13344. return this._doApiCall({
  13345. method: 'cancel',
  13346. params: { message: message || '' },
  13347. skipRead: skipRead
  13348. }).then(function (resp) {
  13349. return resp;
  13350. }, function (err) {
  13351. if (!skipErrorHandling) {
  13352. if (err && err.code == 422 && (err.opt && err.opt.detail.indexOf('reservation has status cancelled') != -1)) {
  13353. return that.get();
  13354. }
  13355. }
  13356. //IMPORTANT
  13357. //Need to return a new deferred reject because otherwise
  13358. //done would be triggered in parent deferred
  13359. return $.Deferred().reject(err);
  13360. });
  13361. };
  13362. /**
  13363. * Cancels repeated reservations and sets the status to `cancelled`
  13364. * @method
  13365. * @name Reservation#cancelRepeat
  13366. * @param message
  13367. * @param skipRead
  13368. * @param skipErrorHandling
  13369. * @returns {*}
  13370. */
  13371. Reservation.prototype.cancelRepeat = function (message, skipRead, skipErrorHandling) {
  13372. var that = this;
  13373. return this._doApiCall({
  13374. method: 'cancelRepeat',
  13375. params: { message: message || '' },
  13376. skipRead: skipRead
  13377. }).then(function (resp) {
  13378. return resp;
  13379. }, function (err) {
  13380. if (!skipErrorHandling) {
  13381. if (err && err.code == 422 && (err.opt && err.opt.detail.indexOf('reservation has status cancelled') != -1)) {
  13382. return that.get();
  13383. }
  13384. }
  13385. //IMPORTANT
  13386. //Need to return a new deferred reject because otherwise
  13387. //done would be triggered in parent deferred
  13388. return $.Deferred().reject(err);
  13389. });
  13390. };
  13391. /**
  13392. * Turns an open reservation into an order (which still needs to be checked out)
  13393. * @method
  13394. * @name Reservation#makeOrder
  13395. * @param skipErrorHandling
  13396. * @returns {*}
  13397. */
  13398. Reservation.prototype.makeOrder = function (skipErrorHandling) {
  13399. var that = this;
  13400. return this._doApiCall({
  13401. method: 'makeOrder',
  13402. skipRead: true
  13403. }).then(function (resp) {
  13404. return resp;
  13405. }, function (err) {
  13406. if (!skipErrorHandling) {
  13407. if (err && err.code == 422 && (err.opt && err.opt.detail.indexOf('reservation has status closed') != -1)) {
  13408. return that.get().then(function (resp) {
  13409. var orderId = that._getId(resp.order);
  13410. // need to return fake order object
  13411. return { _id: orderId };
  13412. });
  13413. }
  13414. }
  13415. //IMPORTANT
  13416. //Need to return a new deferred reject because otherwise
  13417. //done would be triggered in parent deferred
  13418. return $.Deferred().reject(err);
  13419. });
  13420. };
  13421. /**
  13422. * Switch reservation to order
  13423. * @method
  13424. * @name Reservation#switchToOrder
  13425. * @return {*}
  13426. */
  13427. Reservation.prototype.switchToOrder = function () {
  13428. return this._doApiCall({
  13429. method: 'switchToOrder',
  13430. skipRead: true
  13431. });
  13432. };
  13433. /**
  13434. * Generates a PDF document for the reservation
  13435. * @method
  13436. * @name Reservation#generateDocument
  13437. * @param {string} template id
  13438. * @param {string} signature (base64)
  13439. * @param {bool} skipRead
  13440. * @returns {promise}
  13441. */
  13442. Reservation.prototype.generateDocument = function (template, signature, skipRead) {
  13443. return this._doApiLongCall({
  13444. method: 'generateDocument',
  13445. params: {
  13446. template: template,
  13447. signature: signature
  13448. },
  13449. skipRead: skipRead
  13450. });
  13451. };
  13452. /**
  13453. * Creates a new, incomplete reservation with the same info
  13454. * as the original reservation but other fromDate, toDate
  13455. * Important; the response will be another Reservation document!
  13456. * @method
  13457. * @name Reservation#reserveAgain
  13458. * @param fromDate
  13459. * @param toDate
  13460. * @param customer
  13461. * @param location
  13462. * @param skipRead
  13463. * @returns {promise}
  13464. */
  13465. Reservation.prototype.reserveAgain = function (fromDate, toDate, customer, location, skipRead) {
  13466. var params = {
  13467. location: location,
  13468. customer: customer
  13469. };
  13470. if (fromDate) {
  13471. params.fromDate = fromDate;
  13472. }
  13473. if (toDate) {
  13474. params.toDate = toDate;
  13475. }
  13476. return this._doApiCall({
  13477. method: 'reserveAgain',
  13478. params: params,
  13479. skipRead: skipRead
  13480. });
  13481. };
  13482. /**
  13483. * Creates a list of new reservations with `open` status
  13484. * as the original reservation but other fromDate, toDate
  13485. * Important; the response will be a list of other Reservation documents
  13486. * @method
  13487. * @name Reservation#reserveRepeat
  13488. * @param frequency (days, weeks, weekdays, months)
  13489. * @param customer
  13490. * @param location
  13491. * @param until
  13492. * @returns {promise}
  13493. */
  13494. Reservation.prototype.reserveRepeat = function (frequency, until, customer, location) {
  13495. return this._doApiCall({
  13496. method: 'reserveRepeat',
  13497. params: {
  13498. frequency: frequency,
  13499. until: until,
  13500. customer: customer,
  13501. location: location
  13502. },
  13503. skipRead: true
  13504. }); // response is a array of reservations
  13505. };
  13506. //
  13507. // Implementation
  13508. //
  13509. Reservation.prototype._checkFromToDate = function (from, to) {
  13510. var dateHelper = this._getDateHelper();
  13511. var roundedFromDate = from;
  13512. //(from) ? this._getHelper().roundTimeFrom(from) : null;
  13513. var roundedToDate = to;
  13514. //(due) ? this._getHelper().roundTimeTo(due) : null;
  13515. if (roundedFromDate && roundedToDate) {
  13516. return $.when(this._checkFromDateBetweenMinMax(roundedFromDate), this._checkToDateBetweenMinMax(roundedToDate)).then(function (fromRes, toRes) {
  13517. var interval = dateHelper.roundMinutes;
  13518. // TODO: We should never get here
  13519. if (roundedToDate.diff(roundedFromDate, 'minutes') < interval) {
  13520. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order from date, after (or too close to) to date ' + roundedToDate.toJSONDate()));
  13521. }
  13522. if (roundedFromDate.diff(roundedToDate, 'minutes') > interval) {
  13523. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot set order due date, before (or too close to) from date ' + roundedFromDate.toJSONDate()));
  13524. }
  13525. });
  13526. } else if (roundedFromDate) {
  13527. return this._checkFromDateBetweenMinMax(roundedFromDate);
  13528. } else if (roundedToDate) {
  13529. return this._checkToDateBetweenMinMax(roundedToDate);
  13530. } else {
  13531. return $.Deferred().reject(new api.ApiUnprocessableEntity('Cannot from/due date, both are null'));
  13532. }
  13533. };
  13534. Reservation.prototype._getUnavailableItems = function () {
  13535. var unavailable = {};
  13536. if (this.status == 'open' && this.location && this.items != null && this.items.length > 0) {
  13537. var that = this;
  13538. var locId = that._getId(that.location);
  13539. $.each(this.items, function (i, item) {
  13540. if (item.status != 'available') {
  13541. unavailable['status'] = unavailable['status'] || [];
  13542. unavailable['status'].push(item._id);
  13543. }
  13544. });
  13545. }
  13546. return unavailable;
  13547. };
  13548. return Reservation;
  13549. }(jquery, api, transaction, conflict);
  13550. Template = function ($, common, api, Document) {
  13551. // Some constant values
  13552. var DEFAULTS = {
  13553. id: '',
  13554. status: 'inactive',
  13555. name: '',
  13556. body: '',
  13557. format: '',
  13558. kind: '',
  13559. width: 0,
  13560. height: 0,
  13561. unit: 'inch',
  13562. askSignature: false,
  13563. system: true,
  13564. archived: null,
  13565. createdBy: null,
  13566. createdOn: null,
  13567. modifiedBy: null,
  13568. modifiedOn: null
  13569. };
  13570. // Allow overriding the ctor during inheritance
  13571. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  13572. var tmp = function () {
  13573. };
  13574. tmp.prototype = Document.prototype;
  13575. /**
  13576. * Template serves as a starting point for a document or asset label
  13577. * @name Template
  13578. * @class
  13579. * @property {string} name the name
  13580. * @property {string} status the status
  13581. * @property {string} body the body (in html, xml, text, ... depending on the `format`)
  13582. * @property {string} format the format (pdf, label, dymo)
  13583. * @property {string} kind the kind of pdf (order, reservation, customer)
  13584. * @property {boolean} askSignature should we ask for a signature when generating a pdf
  13585. * @property {float} width the width of the template
  13586. * @property {float} height the height of the template
  13587. * @property {string} unit this unit that is used for dimensions (mm, inch)
  13588. * @property {boolean} system is it a system template which cannot be changed?
  13589. * @property {Moment} archived is the template archived
  13590. * @property {string} createdBy the user that created the template (null for system templates)
  13591. * @property {Moment} createdOn when the template was created
  13592. * @property {string} modifiedBy the user that modified the template (null for system templates)
  13593. * @property {Moment} modifiedOn when the template was modified
  13594. * @constructor
  13595. * @extends Document
  13596. */
  13597. var Template = function (opt) {
  13598. var spec = $.extend({}, opt);
  13599. Document.call(this, spec);
  13600. this.name = spec.name || DEFAULTS.name;
  13601. this.status = spec.status || DEFAULTS.status;
  13602. this.body = spec.body || DEFAULTS.body;
  13603. this.format = spec.format || DEFAULTS.format;
  13604. this.kind = spec.kind || DEFAULTS.kind;
  13605. this.askSignature = spec.askSignature != null ? spec.askSignature == true : DEFAULTS.askSignature;
  13606. this.width = spec.width || DEFAULTS.width;
  13607. this.height = spec.height || DEFAULTS.height;
  13608. this.unit = spec.unit || DEFAULTS.unit;
  13609. this.system = spec.system != null ? spec.system == true : DEFAULTS.system;
  13610. this.archived = spec.archived || DEFAULTS.archived;
  13611. this.createdBy = spec.createdBy || DEFAULTS.createdBy;
  13612. this.createdOn = spec.createdOn || DEFAULTS.createdOn;
  13613. this.modifiedBy = spec.modifiedBy || DEFAULTS.modifiedBy;
  13614. this.modifiedOn = spec.modifiedOn || DEFAULTS.modifiedOn;
  13615. };
  13616. Template.prototype = new tmp();
  13617. Template.prototype.constructor = Template;
  13618. //
  13619. // Specific validators
  13620. /**
  13621. * Checks if name is valid
  13622. * @name Template#isValidName
  13623. * @method
  13624. * @return {Boolean}
  13625. */
  13626. Template.prototype.isValidName = function () {
  13627. this.name = $.trim(this.name);
  13628. return this.name.length >= 3;
  13629. };
  13630. //
  13631. // Document overrides
  13632. //
  13633. /**
  13634. * Checks if the template has any validation errors
  13635. * @name Template#isValid
  13636. * @method
  13637. * @returns {boolean}
  13638. * @override
  13639. */
  13640. Template.prototype.isValid = function () {
  13641. // TODO: Check if the format, kind, etc is correct
  13642. return this.isValidName();
  13643. };
  13644. Template.prototype._getDefaults = function () {
  13645. return DEFAULTS;
  13646. };
  13647. /**
  13648. * Checks if the object is empty, it never is
  13649. * @name Template#isEmpty
  13650. * @method
  13651. * @returns {boolean}
  13652. * @override
  13653. */
  13654. Template.prototype.isEmpty = function () {
  13655. return Document.prototype.isEmpty.call(this) && this.name == DEFAULTS.name;
  13656. };
  13657. /**
  13658. * Checks if the object is archived
  13659. * @name Template#isArchived
  13660. * @method
  13661. * @returns {boolean}
  13662. */
  13663. Template.prototype.isArchived = function () {
  13664. return common.templateIsArchived(this);
  13665. };
  13666. /**
  13667. * Checks if the template is dirty and needs saving
  13668. * @returns {boolean}
  13669. * @override
  13670. */
  13671. Template.prototype.isDirty = function () {
  13672. var isDirty = Document.prototype.isDirty.call(this);
  13673. if (!isDirty && this.raw) {
  13674. isDirty = this.name != this.raw.name || this.body != this.raw.body || this.format != this.raw.format || this.kind != this.raw.kind || this.askSignature != this.raw.askSignature || this.width != this.raw.width || this.height != this.raw.height || this.unit != this.raw.unit;
  13675. }
  13676. return isDirty;
  13677. };
  13678. //
  13679. // Business logic
  13680. //
  13681. /**
  13682. * Clones the template to a new one
  13683. * @name Template#clone
  13684. * @returns {promise}
  13685. */
  13686. Template.prototype.clone = function () {
  13687. return this.ds.call(this.id, 'clone');
  13688. };
  13689. /**
  13690. * Archives this template
  13691. * @name Template#archive
  13692. * @param skipRead
  13693. * @returns {promise}
  13694. */
  13695. Template.prototype.archive = function (skipRead) {
  13696. return this._doApiCall({
  13697. method: 'archive',
  13698. skipRead: skipRead
  13699. });
  13700. };
  13701. /**
  13702. * Unarchives this template
  13703. * @name Template#undoArchive
  13704. * @param skipRead
  13705. * @returns {promise}
  13706. */
  13707. Template.prototype.undoArchive = function (skipRead) {
  13708. return this._doApiCall({
  13709. method: 'undoArchive',
  13710. skipRead: skipRead
  13711. });
  13712. };
  13713. /**
  13714. * Activates this template
  13715. * @name Template#activate
  13716. * @param skipRead
  13717. * @returns {promise}
  13718. */
  13719. Template.prototype.activate = function (skipRead) {
  13720. return this._doApiCall({
  13721. method: 'activate',
  13722. skipRead: skipRead
  13723. });
  13724. };
  13725. /**
  13726. * Deactivates this template
  13727. * @name Template#deactivate
  13728. * @param skipRead
  13729. * @returns {promise}
  13730. */
  13731. Template.prototype.deactivate = function (skipRead) {
  13732. return this._doApiCall({
  13733. method: 'deactivate',
  13734. skipRead: skipRead
  13735. });
  13736. };
  13737. /**
  13738. * Checks if we can delete the Template document
  13739. * @name Template#canDelete
  13740. * @method
  13741. * @returns {boolean}
  13742. * @override
  13743. */
  13744. Template.prototype.canDelete = function () {
  13745. return common.templateCanDelete(this);
  13746. };
  13747. /**
  13748. * Checks if we can activate a template
  13749. * @name Template#canActivate
  13750. * @returns {boolean}
  13751. */
  13752. Template.prototype.canActivate = function () {
  13753. return common.templateCanActivate(this);
  13754. };
  13755. /**
  13756. * Checks if we can deactivate a template
  13757. * @name Template#canDeactivate
  13758. * @returns {boolean}
  13759. */
  13760. Template.prototype.canDeactivate = function () {
  13761. return common.templateCanDeactivate(this);
  13762. };
  13763. /**
  13764. * Checks if we can archive a template
  13765. * @name Template#canArchive
  13766. * @returns {boolean}
  13767. */
  13768. Template.prototype.canArchive = function () {
  13769. return common.templateCanArchive(this);
  13770. };
  13771. /**
  13772. * Checks if we can undoArchive a template
  13773. * @name Template#canUndoArchive
  13774. * @returns {boolean}
  13775. */
  13776. Template.prototype.canUndoArchive = function () {
  13777. return common.templateCanUndoArchive(this);
  13778. };
  13779. // toJson, fromJson
  13780. // ----
  13781. /**
  13782. * _toJson, makes a dict of params to use during create / update
  13783. * @param options
  13784. * @returns {{}}
  13785. * @private
  13786. */
  13787. Template.prototype._toJson = function (options) {
  13788. var data = Document.prototype._toJson.call(this, options);
  13789. data.name = this.name;
  13790. data.body = this.body;
  13791. data.kind = this.kind;
  13792. data.askSignature = this.askSignature;
  13793. data.width = this.width;
  13794. data.height = this.height;
  13795. data.unit = this.unit;
  13796. // don't write out fields for:
  13797. // - format
  13798. // - status
  13799. // - system
  13800. // - created, modified
  13801. return data;
  13802. };
  13803. /**
  13804. * _fromJson: read some basic information
  13805. * @method
  13806. * @param {object} data the json response
  13807. * @param {object} options dict
  13808. * @returns {Promise}
  13809. * @private
  13810. */
  13811. Template.prototype._fromJson = function (data, options) {
  13812. var that = this;
  13813. return Document.prototype._fromJson.call(this, data, options).then(function () {
  13814. that.name = data.name || DEFAULTS.name;
  13815. that.status = data.status || DEFAULTS.status;
  13816. that.body = data.body || DEFAULTS.body;
  13817. that.format = data.format || DEFAULTS.format;
  13818. that.kind = data.kind || DEFAULTS.kind;
  13819. that.askSignature = data.askSignature != null ? data.askSignature == true : DEFAULTS.askSignature;
  13820. that.width = data.width || DEFAULTS.width;
  13821. that.height = data.height || DEFAULTS.height;
  13822. that.unit = data.unit || DEFAULTS.unit;
  13823. that.system = data.system != null ? data.system == true : DEFAULTS.system;
  13824. that.archived = data.archived || DEFAULTS.archived;
  13825. that.createdBy = data.createdBy || DEFAULTS.createdBy;
  13826. that.createdOn = data.createdOn || DEFAULTS.createdOn;
  13827. that.modifiedBy = data.modifiedBy || DEFAULTS.modifiedBy;
  13828. that.modifiedOn = data.modifiedOn || DEFAULTS.modifiedOn;
  13829. return data;
  13830. });
  13831. };
  13832. return Template;
  13833. }(jquery, common, api, document);
  13834. Transaction = function ($, api, Base, Location, DateHelper, Helper) {
  13835. var DEFAULTS = {
  13836. status: 'creating',
  13837. from: null,
  13838. to: null,
  13839. due: null,
  13840. contact: null,
  13841. location: null,
  13842. number: '',
  13843. items: [],
  13844. conflicts: [],
  13845. by: null,
  13846. archived: null,
  13847. itemSummary: null,
  13848. name: null
  13849. };
  13850. // Allow overriding the ctor during inheritance
  13851. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  13852. var tmp = function () {
  13853. };
  13854. tmp.prototype = Base.prototype;
  13855. /**
  13856. * @name Transaction
  13857. * @class Transaction
  13858. * @constructor
  13859. * @extends Base
  13860. * @property {boolean} autoCleanup - Automatically cleanup the transaction if it becomes empty?
  13861. * @property {DateHelper} dateHelper - A DateHelper object ref
  13862. * @property {string} status - The transaction status
  13863. * @property {moment} from - The transaction from date
  13864. * @property {moment} to - The transaction to date
  13865. * @property {moment} due - The transaction due date
  13866. * @property {string} number - The booking number
  13867. * @property {string} contact - The Contact.id for this transaction
  13868. * @property {string} location - The Location.id for this transaction
  13869. * @property {Array} items - A list of Item.id strings
  13870. * @property {Array} conflicts - A list of conflict hashes
  13871. */
  13872. var Transaction = function (opt) {
  13873. var spec = $.extend({}, opt);
  13874. Base.call(this, spec);
  13875. this.dsItems = spec.dsItems;
  13876. // we'll also access the /items collection
  13877. // should we automatically delete the transaction from the database?
  13878. this.autoCleanup = spec.autoCleanup != null ? spec.autoCleanup : false;
  13879. this.dateHelper = spec.dateHelper || new DateHelper();
  13880. this.helper = spec.helper || new Helper();
  13881. this.status = spec.status || DEFAULTS.status;
  13882. // the status of the order or reservation
  13883. this.from = spec.from || DEFAULTS.from;
  13884. // a date in the future
  13885. this.to = spec.to || DEFAULTS.to;
  13886. // a date in the future
  13887. this.due = spec.due || DEFAULTS.due;
  13888. // a date even further in the future, we suggest some standard avg durations
  13889. this.number = spec.number || DEFAULTS.number;
  13890. // a booking number
  13891. this.contact = spec.contact || DEFAULTS.contact;
  13892. // a contact id
  13893. this.location = spec.location || DEFAULTS.location;
  13894. // a location id
  13895. this.items = spec.items || DEFAULTS.items.slice();
  13896. // an array of item ids
  13897. this.conflicts = spec.conflicts || DEFAULTS.conflicts.slice();
  13898. // an array of Conflict objects
  13899. this.by = spec.by || DEFAULTS.by;
  13900. this.itemSummary = spec.itemSummary || DEFAULTS.itemSummary;
  13901. this.name = spec.name || DEFAULTS.name;
  13902. };
  13903. Transaction.prototype = new tmp();
  13904. Transaction.prototype.constructor = Base;
  13905. //
  13906. // Date helpers (possibly overwritten)
  13907. //
  13908. /**
  13909. * Gets the now time
  13910. * @returns {Moment}
  13911. */
  13912. Transaction.prototype.getNow = function () {
  13913. return this._getDateHelper().getNow();
  13914. };
  13915. /**
  13916. * Gets the now time rounded
  13917. * @returns {Moment}
  13918. */
  13919. Transaction.prototype.getNowRounded = function () {
  13920. return this._getDateHelper().roundTimeFrom(this.getNow());
  13921. };
  13922. /**
  13923. * Gets the next time slot after a date, by default after now
  13924. * @returns {Moment}
  13925. */
  13926. Transaction.prototype.getNextTimeSlot = function (d) {
  13927. d = d || this.getNowRounded();
  13928. var next = moment(d).add(this._getDateHelper().roundMinutes, 'minutes');
  13929. if (next.isSame(d)) {
  13930. next = next.add(this._getDateHelper().roundMinutes, 'minutes');
  13931. }
  13932. return next;
  13933. };
  13934. /**
  13935. * Gets the lowest possible from date, by default now
  13936. * @method
  13937. * @name Transaction#getMinDateFrom
  13938. * @returns {Moment}
  13939. */
  13940. Transaction.prototype.getMinDateFrom = function () {
  13941. return this.getMinDate();
  13942. };
  13943. /**
  13944. * Gets the highest possible from date, by default years from now
  13945. * @method
  13946. * @name Transaction#getMaxDateFrom
  13947. * @returns {Moment}
  13948. */
  13949. Transaction.prototype.getMaxDateFrom = function () {
  13950. return this.getMaxDate();
  13951. };
  13952. /**
  13953. * Gets the lowest possible to date, by default from +1 timeslot
  13954. * @method
  13955. * @name Transaction#getMinDateTo
  13956. * @returns {Moment}
  13957. */
  13958. Transaction.prototype.getMinDateTo = function () {
  13959. // to can only be one timeslot after the min from date
  13960. return this.getNextTimeSlot(this.getMinDateFrom());
  13961. };
  13962. /**
  13963. * Gets the highest possible to date, by default years from now
  13964. * @method
  13965. * @name Transaction#getMaxDateTo
  13966. * @returns {Moment}
  13967. */
  13968. Transaction.prototype.getMaxDateTo = function () {
  13969. return this.getMaxDate();
  13970. };
  13971. /**
  13972. * Gets the lowest possible due date, by default same as getMinDateTo
  13973. * @method
  13974. * @name Transaction#getMinDateDue
  13975. * @returns {Moment}
  13976. */
  13977. Transaction.prototype.getMinDateDue = function () {
  13978. return this.getMinDateTo();
  13979. };
  13980. /**
  13981. * Gets the highest possible due date, by default same as getMaxDateDue
  13982. * @method
  13983. * @name Transaction#getMaxDateDue
  13984. * @returns {Moment}
  13985. */
  13986. Transaction.prototype.getMaxDateDue = function () {
  13987. return this.getMaxDateTo();
  13988. };
  13989. /**
  13990. * DEPRECATED
  13991. * Gets the lowest possible date to start this transaction
  13992. * @method
  13993. * @name Transaction#getMinDate
  13994. * @returns {Moment} min date
  13995. */
  13996. Transaction.prototype.getMinDate = function () {
  13997. return this.getNow();
  13998. };
  13999. /**
  14000. * DEPRECATED
  14001. * Gets the latest possible date to end this transaction
  14002. * @method
  14003. * @name Transaction#getMaxDate
  14004. * @returns {Moment} max date
  14005. */
  14006. Transaction.prototype.getMaxDate = function () {
  14007. var dateHelper = this._getDateHelper();
  14008. var now = dateHelper.getNow();
  14009. var next = dateHelper.roundTimeTo(now);
  14010. return next.add(2, 'years');
  14011. };
  14012. /**
  14013. * suggestEndDate, makes a new moment() object with a suggested end date,
  14014. * already rounded up according to the group.profile settings
  14015. * @method suggestEndDate
  14016. * @name Transaction#suggestEndDate
  14017. * @param {Moment} m a suggested end date for this transaction
  14018. * @returns {*}
  14019. */
  14020. Transaction.prototype.suggestEndDate = function (m) {
  14021. var dateHelper = this._getDateHelper();
  14022. var end = dateHelper.addAverageDuration(m || dateHelper.getNow());
  14023. return dateHelper.roundTimeTo(end);
  14024. };
  14025. //
  14026. // Base overrides
  14027. //
  14028. /**
  14029. * Checks if the transaction is empty
  14030. * @method isEmpty
  14031. * @name Transaction#isEmpty
  14032. * @returns {boolean}
  14033. */
  14034. Transaction.prototype.isEmpty = function () {
  14035. return Base.prototype.isEmpty.call(this) && this.status == DEFAULTS.status && (this.crtype == 'cheqroom.types.order' ? true : this.from == DEFAULTS.from) && this.to == DEFAULTS.to && this.due == DEFAULTS.due && this.number == DEFAULTS.number && this.contact == DEFAULTS.contact && this.location == DEFAULTS.location && this.items.length == 0 // not DEFAULTS.items? :)
  14036. ;
  14037. };
  14038. /**
  14039. * Checks if the transaction is dirty and needs saving
  14040. * @method
  14041. * @name Transaction#isDirty
  14042. * @returns {boolean}
  14043. */
  14044. Transaction.prototype.isDirty = function () {
  14045. return Base.prototype.isDirty.call(this) || this._isDirtyBasic() || this._isDirtyDates() || this._isDirtyLocation() || this._isDirtyContact() || this._isDirtyItems();
  14046. };
  14047. Transaction.prototype._isDirtyBasic = function () {
  14048. if (this.raw) {
  14049. var status = this.raw.status || DEFAULTS.status;
  14050. return this.status != status;
  14051. } else {
  14052. return false;
  14053. }
  14054. };
  14055. Transaction.prototype._isDirtyDates = function () {
  14056. if (this.raw) {
  14057. var from = this.raw.from || DEFAULTS.from;
  14058. var to = this.raw.to || DEFAULTS.to;
  14059. var due = this.raw.due || DEFAULTS.due;
  14060. return this.from != from || this.to != to || this.due != due;
  14061. } else {
  14062. return false;
  14063. }
  14064. };
  14065. Transaction.prototype._isDirtyLocation = function () {
  14066. if (this.raw) {
  14067. var location = DEFAULTS.location;
  14068. if (this.raw.location) {
  14069. location = this.raw.location._id ? this.raw.location._id : this.raw.location;
  14070. }
  14071. return this.location != location;
  14072. } else {
  14073. return false;
  14074. }
  14075. };
  14076. Transaction.prototype._isDirtyContact = function () {
  14077. if (this.raw) {
  14078. var contact = DEFAULTS.contact;
  14079. if (this.raw.customer) {
  14080. contact = this.raw.customer._id ? this.raw.customer._id : this.raw.customer;
  14081. }
  14082. return this.contact != contact;
  14083. } else {
  14084. return false;
  14085. }
  14086. };
  14087. Transaction.prototype._isDirtyItems = function () {
  14088. if (this.raw) {
  14089. var items = DEFAULTS.items.slice();
  14090. if (this.raw.items) {
  14091. }
  14092. return false;
  14093. } else {
  14094. return false;
  14095. }
  14096. };
  14097. Transaction.prototype._getDefaults = function () {
  14098. return DEFAULTS;
  14099. };
  14100. /**
  14101. * Writes out some shared fields for all transactions
  14102. * Inheriting classes will probably add more to this
  14103. * @param options
  14104. * @returns {object}
  14105. * @private
  14106. */
  14107. Transaction.prototype._toJson = function (options) {
  14108. var data = Base.prototype._toJson.call(this, options);
  14109. //data.started = this.from; // VT: Will be set during checkout
  14110. //data.finished = this.to; // VT: Will be set during final checkin
  14111. data.due = this.due;
  14112. if (this.location) {
  14113. // Make sure we send the location as id, not the entire object
  14114. data.location = this._getId(this.location);
  14115. }
  14116. if (this.contact) {
  14117. // Make sure we send the contact as id, not the entire object
  14118. // VT: It's still called the "customer" field on the backend!
  14119. data.customer = this._getId(this.contact);
  14120. }
  14121. return data;
  14122. };
  14123. /**
  14124. * Reads the transaction from a json object
  14125. * @param data
  14126. * @param options
  14127. * @returns {promise}
  14128. * @private
  14129. */
  14130. Transaction.prototype._fromJson = function (data, options) {
  14131. var that = this;
  14132. return Base.prototype._fromJson.call(this, data, options).then(function () {
  14133. that.cover = null;
  14134. // don't read cover property for Transactions
  14135. that.status = data.status || DEFAULTS.status;
  14136. that.number = data.number || DEFAULTS.number;
  14137. that.location = data.location || DEFAULTS.location;
  14138. that.contact = data.customer || DEFAULTS.contact;
  14139. that.items = data.items || DEFAULTS.items.slice();
  14140. that.by = data.by || DEFAULTS.by;
  14141. that.archived = data.archived || DEFAULTS.archived;
  14142. that.itemSummary = data.itemSummary || DEFAULTS.itemSummary;
  14143. that.name = data.name || DEFAULTS.name;
  14144. return that._getConflicts().then(function (conflicts) {
  14145. that.conflicts = conflicts;
  14146. });
  14147. });
  14148. };
  14149. Transaction.prototype._toLog = function (options) {
  14150. var obj = this._toJson(options);
  14151. obj.minDateFrom = this.getMinDateFrom().toJSONDate();
  14152. obj.maxDateFrom = this.getMaxDateFrom().toJSONDate();
  14153. obj.minDateDue = this.getMinDateDue().toJSONDate();
  14154. obj.maxDateDue = this.getMaxDateDue().toJSONDate();
  14155. obj.minDateTo = this.getMinDateTo().toJSONDate();
  14156. obj.maxDateTo = this.getMaxDateTo().toJSONDate();
  14157. console.log(obj);
  14158. };
  14159. Transaction.prototype._checkFromDateBetweenMinMax = function (d) {
  14160. return this._checkDateBetweenMinMax(d, this.getMinDateFrom(), this.getMaxDateFrom());
  14161. };
  14162. Transaction.prototype._checkDueDateBetweenMinMax = function (d) {
  14163. return this._checkDateBetweenMinMax(d, this.getMinDateDue(), this.getMaxDateDue());
  14164. };
  14165. Transaction.prototype._checkToDateBetweenMinMax = function (d) {
  14166. return this._checkDateBetweenMinMax(d, this.getMinDateTo(), this.getMaxDateTo());
  14167. };
  14168. Transaction.prototype._getUniqueItemIds = function (ids) {
  14169. ids = ids || [];
  14170. //https://stackoverflow.com/questions/38373364/the-best-way-to-remove-duplicate-strings-in-an-array
  14171. return ids.reduce(function (p, c, i, a) {
  14172. if (p.indexOf(c) == -1)
  14173. p.push(c);
  14174. return p;
  14175. }, []);
  14176. };
  14177. // Setters
  14178. // ----
  14179. // From date setters
  14180. /**
  14181. * Clear the transaction from date
  14182. * @method
  14183. * @name Transaction#clearFromDate
  14184. * @param skipRead
  14185. * @returns {promise}
  14186. */
  14187. Transaction.prototype.clearFromDate = function (skipRead) {
  14188. this.from = DEFAULTS.from;
  14189. return this._handleTransaction(skipRead);
  14190. };
  14191. /**
  14192. * Sets the transaction from date
  14193. * @method
  14194. * @name Transaction#setFromDate
  14195. * @param date
  14196. * @param skipRead
  14197. * @returns {promise}
  14198. */
  14199. Transaction.prototype.setFromDate = function (date, skipRead) {
  14200. this.from = this._getDateHelper().roundTimeFrom(date);
  14201. return this._handleTransaction(skipRead);
  14202. };
  14203. // To date setters
  14204. /**
  14205. * Clear the transaction to date
  14206. * @method
  14207. * @name Transaction#clearToDate
  14208. * @param skipRead
  14209. * @returns {promise}
  14210. */
  14211. Transaction.prototype.clearToDate = function (skipRead) {
  14212. this.to = DEFAULTS.to;
  14213. return this._handleTransaction(skipRead);
  14214. };
  14215. /**
  14216. * Sets the transaction to date
  14217. * @method
  14218. * @name Transaction#setToDate
  14219. * @param date
  14220. * @param skipRead
  14221. * @returns {promise}
  14222. */
  14223. Transaction.prototype.setToDate = function (date, skipRead) {
  14224. this.to = this._getDateHelper().roundTimeTo(date);
  14225. return this._handleTransaction(skipRead);
  14226. };
  14227. // Due date setters
  14228. /**
  14229. * Clear the transaction due date
  14230. * @method
  14231. * @name Transaction#clearDueDate
  14232. * @param skipRead
  14233. * @returns {promise}
  14234. */
  14235. Transaction.prototype.clearDueDate = function (skipRead) {
  14236. this.due = DEFAULTS.due;
  14237. return this._handleTransaction(skipRead);
  14238. };
  14239. /**
  14240. * Set the transaction due date
  14241. * @method
  14242. * @name Transaction#setDueDate
  14243. * @param date
  14244. * @param skipRead
  14245. * @returns {promise}
  14246. */
  14247. Transaction.prototype.setDueDate = function (date, skipRead) {
  14248. this.due = this._getDateHelper().roundTimeTo(date);
  14249. return this._handleTransaction(skipRead);
  14250. };
  14251. // Location setters
  14252. /**
  14253. * Sets the location for this transaction
  14254. * @method
  14255. * @name Transaction#setLocation
  14256. * @param locationId
  14257. * @param skipRead skip parsing the returned json response into the transaction
  14258. * @returns {promise}
  14259. */
  14260. Transaction.prototype.setLocation = function (locationId, skipRead) {
  14261. this.location = locationId;
  14262. if (this.existsInDb()) {
  14263. return this._doApiCall({
  14264. method: 'setLocation',
  14265. params: { location: locationId },
  14266. skipRead: skipRead
  14267. });
  14268. } else {
  14269. return this._createTransaction(skipRead);
  14270. }
  14271. };
  14272. /**
  14273. * Clears the location for this transaction
  14274. * @method
  14275. * @name Transaction#clearLocation
  14276. * @param skipRead skip parsing the returned json response into the transaction
  14277. * @returns {promise}
  14278. */
  14279. Transaction.prototype.clearLocation = function (skipRead) {
  14280. var that = this;
  14281. this.location = DEFAULTS.location;
  14282. return this._doApiCall({
  14283. method: 'clearLocation',
  14284. skipRead: skipRead
  14285. }).then(function () {
  14286. return that._ensureTransactionDeleted();
  14287. });
  14288. };
  14289. // Contact setters
  14290. /**
  14291. * Sets the contact for this transaction
  14292. * @method
  14293. * @name Transaction#setContact
  14294. * @param contactId
  14295. * @param skipRead skip parsing the returned json response into the transaction
  14296. * @returns {promise}
  14297. */
  14298. Transaction.prototype.setContact = function (contactId, skipRead) {
  14299. this.contact = contactId;
  14300. if (this.existsInDb()) {
  14301. return this._doApiCall({
  14302. method: 'setCustomer',
  14303. params: { customer: contactId },
  14304. skipRead: skipRead
  14305. });
  14306. } else {
  14307. return this._createTransaction(skipRead);
  14308. }
  14309. };
  14310. /**
  14311. * Clears the contact for this transaction
  14312. * @method
  14313. * @name Transaction#clearContact
  14314. * @param skipRead skip parsing the returned json response into the transaction
  14315. * @returns {promise}
  14316. */
  14317. Transaction.prototype.clearContact = function (skipRead) {
  14318. var that = this;
  14319. this.contact = DEFAULTS.contact;
  14320. return this._doApiCall({
  14321. method: 'clearCustomer',
  14322. skipRead: skipRead
  14323. }).then(function () {
  14324. return that._ensureTransactionDeleted();
  14325. });
  14326. };
  14327. /**
  14328. * Sets transaction name
  14329. * @method
  14330. * @name Transaction#setName
  14331. * @param name
  14332. * @param skipRead skip parsing the returned json response into the transaction
  14333. * @returns {promise}
  14334. */
  14335. Transaction.prototype.setName = function (name, skipRead) {
  14336. return this._doApiCall({
  14337. method: 'setName',
  14338. params: { name: name },
  14339. skipRead: skipRead
  14340. });
  14341. };
  14342. /**
  14343. * Clears transaction name
  14344. * @method
  14345. * @name Transaction#clearName
  14346. * @param skipRead skip parsing the returned json response into the transaction
  14347. * @returns {promise}
  14348. */
  14349. Transaction.prototype.clearName = function (skipRead) {
  14350. return this._doApiCall({
  14351. method: 'clearName',
  14352. skipRead: skipRead
  14353. });
  14354. };
  14355. // Business logic
  14356. // ----
  14357. // Inheriting classes will use the setter functions below to update the object in memory
  14358. // the _handleTransaction will create, update or delete the actual document via the API
  14359. /**
  14360. * addItems; adds a bunch of Items to the transaction using a list of item ids
  14361. * It creates the transaction if it doesn't exist yet
  14362. * @name Transaction#addItems
  14363. * @method
  14364. * @param items
  14365. * @param skipRead
  14366. * @returns {promise}
  14367. */
  14368. Transaction.prototype.addItems = function (items, skipRead) {
  14369. var that = this;
  14370. //Remove duplicate item ids
  14371. items = that._getUniqueItemIds(items);
  14372. return this._ensureTransactionExists(skipRead).then(function () {
  14373. return that._doApiCall({
  14374. method: 'addItems',
  14375. params: { items: items },
  14376. skipRead: skipRead
  14377. });
  14378. });
  14379. };
  14380. /**
  14381. * removeItems; removes a bunch of Items from the transaction using a list of item ids
  14382. * It deletes the transaction if it's empty afterwards and autoCleanup is true
  14383. * @name Transaction#removeItems
  14384. * @method
  14385. * @param items
  14386. * @param skipRead
  14387. * @returns {promise}
  14388. */
  14389. Transaction.prototype.removeItems = function (items, skipRead) {
  14390. var that = this;
  14391. if (!this.existsInDb()) {
  14392. return $.Deferred().reject(new Error('Cannot removeItems from document without id'));
  14393. }
  14394. //Remove duplicate item ids
  14395. items = that._getUniqueItemIds(items);
  14396. return this._doApiCall({
  14397. method: 'removeItems',
  14398. params: { items: items },
  14399. skipRead: skipRead
  14400. }).then(function (data) {
  14401. return that._ensureTransactionDeleted().then(function () {
  14402. return data;
  14403. });
  14404. });
  14405. };
  14406. /**
  14407. * clearItems; removes all Items from the transaction
  14408. * It deletes the transaction if it's empty afterwards and autoCleanup is true
  14409. * @name Transaction#clearItems
  14410. * @method
  14411. * @param skipRead
  14412. * @returns {promise}
  14413. */
  14414. Transaction.prototype.clearItems = function (skipRead) {
  14415. if (!this.existsInDb()) {
  14416. return $.Deferred().reject(new Error('Cannot clearItems from document without id'));
  14417. }
  14418. var that = this;
  14419. return this._doApiCall({
  14420. method: 'clearItems',
  14421. skipRead: skipRead
  14422. }).then(function (data) {
  14423. return that._ensureTransactionDeleted().then(function () {
  14424. return data;
  14425. });
  14426. });
  14427. };
  14428. /**
  14429. * swapItem; swaps one item for another in a transaction
  14430. * @name Transaction#swapItem
  14431. * @method
  14432. * @param fromItem
  14433. * @param toItem
  14434. * @param skipRead
  14435. * @returns {promise}
  14436. */
  14437. Transaction.prototype.swapItem = function (fromItem, toItem, skipRead) {
  14438. if (!this.existsInDb()) {
  14439. return $.Deferred().reject(new Error('Cannot swapItem from document without id'));
  14440. }
  14441. // swapItem cannot create or delete a transaction
  14442. return this._doApiCall({
  14443. method: 'swapItem',
  14444. params: {
  14445. fromItem: fromItem,
  14446. toItem: toItem
  14447. },
  14448. skipRead: skipRead
  14449. });
  14450. };
  14451. /**
  14452. * hasItems; Gets a list of items that are already part of the transaction
  14453. * @name Transaction#hasItems
  14454. * @method
  14455. * @param itemIds array of string values
  14456. * @returns {Array}
  14457. */
  14458. Transaction.prototype.hasItems = function (itemIds) {
  14459. var allItems = this.items || [];
  14460. var duplicates = [];
  14461. var found = null;
  14462. $.each(itemIds, function (i, itemId) {
  14463. $.each(allItems, function (i, it) {
  14464. if (it._id == itemId) {
  14465. found = itemId;
  14466. return false;
  14467. }
  14468. });
  14469. if (found != null) {
  14470. duplicates.push(found);
  14471. }
  14472. });
  14473. return duplicates;
  14474. };
  14475. /**
  14476. * Archive a transaction
  14477. * @name Transaction#archive
  14478. * @param skipRead
  14479. * @returns {promise}
  14480. */
  14481. Transaction.prototype.archive = function (skipRead) {
  14482. if (!this.canArchive()) {
  14483. return $.Deferred().reject(new Error('Cannot archive document'));
  14484. }
  14485. return this._doApiCall({
  14486. method: 'archive',
  14487. params: {},
  14488. skipRead: skipRead
  14489. });
  14490. };
  14491. /**
  14492. * Undo archive of a transaction
  14493. * @name Transaction#undoArchive
  14494. * @param skipRead
  14495. * @returns {promise}
  14496. */
  14497. Transaction.prototype.undoArchive = function (skipRead) {
  14498. if (!this.canUndoArchive()) {
  14499. return $.Deferred().reject(new Error('Cannot unarchive document'));
  14500. }
  14501. return this._doApiCall({
  14502. method: 'undoArchive',
  14503. params: {},
  14504. skipRead: skipRead
  14505. });
  14506. };
  14507. /**
  14508. * Checks if we can archive a transaction (based on status)
  14509. * @name Transaction#canArchive
  14510. * @returns {boolean}
  14511. */
  14512. Transaction.prototype.canArchive = function () {
  14513. return this.archived == null && (this.status == 'cancelled' || this.status == 'closed');
  14514. };
  14515. /**
  14516. * Checks if we can unarchive a transaction (based on status)
  14517. * @name Transaction#canUndoArchive
  14518. * @returns {boolean}
  14519. */
  14520. Transaction.prototype.canUndoArchive = function () {
  14521. return this.archived != null && (this.status == 'cancelled' || this.status == 'closed');
  14522. };
  14523. Transaction.prototype.setField = function (field, value, skipRead) {
  14524. var that = this;
  14525. return this._ensureTransactionExists(skipRead).then(function () {
  14526. return that._doApiCall({
  14527. method: 'setField',
  14528. params: {
  14529. field: field,
  14530. value: value
  14531. },
  14532. skipRead: skipRead
  14533. });
  14534. });
  14535. };
  14536. //
  14537. // Implementation stuff
  14538. //
  14539. /**
  14540. * Gets a list of Conflict objects for this transaction
  14541. * Will be overriden by inheriting classes
  14542. * @returns {promise}
  14543. * @private
  14544. */
  14545. Transaction.prototype._getConflicts = function () {
  14546. return $.Deferred().resolve([]);
  14547. };
  14548. Transaction.prototype._getDateHelper = function () {
  14549. return this.dateHelper;
  14550. };
  14551. /**
  14552. * Searches for Items that are available for this transaction
  14553. * @param params: a dict with params, just like items/search
  14554. * @param listName: restrict search to a certain list
  14555. * @param useAvailabilities (uses items/searchAvailable instead of items/search)
  14556. * @param onlyUnbooked (true by default, only used when useAvailabilities=true)
  14557. * @param skipItems array of item ids that should be skipped
  14558. * @private
  14559. * @returns {*}
  14560. */
  14561. Transaction.prototype._searchItems = function (params, listName, useAvailabilities, onlyUnbooked, skipItems) {
  14562. if (this.dsItems == null) {
  14563. return $.Deferred().reject(new api.ApiBadRequest(this.crtype + ' has no DataSource for items'));
  14564. }
  14565. // Restrict the search to just the Items that are:
  14566. // - at this location
  14567. // - in the specified list (if any)
  14568. params = params || {};
  14569. params.location = this._getId(this.location);
  14570. if (listName != null && listName.length > 0) {
  14571. params.listName = listName;
  14572. }
  14573. // Make sure we only pass the item ids,
  14574. // and not the entire items
  14575. var that = this;
  14576. var skipList = null;
  14577. if (skipItems && skipItems.length) {
  14578. skipList = skipItems.slice(0);
  14579. $.each(skipList, function (i, item) {
  14580. skipList[i] = that._getId(item);
  14581. });
  14582. }
  14583. if (useAvailabilities == true) {
  14584. // We'll use a more advanced API call /items/searchAvailable
  14585. // It's a bit slower and the .count result is not usable
  14586. // It requires some more parameters to be set
  14587. params.onlyUnbooked = onlyUnbooked != null ? onlyUnbooked : true;
  14588. params.fromDate = this.from;
  14589. params.toDate = this.to || this.due;
  14590. //need due date for orders!!!!!
  14591. params._limit = params._limit || 20;
  14592. params._skip = params._skip || 0;
  14593. if (skipList && skipList.length) {
  14594. params.skipItems = skipList;
  14595. }
  14596. return this.dsItems.call(null, 'searchAvailable', params);
  14597. } else {
  14598. // We don't need to use availabilities,
  14599. // we should better use the regular /search
  14600. // it's faster and has better paging :)
  14601. if (skipList && skipList.length) {
  14602. params.pk__nin = skipList;
  14603. }
  14604. return this.dsItems.search(params);
  14605. }
  14606. };
  14607. /**
  14608. * Returns a rejected promise when a date is not between min and max date
  14609. * Otherwise the deferred just resolves to the date
  14610. * It's used to do some quick checks of transaction dates
  14611. * @param date
  14612. * @returns {*}
  14613. * @private
  14614. */
  14615. Transaction.prototype._checkDateBetweenMinMax = function (date, minDate, maxDate) {
  14616. minDate = minDate || this.getMinDate();
  14617. maxDate = maxDate || this.getMaxDate();
  14618. if (date < minDate || date > maxDate) {
  14619. var msg = 'date ' + date.toJSONDate() + ' is outside of min max range ' + minDate.toJSONDate() + '->' + maxDate.toJSONDate();
  14620. return $.Deferred().reject(new api.ApiUnprocessableEntity(msg));
  14621. } else {
  14622. return $.Deferred().resolve(date);
  14623. }
  14624. };
  14625. /**
  14626. * _handleTransaction: creates, updates or deletes a transaction document
  14627. * @returns {*}
  14628. * @private
  14629. */
  14630. Transaction.prototype._handleTransaction = function (skipRead) {
  14631. var isEmpty = this.isEmpty();
  14632. if (this.existsInDb()) {
  14633. if (isEmpty) {
  14634. if (this.autoCleanup) {
  14635. return this._deleteTransaction();
  14636. } else {
  14637. return $.Deferred().resolve();
  14638. }
  14639. } else {
  14640. return this._updateTransaction(skipRead);
  14641. }
  14642. } else if (!isEmpty) {
  14643. return this._createTransaction(skipRead);
  14644. } else {
  14645. return $.Deferred().resolve();
  14646. }
  14647. };
  14648. Transaction.prototype._deleteTransaction = function () {
  14649. return this.delete();
  14650. };
  14651. Transaction.prototype._updateTransaction = function (skipRead) {
  14652. return this.update(skipRead);
  14653. };
  14654. Transaction.prototype._createTransaction = function (skipRead) {
  14655. return this.create(skipRead);
  14656. };
  14657. Transaction.prototype._ensureTransactionExists = function (skipRead) {
  14658. return !this.existsInDb() ? this._createTransaction(skipRead) : $.Deferred().resolve();
  14659. };
  14660. Transaction.prototype._ensureTransactionDeleted = function () {
  14661. return this.isEmpty() && this.autoCleanup ? this._deleteTransaction() : $.Deferred().resolve();
  14662. };
  14663. return Transaction;
  14664. }(jquery, api, base, location, dateHelper, helper);
  14665. User = function ($, Base, common) {
  14666. var DEFAULTS = {
  14667. name: '',
  14668. email: '',
  14669. group: '',
  14670. // groupid
  14671. picture: '',
  14672. role: 'user',
  14673. // user, admin
  14674. active: true,
  14675. isOwner: false,
  14676. archived: null,
  14677. restrictLocations: []
  14678. };
  14679. // Allow overriding the ctor during inheritance
  14680. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  14681. var tmp = function () {
  14682. };
  14683. tmp.prototype = Base.prototype;
  14684. /**
  14685. * @name User
  14686. * @class User
  14687. * @constructor
  14688. * @extends Base
  14689. * @property {string} name - The name
  14690. * @property {string} role - The role (admin, user)
  14691. * @property {boolean} active - Is the user active?
  14692. */
  14693. var User = function (opt) {
  14694. var spec = $.extend({
  14695. _fields: [
  14696. '*',
  14697. 'group',
  14698. 'picture'
  14699. ]
  14700. }, opt);
  14701. Base.call(this, spec);
  14702. this.helper = spec.helper;
  14703. this.name = spec.name || DEFAULTS.name;
  14704. this.picture = spec.picture || DEFAULTS.picture;
  14705. this.email = spec.email || DEFAULTS.email;
  14706. this.role = spec.role || DEFAULTS.role;
  14707. this.group = spec.group || DEFAULTS.group;
  14708. this.active = spec.active != null ? spec.active : DEFAULTS.active;
  14709. this.isOwner = spec.isOwner != null ? spec.isOwner : DEFAULTS.isOwner;
  14710. this.archived = spec.archived || DEFAULTS.archived;
  14711. this.restrictLocations = spec.restrictLocations ? spec.restrictLocations.slice() : DEFAULTS.restrictLocations.slice();
  14712. this.dsAnonymous = spec.dsAnonymous;
  14713. };
  14714. User.prototype = new tmp();
  14715. User.prototype.constructor = User;
  14716. //
  14717. // Document overrides
  14718. //
  14719. User.prototype.isValidName = function () {
  14720. this.name = $.trim(this.name);
  14721. return this.name.length >= 4;
  14722. };
  14723. User.prototype.isValidEmail = function () {
  14724. this.email = $.trim(this.email);
  14725. return common.isValidEmail(this.email);
  14726. };
  14727. User.prototype.isValidRole = function () {
  14728. switch (this.role) {
  14729. case 'user':
  14730. case 'admin':
  14731. case 'root':
  14732. case 'selfservice':
  14733. return true;
  14734. default:
  14735. return false;
  14736. }
  14737. };
  14738. User.prototype.emailExists = function () {
  14739. if (this.isValidEmail()) {
  14740. // Don't check for emailExists for exisiting user
  14741. if (this.id != null && this.email == this.raw.email) {
  14742. return $.Deferred().resolve(false);
  14743. }
  14744. return this.dsAnonymous.call('emailExists', { email: this.email }).then(function (resp) {
  14745. return resp.result;
  14746. });
  14747. } else {
  14748. return $.Deferred().resolve(false);
  14749. }
  14750. };
  14751. User.prototype.isValidPassword = function () {
  14752. this.password = $.trim(this.password);
  14753. return common.isValidPassword(this.password);
  14754. };
  14755. /**
  14756. * Checks if the user is valid
  14757. * @returns {boolean}
  14758. */
  14759. User.prototype.isValid = function () {
  14760. return this.isValidName() && this.isValidEmail() && this.isValidRole();
  14761. };
  14762. /**
  14763. * Checks if the user is empty
  14764. * @method
  14765. * @name User#isEmpty
  14766. * @returns {boolean}
  14767. */
  14768. User.prototype.isEmpty = function () {
  14769. // We check: name, role
  14770. return Base.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.email == DEFAULTS.email && this.role == DEFAULTS.role && (this.restrictLocations && this.restrictLocations.length == 0);
  14771. };
  14772. User.prototype._isDirtyInfo = function () {
  14773. if (this.raw) {
  14774. var name = this.raw.name || DEFAULTS.name;
  14775. var role = this.raw.role || DEFAULTS.role;
  14776. var email = this.raw.email || DEFAULTS.email;
  14777. var active = this.raw.active != null ? this.raw.active : DEFAULTS.active;
  14778. return this.name != name || this.email != email || this.role != role || this.active != active;
  14779. }
  14780. return false;
  14781. };
  14782. User.prototype._isDirtyRestrictLocations = function () {
  14783. if (this.raw) {
  14784. var that = this, restrictLocations = this.raw.restrictLocations || DEFAULTS.restrictLocations;
  14785. // Check if other locations have been selected
  14786. return this.restrictLocations.filter(function (x) {
  14787. return restrictLocations.indexOf(x) < 0;
  14788. }).length > 0 || restrictLocations.filter(function (x) {
  14789. return that.restrictLocations.indexOf(x) < 0;
  14790. }).length > 0;
  14791. }
  14792. return false;
  14793. };
  14794. /**
  14795. * Checks if the user is dirty and needs saving
  14796. * @method
  14797. * @name User#isDirty
  14798. * @returns {boolean}
  14799. */
  14800. User.prototype.isDirty = function () {
  14801. var isDirty = Base.prototype.isDirty.call(this);
  14802. return isDirty || this._isDirtyInfo() || this._isDirtyRestrictLocations();
  14803. };
  14804. /**
  14805. * Gets a url for a user avatar
  14806. * 'XS': (64, 64),
  14807. * 'S': (128, 128),
  14808. * 'M': (256, 256),
  14809. * 'L': (512, 512)
  14810. * @param size {string} default null is original size
  14811. * @param bustCache {boolean}
  14812. * @returns {string}
  14813. */
  14814. User.prototype.getImageUrl = function (size, bustCache) {
  14815. return this.picture != null && this.picture.length > 0 ? this.helper.getImageCDNUrl(this.group, this.picture, size, bustCache) : this.helper.getImageUrl(this.ds, this.id, size, bustCache);
  14816. };
  14817. User.prototype._getDefaults = function () {
  14818. return DEFAULTS;
  14819. };
  14820. // OVERRIDE BASE: addKeyValue not implemented
  14821. User.prototype.addKeyValue = function (key, value, kind, skipRead) {
  14822. return $.Deferred().reject('Not implemented for User, use setPicture instead?');
  14823. };
  14824. // OVERRIDE BASE: addKeyValue not implemented
  14825. User.prototype.addKeyValue = function (id, key, value, kind, skipRead) {
  14826. return $.Deferred().reject('Not implemented for User, use setPicture instead?');
  14827. };
  14828. // OVERRIDE BASE: removeKeyValue not implemented
  14829. User.prototype.removeKeyValue = function (id, skipRead) {
  14830. return $.Deferred().reject('Not implemented for User, use clearPicture instead?');
  14831. };
  14832. User.prototype.setPicture = function (attachmentId, skipRead) {
  14833. if (!this.existsInDb()) {
  14834. return $.Deferred().reject('User does not exist in database');
  14835. }
  14836. this.picture = attachmentId;
  14837. return this._doApiCall({
  14838. method: 'setPicture',
  14839. params: { attachment: attachmentId },
  14840. skipRead: skipRead
  14841. });
  14842. };
  14843. User.prototype.clearPicture = function (skipRead) {
  14844. if (!this.existsInDb()) {
  14845. return $.Deferred().reject('User does not exist in database');
  14846. }
  14847. return this._doApiCall({
  14848. method: 'clearPicture',
  14849. skipRead: skipRead
  14850. });
  14851. };
  14852. //
  14853. // Business logic
  14854. //
  14855. /**
  14856. * Checks if a user can be activated
  14857. * @returns {boolean}
  14858. */
  14859. User.prototype.canActivate = function () {
  14860. return !this.active && this.archived == null;
  14861. };
  14862. /**
  14863. * Checks if a user can be deactivated
  14864. * @returns {boolean}
  14865. */
  14866. User.prototype.canDeactivate = function () {
  14867. // TODO: We should also check if we're not deactivating the last or only user
  14868. return this.active && this.archived == null && !this.isOwner;
  14869. };
  14870. /**
  14871. * Checks if a user can be archived
  14872. * @returns {boolean}
  14873. */
  14874. User.prototype.canArchive = function () {
  14875. // TODO: We should also check if we're not deactivating the last or only user
  14876. return this.archived == null && !this.isOwner;
  14877. };
  14878. /**
  14879. * Checks if a user can be unarchived
  14880. * @returns {boolean}
  14881. */
  14882. User.prototype.canUndoArchive = function () {
  14883. return this.archived != null;
  14884. };
  14885. /**
  14886. * Checks if a user can be owner
  14887. * @returns {boolean}
  14888. */
  14889. User.prototype.canBeOwner = function () {
  14890. return this.archived == null && this.active && !this.isOwner && this.role == 'admin';
  14891. };
  14892. /**
  14893. * Activates a user
  14894. * @param skipRead
  14895. * @returns {promise}
  14896. */
  14897. User.prototype.activate = function (skipRead) {
  14898. if (!this.existsInDb()) {
  14899. return $.Deferred().reject('User does not exist in database');
  14900. }
  14901. return this._doApiCall({
  14902. method: 'activate',
  14903. skipRead: skipRead
  14904. });
  14905. };
  14906. /**
  14907. * Deactivates a user
  14908. * @param skipRead
  14909. * @returns {promise}
  14910. */
  14911. User.prototype.deactivate = function (skipRead) {
  14912. if (!this.existsInDb()) {
  14913. return $.Deferred().reject('User does not exist in database');
  14914. }
  14915. return this._doApiCall({
  14916. method: 'deactivate',
  14917. skipRead: skipRead
  14918. });
  14919. };
  14920. /**
  14921. * Archives a user
  14922. * @param skipRead
  14923. * @returns {promise}
  14924. */
  14925. User.prototype.archive = function (skipRead) {
  14926. if (!this.existsInDb()) {
  14927. return $.Deferred().reject('User does not exist in database');
  14928. }
  14929. return this._doApiCall({
  14930. method: 'archive',
  14931. skipRead: skipRead
  14932. });
  14933. };
  14934. /**
  14935. * Unarchives a user
  14936. * @param skipRead
  14937. * @returns {promise}
  14938. */
  14939. User.prototype.undoArchive = function (skipRead) {
  14940. if (!this.existsInDb()) {
  14941. return $.Deferred().reject('User does not exist in database');
  14942. }
  14943. return this._doApiCall({
  14944. method: 'undoArchive',
  14945. skipRead: skipRead
  14946. });
  14947. };
  14948. /**
  14949. * Restrict user access to specific location(s)
  14950. * @param locations
  14951. * @param skipRead
  14952. * @returns {promise}
  14953. */
  14954. User.prototype.setRestrictLocations = function (locations, skipRead) {
  14955. if (!this.existsInDb()) {
  14956. return $.Deferred().reject('User does not exist in database');
  14957. }
  14958. return this._doApiCall({
  14959. method: 'setRestrictLocations',
  14960. params: { restrictLocations: locations },
  14961. skipRead: skipRead
  14962. });
  14963. };
  14964. /**
  14965. * Clear user location(s) access (makes all location accessible for the user)
  14966. * @param skipRead
  14967. * @returns {promise}
  14968. */
  14969. User.prototype.clearRestrictLocations = function (skipRead) {
  14970. if (!this.existsInDb()) {
  14971. return $.Deferred().reject('User does not exist in database');
  14972. }
  14973. return this._doApiCall({
  14974. method: 'clearRestrictLocations',
  14975. skipRead: skipRead
  14976. });
  14977. };
  14978. /**
  14979. * Updates the user
  14980. * @param skipRead
  14981. * @returns {*}
  14982. */
  14983. User.prototype.update = function (skipRead) {
  14984. if (this.isEmpty()) {
  14985. return $.Deferred().reject(new Error('Cannot update to empty user'));
  14986. }
  14987. if (!this.existsInDb()) {
  14988. return $.Deferred().reject(new Error('Cannot update user without id'));
  14989. }
  14990. if (!this.isValid()) {
  14991. return $.Deferred().reject(new Error('Cannot update, invalid user'));
  14992. }
  14993. var that = this, dfdRestrictLocations = $.Deferred(), dfdInfo = $.Deferred();
  14994. if (this._isDirtyInfo()) {
  14995. dfdInfo = this.ds.update(this.id, this._toJson(), this._fields);
  14996. } else {
  14997. dfdInfo.resolve();
  14998. }
  14999. if (this._isDirtyRestrictLocations()) {
  15000. if (this.restrictLocations.length != 0) {
  15001. dfdRestrictLocations = this.setRestrictLocations(this.restrictLocations, true);
  15002. } else {
  15003. dfdRestrictLocations = this.clearRestrictLocations(true);
  15004. }
  15005. } else {
  15006. dfdRestrictLocations.resolve();
  15007. }
  15008. return $.when(dfdInfo, dfdRestrictLocations);
  15009. };
  15010. /**
  15011. * Writes the user to a json object
  15012. * @param options
  15013. * @returns {object}
  15014. * @private
  15015. */
  15016. User.prototype._toJson = function (options) {
  15017. var data = Base.prototype._toJson.call(this, options);
  15018. data.name = this.name || DEFAULTS.name;
  15019. data.email = this.email || DEFAULTS.email;
  15020. data.group = this.group || DEFAULTS.group;
  15021. data.role = this.role || DEFAULTS.role;
  15022. return data;
  15023. };
  15024. /**
  15025. * Reads the user from the json object
  15026. * @param data
  15027. * @param options
  15028. * @returns {promise}
  15029. * @private
  15030. */
  15031. User.prototype._fromJson = function (data, options) {
  15032. var that = this;
  15033. return Base.prototype._fromJson.call(this, data, options).then(function () {
  15034. // Read the group id from group or group._id
  15035. // depending on the fields
  15036. that.group = data.group && data.group._id != null ? data.group._id : data.group || DEFAULTS.group;
  15037. that.name = data.name || DEFAULTS.name;
  15038. that.picture = data.picture || DEFAULTS.picture;
  15039. that.email = data.email || DEFAULTS.email;
  15040. that.role = data.role || DEFAULTS.role;
  15041. that.active = data.active != null ? data.active : DEFAULTS.active;
  15042. that.isOwner = data.isOwner != null ? data.isOwner : DEFAULTS.isOwner;
  15043. that.archived = data.archived || DEFAULTS.archived;
  15044. that.restrictLocations = data.restrictLocations ? data.restrictLocations.slice() : DEFAULTS.restrictLocations.slice();
  15045. $.publish('user.fromJson', data);
  15046. return data;
  15047. });
  15048. };
  15049. return User;
  15050. }(jquery, base, common);
  15051. UserSync = function ($, Base, common) {
  15052. var DEFAULTS = {
  15053. kind: 'ldap',
  15054. name: '',
  15055. host: 'ldap://yourdomain.com',
  15056. port: 389,
  15057. timeOut: 10,
  15058. login: '',
  15059. password: '',
  15060. newUsers: 'create',
  15061. existingUsers: 'update',
  15062. missingUsers: 'ignore',
  15063. autoSync: false,
  15064. role: 'selfservice',
  15065. query: '(cn=*)',
  15066. base: 'ou=team,dc=yourdomain,dc=com',
  15067. loginField: 'uid',
  15068. nameField: 'cn',
  15069. emailField: 'mail',
  15070. restrictLocations: [],
  15071. timezone: 'Etc/GMT',
  15072. hostCert: 'ldap_tls_demand'
  15073. };
  15074. // Allow overriding the ctor during inheritance
  15075. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  15076. var tmp = function () {
  15077. };
  15078. tmp.prototype = Base.prototype;
  15079. /**
  15080. * @name UserSync
  15081. * @class UserSync
  15082. * @constructor
  15083. * @extends Base
  15084. * @property {string} kind - The kind
  15085. * @property {string} name - The name
  15086. * @property {string} host - The url of the host
  15087. * @property {int} port - The port number
  15088. * @property {int} timeOut - The timeOut in seconds
  15089. * @property {string} login - The login for the host
  15090. * @property {string} password - The password for the host
  15091. * @property {string} newUsers - What to with new Users (ignore, create)
  15092. * @property {string} existingUsers - What to with existing Users (ignore, update)
  15093. * @property {string} missingUsers - What to with missing Users (ignore, archive, deactivate)
  15094. * @property {boolean} autoSync - Do a nightly sync automatically?
  15095. * @property {string} role - Sync users under which role? (selfservice, user, admin)
  15096. * @property {string} query - The query
  15097. * @property {string} base - The base
  15098. * @property {string} loginField - The loginField
  15099. * @property {string} nameField - The nameField
  15100. * @property {string} emailField - The emailField
  15101. */
  15102. var UserSync = function (opt) {
  15103. var spec = $.extend({ _fields: ['*'] }, opt);
  15104. Base.call(this, spec);
  15105. this.helper = spec.helper;
  15106. this.kind = spec.kind || DEFAULTS.kind;
  15107. this.name = spec.name || DEFAULTS.name;
  15108. this.host = spec.host || DEFAULTS.host;
  15109. this.port = spec.port || DEFAULTS.port;
  15110. this.timeOut = spec.timeOut || DEFAULTS.timeOut;
  15111. this.login = spec.login || DEFAULTS.login;
  15112. this.password = spec.password || DEFAULTS.password;
  15113. this.newUsers = spec.newUsers || DEFAULTS.newUsers;
  15114. this.existingUsers = spec.existingUsers || DEFAULTS.existingUsers;
  15115. this.missingUsers = spec.missingUsers || DEFAULTS.missingUsers;
  15116. this.autoSync = spec.autoSync != null ? spec.autoSync : DEFAULTS.autoSync;
  15117. this.role = spec.role || DEFAULTS.role;
  15118. this.query = spec.query || DEFAULTS.query;
  15119. this.base = spec.base || DEFAULTS.base;
  15120. this.loginField = spec.loginField || DEFAULTS.loginField;
  15121. this.nameField = spec.nameField || DEFAULTS.nameField;
  15122. this.emailField = spec.emailField || DEFAULTS.emailField;
  15123. this.restrictLocations = spec.restrictLocations ? spec.restrictLocations.slice() : DEFAULTS.restrictLocations.slice();
  15124. this.timezone = spec.timezone || DEFAULTS.timezone;
  15125. this.hostCert = spec.hostCert || DEFAULTS.hostCert;
  15126. };
  15127. UserSync.prototype = new tmp();
  15128. UserSync.prototype.constructor = UserSync;
  15129. //
  15130. // Document overrides
  15131. //
  15132. UserSync.prototype.isValidName = function () {
  15133. this.name = $.trim(this.name);
  15134. return this.name.length >= 3;
  15135. };
  15136. UserSync.prototype.isValidRole = function () {
  15137. switch (this.role) {
  15138. case 'user':
  15139. case 'admin':
  15140. case 'selfservice':
  15141. return true;
  15142. default:
  15143. return false;
  15144. }
  15145. };
  15146. /**
  15147. * Checks if the usersync is valid
  15148. * @method
  15149. * @name UserSync#isValid
  15150. * @returns {boolean}
  15151. */
  15152. UserSync.prototype.isValid = function () {
  15153. return this.isValidName() && this.isValidRole();
  15154. };
  15155. /**
  15156. * Checks if the user is empty
  15157. * @method
  15158. * @name UserSync#isEmpty
  15159. * @returns {boolean}
  15160. */
  15161. UserSync.prototype.isEmpty = function () {
  15162. return Base.prototype.isEmpty.call(this) && this.kind == DEFAULTS.kind && this.name == DEFAULTS.name && this.host == DEFAULTS.host && this.port == DEFAULTS.port && this.timeOut == DEFAULTS.timeOut && this.login == DEFAULTS.login && this.password == DEFAULTS.password && this.newUsers == DEFAULTS.newUsers && this.existsingUsers == DEFAULTS.existingUsers && this.missingUsers == DEFAULTS.missingUsers && this.autoSync == DEFAULTS.autoSync && this.role == DEFAULTS.role && this.query == DEFAULTS.query && this.base == DEFAULTS.base && this.loginField == DEFAULTS.loginField && this.nameField == DEFAULTS.nameField && this.emailField == DEFAULTS.emailField && this.timezone == DEFAULTS.timezone && this.hostCert == DEFAULTS.hostCert && (this.restrictLocations && this.restrictLocations.length == 0);
  15163. };
  15164. /**
  15165. * Checks if the user is dirty and needs saving
  15166. * @method
  15167. * @name UserSync#isDirty
  15168. * @returns {boolean}
  15169. */
  15170. UserSync.prototype.isDirty = function () {
  15171. return this._isDirtyInfo() || this._isDirtyRestrictLocations();
  15172. };
  15173. UserSync.prototype._isDirtyInfo = function () {
  15174. var isDirty = Base.prototype.isDirty.call(this);
  15175. if (!isDirty && this.raw) {
  15176. var kind = this.raw.kind || DEFAULTS.kind;
  15177. var name = this.raw.name || DEFAULTS.name;
  15178. var host = this.raw.host || DEFAULTS.host;
  15179. var port = this.raw.port || DEFAULTS.port;
  15180. var timeOut = this.raw.timeOut || DEFAULTS.timeOut;
  15181. var login = this.raw.login || DEFAULTS.login;
  15182. var password = this.raw.password || DEFAULTS.password;
  15183. var newUsers = this.raw.newUsers || DEFAULTS.newUsers;
  15184. var existingUsers = this.raw.existingUsers || DEFAULTS.existingUsers;
  15185. var missingUsers = this.raw.missingUsers || DEFAULTS.missingUsers;
  15186. var autoSync = this.raw.autoSync != null ? this.raw.autoSync : DEFAULTS.autoSync;
  15187. var role = this.raw.role || DEFAULTS.role;
  15188. var query = this.raw.query || DEFAULTS.query;
  15189. var base = this.raw.base || DEFAULTS.base;
  15190. var loginField = this.raw.loginField || DEFAULTS.loginField;
  15191. var nameField = this.raw.nameField || DEFAULTS.nameField;
  15192. var emailField = this.raw.emailField || DEFAULTS.emailField;
  15193. var timezone = this.raw.timezone || DEFAULTS.timezone;
  15194. var hostCert = this.raw.hostCert || DEFAULTS.hostCert;
  15195. return this.kind != kind || this.name != name || this.host != host || this.port != port || this.timeOut != timeOut || this.login != login || this.password != password || this.newUsers != newUsers || this.existingUsers != existingUsers || this.missingUsers != missingUsers || this.autoSync != autoSync || this.role != role || this.query != query || this.base != base || this.loginField != loginField || this.nameField != nameField || this.emailField != emailField || this.timezone != timezone || this.hostCert != hostCert || this._isDirtyRestrictLocations();
  15196. }
  15197. return isDirty;
  15198. };
  15199. UserSync.prototype._isDirtyRestrictLocations = function () {
  15200. if (this.raw) {
  15201. var that = this, restrictLocations = this.raw.restrictLocations || DEFAULTS.restrictLocations;
  15202. // Check if other locations have been selected
  15203. return this.restrictLocations.filter(function (x) {
  15204. return restrictLocations.indexOf(x) < 0;
  15205. }).length > 0 || restrictLocations.filter(function (x) {
  15206. return that.restrictLocations.indexOf(x) < 0;
  15207. }).length > 0;
  15208. }
  15209. return false;
  15210. };
  15211. UserSync.prototype._getDefaults = function () {
  15212. return DEFAULTS;
  15213. };
  15214. //
  15215. // Business logic
  15216. //
  15217. /**
  15218. * Clones the template to a new one
  15219. * @name UserSync#clone
  15220. * @returns {promise}
  15221. */
  15222. UserSync.prototype.clone = function () {
  15223. return this.ds.call(this.id, 'clone');
  15224. };
  15225. /**
  15226. * Tests the specified connection
  15227. * @name UserSync#testConnection
  15228. * @returns {promise}
  15229. */
  15230. UserSync.prototype.testConnection = function () {
  15231. return this.ds.call(this.id, 'testConnection');
  15232. };
  15233. /**
  15234. * Tests the specified sync
  15235. * @name UserSync#syncUsers
  15236. * @param wetRun
  15237. * @returns {promise}
  15238. */
  15239. UserSync.prototype.syncUsers = function (wetRun) {
  15240. return this.ds.call(this.id, 'syncUsers', { wetRun: wetRun });
  15241. };
  15242. /**
  15243. * Writes the usersync to a json object
  15244. * @param options
  15245. * @returns {object}
  15246. * @private
  15247. */
  15248. UserSync.prototype._toJson = function (options) {
  15249. var data = Base.prototype._toJson.call(this, options);
  15250. data.kind = this.kind || DEFAULTS.kind;
  15251. data.name = this.name || DEFAULTS.name;
  15252. data.host = this.host || DEFAULTS.host;
  15253. data.port = this.port || DEFAULTS.port;
  15254. data.timeOut = this.timeOut || DEFAULTS.timeOut;
  15255. data.login = this.login || DEFAULTS.login;
  15256. data.password = this.password || DEFAULTS.password;
  15257. data.newUsers = this.newUsers || DEFAULTS.newUsers;
  15258. data.existingUsers = this.existingUsers || DEFAULTS.existingUsers;
  15259. data.missingUsers = this.missingUsers || DEFAULTS.missingUsers;
  15260. data.autoSync = this.autoSync != null ? this.autoSync : DEFAULTS.autoSync;
  15261. data.role = this.role || DEFAULTS.role;
  15262. data.query = this.query || DEFAULTS.query;
  15263. data.base = this.base || DEFAULTS.base;
  15264. data.loginField = this.loginField || DEFAULTS.loginField;
  15265. data.nameField = this.nameField || DEFAULTS.nameField;
  15266. data.emailField = this.emailField || DEFAULTS.emailField;
  15267. data.timezone = this.timezone || DEFAULTS.timezone;
  15268. data.hostCert = this.hostCert || DEFAULTS.hostCert;
  15269. return data;
  15270. };
  15271. /**
  15272. * Reads the usersync from the json object
  15273. * @param data
  15274. * @param options
  15275. * @returns {promise}
  15276. * @private
  15277. */
  15278. UserSync.prototype._fromJson = function (data, options) {
  15279. var that = this;
  15280. return Base.prototype._fromJson.call(this, data, options).then(function () {
  15281. // Read the group id from group or group._id
  15282. // depending on the fields
  15283. that.kind = data.kind || DEFAULTS.kind;
  15284. that.name = data.name || DEFAULTS.name;
  15285. that.host = data.host || DEFAULTS.host;
  15286. that.port = data.port || DEFAULTS.port;
  15287. that.timeOut = data.timeOut || DEFAULTS.timeOut;
  15288. that.login = data.login || DEFAULTS.login;
  15289. that.password = data.password || DEFAULTS.password;
  15290. that.newUsers = data.newUsers || DEFAULTS.newUsers;
  15291. that.existingUsers = data.existingUsers || DEFAULTS.existingUsers;
  15292. that.missingUsers = data.missingUsers || DEFAULTS.missingUsers;
  15293. that.autoSync = data.autoSync != null ? data.autoSync : DEFAULTS.autoSync;
  15294. that.role = data.role || DEFAULTS.role;
  15295. that.query = data.query || DEFAULTS.query;
  15296. that.base = data.base || DEFAULTS.base;
  15297. that.loginField = data.loginField || DEFAULTS.loginField;
  15298. that.nameField = data.nameField || DEFAULTS.nameField;
  15299. that.emailField = data.emailField || DEFAULTS.emailField;
  15300. that.restrictLocations = data.restrictLocations ? data.restrictLocations.slice() : DEFAULTS.restrictLocations.slice();
  15301. that.timezone = data.timezone || DEFAULTS.timezone;
  15302. that.hostCert = data.hostCert || DEFAULTS.hostCert;
  15303. $.publish('usersync.fromJson', data);
  15304. return data;
  15305. });
  15306. };
  15307. /**
  15308. * Restrict user access to specific location(s)
  15309. * @param locations
  15310. * @param skipRead
  15311. * @returns {promise}
  15312. */
  15313. UserSync.prototype.setRestrictLocations = function (locations, skipRead) {
  15314. if (!this.existsInDb()) {
  15315. return $.Deferred().reject('Usersync does not exist in database');
  15316. }
  15317. return this._doApiCall({
  15318. method: 'setRestrictLocations',
  15319. params: { restrictLocations: locations },
  15320. skipRead: skipRead
  15321. });
  15322. };
  15323. /**
  15324. * Clear user location(s) access (makes all location accessible for the user)
  15325. * @param skipRead
  15326. * @returns {promise}
  15327. */
  15328. UserSync.prototype.clearRestrictLocations = function (skipRead) {
  15329. if (!this.existsInDb()) {
  15330. return $.Deferred().reject('Usersync does not exist in database');
  15331. }
  15332. return this._doApiCall({
  15333. method: 'clearRestrictLocations',
  15334. skipRead: skipRead
  15335. });
  15336. };
  15337. /**
  15338. * Updates the usersync
  15339. * @param skipRead
  15340. * @returns {*}
  15341. */
  15342. UserSync.prototype.update = function (skipRead) {
  15343. if (this.isEmpty()) {
  15344. return $.Deferred().reject(new Error('Cannot update to empty usersync'));
  15345. }
  15346. if (!this.existsInDb()) {
  15347. return $.Deferred().reject(new Error('Cannot update usersync without id'));
  15348. }
  15349. if (!this.isValid()) {
  15350. return $.Deferred().reject(new Error('Cannot update, invalid usersync'));
  15351. }
  15352. var that = this, dfdRestrictLocations = $.Deferred(), dfdInfo = $.Deferred(), params = this._toJson();
  15353. if (this._isDirtyInfo()) {
  15354. dfdInfo = this.ds.update(this.id, params, this._fields).then(function (resp) {
  15355. that._fromJson(resp);
  15356. });
  15357. } else {
  15358. dfdInfo.resolve();
  15359. }
  15360. if (this._isDirtyRestrictLocations()) {
  15361. if (this.restrictLocations.length != 0) {
  15362. dfdRestrictLocations = this.setRestrictLocations(this.restrictLocations, true);
  15363. } else {
  15364. dfdRestrictLocations = this.clearRestrictLocations(true);
  15365. }
  15366. } else {
  15367. dfdRestrictLocations.resolve();
  15368. }
  15369. return $.when(dfdInfo, dfdRestrictLocations);
  15370. };
  15371. UserSync.prototype.create = function (skipRead) {
  15372. if (this.existsInDb()) {
  15373. return $.Deferred().reject(new Error('Cannot create document, already exists in database'));
  15374. }
  15375. if (this.isEmpty()) {
  15376. return $.Deferred().reject(new Error('Cannot create empty document'));
  15377. }
  15378. return this._create(skipRead);
  15379. };
  15380. return UserSync;
  15381. }(jquery, base, common);
  15382. WebHook = function ($, common, api, Document) {
  15383. // Some constant values
  15384. var DEFAULTS = {
  15385. id: '',
  15386. name: '',
  15387. address: '',
  15388. topic: '',
  15389. hookFields: '*, location.*, items.*, customer.*,by.name,by.email',
  15390. // avoid clash with Document.fields
  15391. format: '',
  15392. created: null,
  15393. modified: null,
  15394. enabled: true,
  15395. log10: [],
  15396. fails: 0,
  15397. minutes: 0
  15398. };
  15399. // Allow overriding the ctor during inheritance
  15400. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  15401. var tmp = function () {
  15402. };
  15403. tmp.prototype = Document.prototype;
  15404. /**
  15405. * WebHook describes a webhook which can trigger on certain events (signals)
  15406. * @name WebHook
  15407. * @class
  15408. * @property {string} name the name
  15409. * @property {string} address the url which will be called
  15410. * @property {string} topic the topic name ('item.changelocation', 'item.changegeo', 'item.expire', ...)
  15411. * @property {string} hookFields the fields which should be fetched from the db (default: `*, location.*, items.*, customer.*)
  15412. * @property {string} format the output format (only `json` is supported)
  15413. * @property {Moment} created the creation date of the webhook
  15414. * @property {Moment} modified the modified date of the webhook
  15415. * @property {boolean} enabled whether or not the webhook is enabled
  15416. * @property {array} log10 the last 10 logs of the webhook
  15417. * @property {int} fails the number of consequtive fails
  15418. * @property {int} minutes only for due and overdue webhooks, non-negative
  15419. * @constructor
  15420. * @extends Document
  15421. */
  15422. var WebHook = function (opt) {
  15423. var spec = $.extend({}, opt);
  15424. Document.call(this, spec);
  15425. this.name = spec.name || DEFAULTS.name;
  15426. this.address = spec.address || DEFAULTS.address;
  15427. this.topic = spec.topic || DEFAULTS.topic;
  15428. this.hookFields = spec.hookFields || DEFAULTS.hookFields;
  15429. this.created = spec.created || DEFAULTS.created;
  15430. this.modified = spec.modified || DEFAULTS.modified;
  15431. this.enabled = spec.enabled != null ? spec.enabled == true : DEFAULTS.enabled;
  15432. this.log10 = spec.log10 || DEFAULTS.log10.slice();
  15433. this.fails = spec.fails || DEFAULTS.fails;
  15434. this.minutes = spec.minutes || DEFAULTS.minutes;
  15435. };
  15436. WebHook.prototype = new tmp();
  15437. WebHook.prototype.constructor = WebHook;
  15438. //
  15439. // Specific validators
  15440. /**
  15441. * Checks if name is valid
  15442. * @name WebHook#isValidName
  15443. * @method
  15444. * @return {Boolean}
  15445. */
  15446. WebHook.prototype.isValidName = function () {
  15447. this.name = $.trim(this.name);
  15448. return this.name.length >= 3;
  15449. };
  15450. /**
  15451. * Checks if address is valid
  15452. * @name WebHook#isValidAddress
  15453. * @method
  15454. * @return {Boolean}
  15455. */
  15456. WebHook.prototype.isValidAddress = function () {
  15457. this.address = $.trim(this.address);
  15458. return common.isValidURL(this.address);
  15459. };
  15460. //
  15461. // Document overrides
  15462. //
  15463. /**
  15464. * Checks if the webhook has any validation errors
  15465. * @name WebHook#isValid
  15466. * @method
  15467. * @returns {boolean}
  15468. * @override
  15469. */
  15470. WebHook.prototype.isValid = function () {
  15471. return this.isValidName() && this.isValidAddress() && this.topic; // TODO: We need a better check here
  15472. };
  15473. WebHook.prototype._getDefaults = function () {
  15474. return DEFAULTS;
  15475. };
  15476. /**
  15477. * Checks if the object is empty, it never is
  15478. * @name WebHook#isEmpty
  15479. * @method
  15480. * @returns {boolean}
  15481. * @override
  15482. */
  15483. WebHook.prototype.isEmpty = function () {
  15484. return Document.prototype.isEmpty.call(this) && this.name == DEFAULTS.name && this.address == DEFAULTS.address && this.topic == DEFAULTS.topic;
  15485. };
  15486. /**
  15487. * Checks if the webhook is dirty and needs saving
  15488. * @returns {boolean}
  15489. * @override
  15490. */
  15491. WebHook.prototype.isDirty = function () {
  15492. var isDirty = Document.prototype.isDirty.call(this);
  15493. if (!isDirty && this.raw) {
  15494. isDirty = this.name != this.raw.name || this.address != this.raw.address || this.topic != this.raw.topic || this.enabled != this.raw.enabled || this.minutes != this.raw.minutes;
  15495. }
  15496. return isDirty;
  15497. };
  15498. /**
  15499. * Checks via the api if we can delete the WebHook document
  15500. * @name WebHook#canDelete
  15501. * @method
  15502. * @returns {promise}
  15503. * @override
  15504. */
  15505. WebHook.prototype.canDelete = function () {
  15506. // An WebHook can always be deleted
  15507. return $.Deferred().resolve(true);
  15508. };
  15509. // toJson, fromJson
  15510. // ----
  15511. /**
  15512. * _toJson, makes a dict of params to use during create / update
  15513. * @param options
  15514. * @returns {{}}
  15515. * @private
  15516. */
  15517. WebHook.prototype._toJson = function (options) {
  15518. var data = Document.prototype._toJson.call(this, options);
  15519. data.name = this.name;
  15520. data.address = this.address;
  15521. data.topic = this.topic;
  15522. data.fields = this.hookFields;
  15523. data.enabled = this.enabled;
  15524. data.minutes = this.minutes;
  15525. // don't write out fields for:
  15526. // - created_at
  15527. // - log10, log
  15528. // - nr_consecutive_fails
  15529. return data;
  15530. };
  15531. /**
  15532. * _fromJson: read some basic information
  15533. * @method
  15534. * @param {object} data the json response
  15535. * @param {object} options dict
  15536. * @returns {Promise}
  15537. * @private
  15538. */
  15539. WebHook.prototype._fromJson = function (data, options) {
  15540. var that = this;
  15541. return Document.prototype._fromJson.call(this, data, options).then(function () {
  15542. that.name = data.name || DEFAULTS.name;
  15543. that.address = data.address || DEFAULTS.address;
  15544. that.topic = data.topic || DEFAULTS.topic;
  15545. that.hookFields = data.fields || DEFAULTS.hookFields;
  15546. // !
  15547. that.created = data.created_at || DEFAULTS.created;
  15548. // !
  15549. that.modified = data.modified || DEFAULTS.modified;
  15550. that.enabled = data.enabled != null ? data.enabled == true : DEFAULTS.enabled;
  15551. that.log10 = data.log10 || DEFAULTS.log10.slice();
  15552. that.fails = data.nr_consecutive_fails || DEFAULTS.fails;
  15553. that.minutes = data.minutes || DEFAULTS.minutes;
  15554. return data;
  15555. });
  15556. };
  15557. return WebHook;
  15558. }(jquery, common, api, document);
  15559. OrderTransfer = function ($, Base) {
  15560. var DEFAULTS = {
  15561. by: null,
  15562. created: null,
  15563. modified: null,
  15564. status: 'creating',
  15565. items: [],
  15566. started: null,
  15567. accepted: null,
  15568. fromOrder: null,
  15569. toOrder: null,
  15570. startedBy: null
  15571. };
  15572. // Allow overriding the ctor during inheritance
  15573. // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
  15574. var tmp = function () {
  15575. };
  15576. tmp.prototype = Base.prototype;
  15577. /**
  15578. * OrderTransfer
  15579. * @name OrderTransfer
  15580. * @class OrderTransfer
  15581. * @constructor
  15582. * @property {string} id short UUID
  15583. * @property {cr.User} by who created this doc
  15584. * @property {Date} created when was this doc created
  15585. * @property {Date} modified when was this doc last modified
  15586. * @property {string} status creating, open, closed
  15587. * @property {Array} items list of items
  15588. * @property {Date} started when was the transfer started
  15589. * @property {Date} accepted when was the transfer accepted
  15590. * @property {Date} fromOrder from order
  15591. * @property {Date} toOrder to order
  15592. * @property {cr.User} startedBy who started the transfer
  15593. * @extends Base
  15594. */
  15595. var OrderTransfer = function (opt) {
  15596. var spec = $.extend({
  15597. _fields: ['*'],
  15598. crtype: 'cheqroom.types.reservation.ordertransfer'
  15599. }, opt);
  15600. Base.call(this, spec);
  15601. this.by = spec.by || DEFAULTS.by;
  15602. this.created = spec.created || DEFAULTS.created;
  15603. this.modified = spec.modified || DEFAULTS.modified;
  15604. this.status = spec.status || DEFAULTS.status;
  15605. this.items = spec.items || DEFAULTS.items;
  15606. this.started = spec.started || DEFAULTS.started;
  15607. this.accepted = spec.accepted || DEFAULTS.accepted;
  15608. this.fromOrder = spec.fromOrder || DEFAULTS.fromOrder;
  15609. this.toOrder = spec.toOrder || DEFAULTS.toOrder;
  15610. this.startedBy = spec.startedBy || DEFAULTS.startedBy;
  15611. };
  15612. OrderTransfer.prototype = new tmp();
  15613. OrderTransfer.prototype.constructor = OrderTransfer;
  15614. // Base overrides
  15615. // ----
  15616. /**
  15617. * Checks if the order transfer is empty
  15618. * @name OrderTransfer#isEmpty
  15619. * @returns {boolean}
  15620. */
  15621. OrderTransfer.prototype.isEmpty = function () {
  15622. return false;
  15623. };
  15624. OrderTransfer.prototype._toJson = function (options) {
  15625. // Writes out; id, items
  15626. var data = Base.prototype._toJson.call(this, options);
  15627. data.items = this.items || DEFAULTS.items;
  15628. return data;
  15629. };
  15630. OrderTransfer.prototype._fromJson = function (data, options) {
  15631. var that = this;
  15632. return Base.prototype._fromJson.call(this, data, options).then(function () {
  15633. that.by = data.by || DEFAULTS.by;
  15634. that.created = data.created || DEFAULTS.created;
  15635. that.modified = data.modified || DEFAULTS.modified;
  15636. that.items = data.items || DEFAULTS.items;
  15637. that.status = data.status || DEFAULTS.status;
  15638. that.started = data.started || DEFAULTS.started;
  15639. that.accepted = data.accepted || DEFAULTS.accepted;
  15640. that.fromOrder = data.fromOrder || DEFAULTS.fromOrder;
  15641. that.toOrder = data.toOrder || DEFAULTS.toOrder;
  15642. that.startedBy = data.startedBy || DEFAULTS.startedBy;
  15643. return data;
  15644. });
  15645. };
  15646. // Business logic
  15647. // ----
  15648. /**
  15649. * addItems adds items to transfer from an order (must be items of the same order)
  15650. *
  15651. * @name OrderTransfer#addItems
  15652. * @returns {promise}
  15653. */
  15654. OrderTransfer.prototype.addItems = function (items, skipRead) {
  15655. return this._doApiCall({
  15656. method: 'addItems',
  15657. params: { items: items },
  15658. skipRead: skipRead
  15659. });
  15660. };
  15661. /**
  15662. * removeItems removes items from transfer
  15663. *
  15664. * @name OrderTransfer#removeItems
  15665. * @returns {promise}
  15666. */
  15667. OrderTransfer.prototype.removeItems = function (items, skipRead) {
  15668. return this._doApiCall({
  15669. method: 'removeItems',
  15670. params: { items: items },
  15671. skipRead: skipRead
  15672. });
  15673. };
  15674. /**
  15675. * start puts the transfer in status "open"
  15676. *
  15677. * @name OrderTransfer#start
  15678. * @return {promise}
  15679. */
  15680. OrderTransfer.prototype.start = function (skipRead) {
  15681. return this._doApiCall({
  15682. method: 'start',
  15683. params: {},
  15684. skipRead: skipRead
  15685. });
  15686. };
  15687. /**
  15688. * undoStart puts the transfer in status "creating" again
  15689. *
  15690. * @name OrderTransfer#undoStart
  15691. * @return {promise}
  15692. */
  15693. OrderTransfer.prototype.undoStart = function (skipRead) {
  15694. return this._doApiCall({
  15695. method: 'undoStart',
  15696. params: {},
  15697. skipRead: skipRead
  15698. });
  15699. };
  15700. /**
  15701. * accept transfers the items to another customer
  15702. *
  15703. * @name OrderTransfer#accept
  15704. * @return {promise}
  15705. */
  15706. OrderTransfer.prototype.accept = function (params, skipRead) {
  15707. return this._doApiCall({
  15708. method: 'accept',
  15709. params: params,
  15710. skipRead: skipRead
  15711. });
  15712. };
  15713. /**
  15714. * getQRUrl returns path to transfer qr code
  15715. *
  15716. * @name OrderTransfer#qr
  15717. * @return {string}
  15718. */
  15719. OrderTransfer.prototype.getQRUrl = function (size) {
  15720. return this.ds._baseUrl + '/' + this.id + '/call/qr?size=' + (size || 300);
  15721. };
  15722. return OrderTransfer;
  15723. }(jquery, base);
  15724. ColorLabel = function ($) {
  15725. var DEFAULTS = {
  15726. id: null,
  15727. name: '',
  15728. color: 'Gold',
  15729. readonly: false,
  15730. selected: false
  15731. };
  15732. /**
  15733. * @name ColorLabel
  15734. * @class
  15735. * @param spec
  15736. * @constructor
  15737. */
  15738. var ColorLabel = function (spec) {
  15739. spec = spec || {};
  15740. this.raw = $.extend({}, DEFAULTS, spec);
  15741. this.id = spec.id || DEFAULTS.id;
  15742. this.name = spec.name || DEFAULTS.name;
  15743. this.color = spec.color || DEFAULTS.color;
  15744. this.readonly = spec.readonly || DEFAULTS.readonly;
  15745. this.selected = spec.selected || DEFAULTS.selected;
  15746. };
  15747. /**
  15748. * isDirty
  15749. * @name ColorLabel#isDirty
  15750. * @method
  15751. * @returns {boolean}
  15752. */
  15753. ColorLabel.prototype.isDirty = function () {
  15754. return this.raw.name != this.name || this.raw.color != this.color;
  15755. };
  15756. /**
  15757. * isValid
  15758. * @name ColorLabel#isValid
  15759. * @method
  15760. * @returns {boolean}
  15761. */
  15762. ColorLabel.prototype.isValid = function () {
  15763. return this.name && this.name.length > 0;
  15764. };
  15765. /**
  15766. * _fromJson
  15767. * @name ColorLabel#_fromJson
  15768. * @method
  15769. * @returns {boolean}
  15770. */
  15771. ColorLabel.prototype._fromJson = function (data) {
  15772. this.id = data.id || DEFAULTS.id;
  15773. this.name = data.name || DEFAULTS.name;
  15774. this.color = data.color || DEFAULTS.color;
  15775. this.selected = data.selected || DEFAULTS.selected;
  15776. this.readonly = data.readonly || DEFAULTS.readonly;
  15777. return $.Deferred().resolve();
  15778. };
  15779. /**
  15780. * _toJson
  15781. * @name ColorLabel#_toJson
  15782. * @method
  15783. * @returns {boolean}
  15784. */
  15785. ColorLabel.prototype._toJson = function () {
  15786. return {
  15787. id: this.id,
  15788. name: this.name,
  15789. color: this.color,
  15790. selected: this.selected,
  15791. readonly: this.readonly
  15792. };
  15793. };
  15794. return ColorLabel;
  15795. }(jquery);
  15796. Field = function ($) {
  15797. var DEFAULTS = {
  15798. name: null,
  15799. value: null,
  15800. required: false,
  15801. unit: '',
  15802. kind: 'string',
  15803. form: false,
  15804. editor: null,
  15805. description: ''
  15806. };
  15807. /**
  15808. * @name Field
  15809. * @class
  15810. * @param spec
  15811. * @constructor
  15812. */
  15813. var Field = function (spec) {
  15814. spec = spec || {};
  15815. this.raw = spec;
  15816. this.name = spec.name || DEFAULTS.name;
  15817. this.value = spec.value || DEFAULTS.value;
  15818. this.required = spec.required || DEFAULTS.required;
  15819. this.unit = spec.unit || DEFAULTS.unit;
  15820. this.kind = spec.kind || DEFAULTS.kind;
  15821. this.form = spec.form || DEFAULTS.form;
  15822. this.editor = spec.editor || DEFAULTS.editor;
  15823. this.description = spec.description || DEFAULTS.description;
  15824. };
  15825. /**
  15826. * isValid
  15827. * @name Field#isValid
  15828. * @method
  15829. * @returns {boolean}
  15830. */
  15831. Field.prototype.isValid = function () {
  15832. if (!this.required)
  15833. return true;
  15834. return $.trim(this.value) != '';
  15835. };
  15836. /**
  15837. * isDirty
  15838. * @name Field#isDirty
  15839. * @method
  15840. * @returns {boolean}
  15841. */
  15842. Field.prototype.isDirty = function () {
  15843. return this.raw.value != this.value;
  15844. };
  15845. /**
  15846. * isEmpty
  15847. * @name Field#isEmpty
  15848. * @method
  15849. * @returns {boolean}
  15850. */
  15851. Field.prototype.isEmpty = function () {
  15852. return $.trim(this.value) == '';
  15853. };
  15854. return Field;
  15855. }(jquery);
  15856. core = function (api, Availability, Attachment, Base, Category, Comment, Conflict, Contact, DateHelper, Document, Group, Item, Kit, Location, Order, Helper, PermissionHandler, Reservation, Template, Transaction, User, UserSync, WebHook, common, OrderTransfer, ColorLabel, Field) {
  15857. var core = {};
  15858. // namespaces
  15859. core.api = api;
  15860. core.common = common;
  15861. // Constructors
  15862. core.Availability = Availability;
  15863. core.Attachment = Attachment;
  15864. core.Base = Base;
  15865. core.Category = Category;
  15866. core.Comment = Comment;
  15867. core.Conflict = Conflict;
  15868. core.Contact = Contact;
  15869. core.DateHelper = DateHelper;
  15870. core.Document = Document;
  15871. core.Group = Group;
  15872. core.Item = Item;
  15873. core.Kit = Kit;
  15874. core.Location = Location;
  15875. core.Order = Order;
  15876. core.PermissionHandler = PermissionHandler;
  15877. core.Reservation = Reservation;
  15878. core.Template = Template;
  15879. core.Transaction = Transaction;
  15880. core.User = User;
  15881. core.UserSync = UserSync;
  15882. core.WebHook = WebHook;
  15883. core.OrderTransfer = OrderTransfer;
  15884. core.Helper = Helper;
  15885. core.ColorLabel = ColorLabel;
  15886. core.Field = Field;
  15887. return core;
  15888. }(api, Availability, Attachment, Base, Category, Comment, Conflict, Contact, DateHelper, Document, Group, Item, Kit, Location, Order, helper, PermissionHandler, Reservation, Template, Transaction, User, UserSync, WebHook, common, OrderTransfer, ColorLabel, Field);
  15889. return core;
  15890. }))
  15891. },{}],2:[function(require,module,exports){
  15892. var cr = require('cheqroom-core');
  15893.  
  15894. var some = new cr.api.ApiNotFound;
  15895. console.log(some);
  15896.  
  15897. // // var cr = window.cheqroomCore;
  15898. // //
  15899. // // Authenticating via username and password
  15900. // //
  15901. // var baseUrl = 'https://api.cheqroom.com/api/v2_5';
  15902. // var userName = "jesus.alvarado";
  15903. // var password = "jesalv2018";
  15904.  
  15905. // var ajax = new cr.api.ApiAjax();
  15906. // var auth = new cr.api.ApiAuthV2({ajax: ajax, urlAuth: baseUrl + '/authenticate'});
  15907. // var authUser = null;
  15908.  
  15909. // auth.authenticate(userName, password)
  15910. // .done(function(data) {
  15911. // authUser = new cr.api.ApiUser({userId: data.userId, userToken: data.token});
  15912. // });
  15913.  
  15914.  
  15915. // //
  15916. // // Using datasources
  15917. // //
  15918.  
  15919. // // Listing open orders
  15920. // var dsOrders = new cr.api.ApiDataSource({collection: 'orders', ajax: ajax, user: authUser, urlApi: baseUrl});
  15921. // dsOrders.list("open_orders")
  15922. // .done(function(data) {
  15923. // console.log(data);
  15924. // });
  15925. },{"cheqroom-core":1}]},{},[2]);
Add Comment
Please, Sign In to add comment