0))return;e.preventDefault(),n&&s.selection.getSel().modify(\"extend\",t?\"forward\":\"backward\",e.metaKey?\"lineboundary\":\"word\"),m(t)}}),s.on(\"keypress\",function(t){if(!u(t)&&!y.isCollapsed()&&t.charCode&&!e.metaKeyPressed(t)){var n,r,i,o,a,l;n=s.selection.getRng(),l=String.fromCharCode(t.charCode),t.preventDefault(),r=Y(n.startContainer).parents().filter(function(e,t){return!!s.schema.getTextInlineElements()[t.nodeName]}),m(!0),r=r.filter(function(e,t){return!Y.contains(s.getBody(),t)}),r.length?(i=v.createFragment(),r.each(function(e,t){t=t.cloneNode(!1),i.hasChildNodes()?(t.appendChild(i.firstChild),i.appendChild(t)):(a=t,i.appendChild(t)),i.appendChild(t)}),a.appendChild(s.getDoc().createTextNode(l)),o=v.getParent(n.startContainer,v.isBlock),v.isEmpty(o)?Y(o).empty().append(i):n.insertNode(i),n.setStart(a.firstChild,1),n.setEnd(a.firstChild,1),s.selection.setRng(n)):s.selection.setContent(l)}}),s.addCommand(\"Delete\",function(){m()}),s.addCommand(\"ForwardDelete\",function(){m(!0)}),C||(s.on(\"dragstart\",function(e){x=y.getRng(),d(e)}),s.on(\"drop\",function(e){if(!u(e)){var n=f(e);n&&(e.preventDefault(),window.setTimeout(function(){var r=t.getCaretRangeFromPoint(e.x,e.y,g);x&&(y.setRng(x),x=null),m(),y.setRng(r),p(n)},0))}}),s.on(\"cut\",function(e){!u(e)&&e.clipboardData&&(e.preventDefault(),e.clipboardData.clearData(),e.clipboardData.setData(\"text/html\",s.selection.getContent()),e.clipboardData.setData(\"text/plain\",s.selection.getContent({format:\"text\"})),m(!0))}))}function m(){function e(e){var t=J.create(\"body\"),n=e.cloneContents();return t.appendChild(n),Q.serializer.serialize(t,{format:\"html\"})}function n(n){if(!n.setStart){if(n.item)return!1;var r=n.duplicate();return r.moveToElementText(s.getBody()),t.compareRanges(n,r)}var i=e(n),o=J.createRng();o.selectNode(s.getBody());var a=e(o);return i===a}s.on(\"keydown\",function(e){var t=e.keyCode,r,i;if(!u(e)&&(t==X||t==G)){if(r=s.selection.isCollapsed(),i=s.getBody(),r&&!J.isEmpty(i))return;if(!r&&!n(s.selection.getRng()))return;e.preventDefault(),s.setContent(\"\"),i.firstChild&&J.isBlock(i.firstChild)?s.selection.setCursorLocation(i.firstChild,0):s.selection.setCursorLocation(i,0),s.nodeChanged()}})}function g(){s.shortcuts.add(\"meta+a\",null,\"SelectAll\")}function v(){s.settings.content_editable||(J.bind(s.getDoc(),\"focusin\",function(){Q.setRng(Q.getRng())}),J.bind(s.getDoc(),\"mousedown mouseup\",function(e){e.target==s.getDoc().documentElement&&(s.getBody().focus(),\"mousedown\"==e.type?Q.placeCaretAt(e.clientX,e.clientY):Q.setRng(Q.getRng()))}))}function y(){s.on(\"keydown\",function(e){if(!u(e)&&e.keyCode===G){if(!s.getBody().getElementsByTagName(\"hr\").length)return;if(Q.isCollapsed()&&0===Q.getRng(!0).startOffset){var t=Q.getNode(),n=t.previousSibling;if(\"HR\"==t.nodeName)return J.remove(t),void e.preventDefault();n&&n.nodeName&&\"hr\"===n.nodeName.toLowerCase()&&(J.remove(n),e.preventDefault())}}})}function b(){window.Range.prototype.getClientRects||s.on(\"mousedown\",function(e){if(!u(e)&&\"HTML\"===e.target.nodeName){var t=s.getBody();t.blur(),setTimeout(function(){t.focus()},0)}})}function C(){s.on(\"click\",function(e){var t=e.target;/^(IMG|HR)$/.test(t.nodeName)&&(e.preventDefault(),Q.getSel().setBaseAndExtent(t,0,t,1),s.nodeChanged()),\"A\"==t.nodeName&&J.hasClass(t,\"mce-item-anchor\")&&(e.preventDefault(),Q.select(t))})}function x(){function e(){var e=J.getAttribs(Q.getStart().cloneNode(!1));return function(){var t=Q.getStart();t!==s.getBody()&&(J.setAttrib(t,\"style\",null),K(e,function(e){t.setAttributeNode(e.cloneNode(!0))}))}}function t(){return!Q.isCollapsed()&&J.getParent(Q.getStart(),J.isBlock)!=J.getParent(Q.getEnd(),J.isBlock)}s.on(\"keypress\",function(n){var r;return u(n)||8!=n.keyCode&&46!=n.keyCode||!t()?void 0:(r=e(),s.getDoc().execCommand(\"delete\",!1,null),r(),n.preventDefault(),!1)}),J.bind(s.getDoc(),\"cut\",function(n){var r;!u(n)&&t()&&(r=e(),setTimeout(function(){r()},0))})}function w(){document.body.setAttribute(\"role\",\"application\")}function _(){s.on(\"keydown\",function(e){if(!u(e)&&e.keyCode===G&&Q.isCollapsed()&&0===Q.getRng(!0).startOffset){var t=Q.getNode().previousSibling;if(t&&t.nodeName&&\"table\"===t.nodeName.toLowerCase())return e.preventDefault(),!1}})}function E(){c()>7||(l(\"RespectVisibilityInDesign\",!0),s.contentStyles.push(\".mceHideBrInPre pre br {display: none}\"),J.addClass(s.getBody(),\"mceHideBrInPre\"),ee.addNodeFilter(\"pre\",function(e){for(var t=e.length,n,i,o,a;t--;)for(n=e[t].getAll(\"br\"),i=n.length;i--;)o=n[i],a=o.prev,a&&3===a.type&&\"\\n\"!=a.value.charAt(a.value-1)?a.value+=\"\\n\":o.parent.insert(new r(\"#text\",3),o,!0).value=\"\\n\"}),te.addNodeFilter(\"pre\",function(e){for(var t=e.length,n,r,i,o;t--;)for(n=e[t].getAll(\"br\"),r=n.length;r--;)i=n[r],o=i.prev,o&&3==o.type&&(o.value=o.value.replace(/\\r?\\n$/,\"\"))}))}function N(){J.bind(s.getBody(),\"mouseup\",function(){var e,t=Q.getNode();\"IMG\"==t.nodeName&&((e=J.getStyle(t,\"width\"))&&(J.setAttrib(t,\"width\",e.replace(/[^0-9%]+/g,\"\")),J.setStyle(t,\"width\",\"\")),(e=J.getStyle(t,\"height\"))&&(J.setAttrib(t,\"height\",e.replace(/[^0-9%]+/g,\"\")),J.setStyle(t,\"height\",\"\")))})}function k(){s.on(\"keydown\",function(t){var n,r,i,o,a;if(!u(t)&&t.keyCode==e.BACKSPACE&&(n=Q.getRng(),r=n.startContainer,i=n.startOffset,o=J.getRoot(),a=r,n.collapsed&&0===i)){for(;a&&a.parentNode&&a.parentNode.firstChild==a&&a.parentNode!=o;)a=a.parentNode;\"BLOCKQUOTE\"===a.tagName&&(s.formatter.toggle(\"blockquote\",null,a),n=J.createRng(),n.setStart(r,0),n.setEnd(r,0),Q.setRng(n))}})}function S(){function e(){s._refreshContentEditable(),l(\"StyleWithCSS\",!1),l(\"enableInlineTableEditing\",!1),Z.object_resizing||l(\"enableObjectResizing\",!1)}Z.readonly||s.on(\"BeforeExecCommand MouseDown\",e)}function T(){function e(){K(J.select(\"a\"),function(e){var t=e.parentNode,n=J.getRoot();if(t.lastChild===e){for(;t&&!J.isBlock(t);){if(t.parentNode.lastChild!==t||t===n)return;t=t.parentNode}J.add(t,\"br\",{\"data-mce-bogus\":1})}})}s.on(\"SetContent ExecCommand\",function(t){(\"setcontent\"==t.type||\"mceInsertLink\"===t.command)&&e()})}function R(){Z.forced_root_block&&s.on(\"init\",function(){l(\"DefaultParagraphSeparator\",Z.forced_root_block)})}function A(){s.on(\"Undo Redo SetContent\",function(e){e.initial||s.execCommand(\"mceRepaint\")})}function B(){s.on(\"keydown\",function(e){var t;u(e)||e.keyCode!=G||(t=s.getDoc().selection.createRange(),t&&t.item&&(e.preventDefault(),s.undoManager.beforeChange(),J.remove(t.item(0)),s.undoManager.add()))})}function D(){var e;c()>=10&&(e=\"\",K(\"p div h1 h2 h3 h4 h5 h6\".split(\" \"),function(t,n){e+=(n>0?\",\":\"\")+t+\":empty\"}),s.contentStyles.push(e+\"{padding-right: 1px !important}\"))}function M(){c()<9&&(ee.addNodeFilter(\"noscript\",function(e){for(var t=e.length,n,r;t--;)n=e[t],r=n.firstChild,r&&n.attr(\"data-mce-innertext\",r.value)}),te.addNodeFilter(\"noscript\",function(e){for(var t=e.length,n,o,a;t--;)n=e[t],o=e[t].firstChild,o?o.value=i.decode(o.value):(a=n.attributes.map[\"data-mce-innertext\"],a&&(n.attr(\"data-mce-innertext\",null),o=new r(\"#text\",3),o.value=a,o.raw=!0,n.append(o)))}))}function H(){function e(e,t){var n=i.createTextRange();try{n.moveToPoint(e,t)}catch(r){n=null}return n}function t(t){var r;t.button?(r=e(t.x,t.y),r&&(r.compareEndPoints(\"StartToStart\",a)>0?r.setEndPoint(\"StartToStart\",a):r.setEndPoint(\"EndToEnd\",a),r.select())):n()}function n(){var e=r.selection.createRange();a&&!e.item&&0===e.compareEndPoints(\"StartToEnd\",e)&&a.select(),J.unbind(r,\"mouseup\",n),J.unbind(r,\"mousemove\",t),a=o=0}var r=J.doc,i=r.body,o,a,s;r.documentElement.unselectable=!0,J.bind(r,\"mousedown contextmenu\",function(i){if(\"HTML\"===i.target.nodeName){if(o&&n(),s=r.documentElement,s.scrollHeight>s.clientHeight)return;o=1,a=e(i.x,i.y),a&&(J.bind(r,\"mouseup\",n),J.bind(r,\"mousemove\",t),J.getRoot().focus(),a.select())}})}function L(){s.on(\"keyup focusin mouseup\",function(t){65==t.keyCode&&e.metaKeyPressed(t)||Q.normalize()},!0)}function P(){s.contentStyles.push(\"img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}\")}function O(){s.inline||s.on(\"keydown\",function(){document.activeElement==document.body&&s.getWin().focus()})}function I(){s.inline||(s.contentStyles.push(\"body {min-height: 150px}\"),s.on(\"click\",function(e){if(\"HTML\"==e.target.nodeName){var t;t=s.selection.getRng(),s.getBody().focus(),s.selection.setRng(t),s.selection.normalize(),s.nodeChanged()}}))}function F(){o.mac&&s.on(\"keydown\",function(t){!e.metaKeyPressed(t)||37!=t.keyCode&&39!=t.keyCode||(t.preventDefault(),s.selection.getSel().modify(\"move\",37==t.keyCode?\"backward\":\"forward\",\"lineboundary\"))})}function z(){l(\"AutoUrlDetect\",!1)}function W(){s.inline||s.on(\"focus blur beforegetcontent\",function(){var e=s.dom.create(\"br\");s.getBody().appendChild(e),e.parentNode.removeChild(e)},!0)}function V(){s.on(\"click\",function(e){var t=e.target;do if(\"A\"===t.tagName)return void e.preventDefault();while(t=t.parentNode)}),s.contentStyles.push(\".mce-content-body {-webkit-touch-callout: none}\")}function U(){s.on(\"touchstart\",function(e){var t,n,r,i;t=e.target,n=(new Date).getTime(),i=e.changedTouches,!i||i.length>1||(r=i[0],s.once(\"touchend\",function(e){var i=e.changedTouches[0],o;(new Date).getTime()-n>500||Math.abs(r.clientX-i.clientX)>5||Math.abs(r.clientY-i.clientY)>5||(o={target:t},K(\"pageX pageY clientX clientY screenX screenY\".split(\" \"),function(e){o[e]=i[e]}),o=s.fire(\"click\",o),o.isDefaultPrevented()||(s.selection.placeCaretAt(i.clientX,i.clientY),s.nodeChanged()))}))})}function $(){s.on(\"init\",function(){s.dom.bind(s.getBody(),\"submit\",function(e){e.preventDefault()})})}function q(){ee.addNodeFilter(\"br\",function(e){for(var t=e.length;t--;)\"Apple-interchange-newline\"==e[t].attr(\"class\")&&e[t].remove()})}function j(){s.on(\"dragstart\",function(e){d(e)}),s.on(\"drop\",function(e){if(!u(e)){var n=f(e);if(n){e.preventDefault();var r=t.getCaretRangeFromPoint(e.x,e.y,s.getDoc());Q.setRng(r),p(n)}}})}var K=a.each,Y=s.$,G=e.BACKSPACE,X=e.DELETE,J=s.dom,Q=s.selection,Z=s.settings,ee=s.parser,te=s.serializer,ne=o.gecko,re=o.ie,ie=o.webkit,oe=\"data:text/mce-internal,\",ae=re?\"Text\":\"URL\";k(),m(),L(),ie&&(h(),v(),C(),R(),$(),_(),q(),U(),o.iOS?(O(),I(),V()):g()),re&&o.ie<11&&(y(),w(),E(),N(),B(),D(),M(),H()),o.ie>=11&&(I(),W(),_()),o.ie&&(g(),z(),j()),ne&&(y(),b(),x(),S(),T(),A(),P(),F(),_())}}),r(le,[$],function(e){function t(t){return t._eventDispatcher||(t._eventDispatcher=new e({scope:t,toggleEvent:function(n,r){e.isNative(n)&&t.toggleNativeEvent&&t.toggleNativeEvent(n,r)}})),t._eventDispatcher}return{fire:function(e,n,r){var i=this;if(i.removed&&\"remove\"!==e)return n;if(n=t(i).fire(e,n,r),r!==!1&&i.parent)for(var o=i.parent();o&&!n.isPropagationStopped();)o.fire(e,n,!1),o=o.parent();return n},on:function(e,n,r){return t(this).on(e,n,r)},off:function(e,n){return t(this).off(e,n)},once:function(e,n){return t(this).once(e,n)},hasEventListeners:function(e){return t(this).has(e)}}}),r(ce,[le,y,d],function(e,t,n){function r(e,t){return\"selectionchange\"==t?e.getDoc():!e.inline&&/^mouse|click|contextmenu|drop|dragover|dragend/.test(t)?e.getDoc().documentElement:e.settings.event_root?(e.eventRoot||(e.eventRoot=o.select(e.settings.event_root)[0]),e.eventRoot):e.getBody()}function i(e,t){var n=r(e,t),i;if(e.delegates||(e.delegates={}),!e.delegates[t])if(e.settings.event_root){if(a||(a={},e.editorManager.on(\"removeEditor\",function(){var t;if(!e.editorManager.activeEditor&&a){for(t in a)e.dom.unbind(r(e,t));a=null}})),a[t])return;i=function(n){for(var r=n.target,i=e.editorManager.editors,a=i.length;a--;){var s=i[a].getBody();(s===r||o.isChildOf(r,s))&&(i[a].hidden||i[a].fire(t,n))}},a[t]=i,o.bind(n,t,i)}else i=function(n){e.hidden||e.fire(t,n)},o.bind(n,t,i),e.delegates[t]=i}var o=t.DOM,a,s={bindPendingEventDelegates:function(){var e=this;n.each(e._pendingNativeEvents,function(t){i(e,t)})},toggleNativeEvent:function(e,t){var n=this;n.settings.readonly||\"focus\"!=e&&\"blur\"!=e&&(t?n.initialized?i(n,e):n._pendingNativeEvents?n._pendingNativeEvents.push(e):n._pendingNativeEvents=[e]:n.initialized&&(n.dom.unbind(r(n,e),e,n.delegates[e]),delete n.delegates[e]))},unbindAllNativeEvents:function(){var e=this,t;if(e.delegates){for(t in e.delegates)e.dom.unbind(r(e,t),t,e.delegates[t]);delete e.delegates}e.inline||(e.getBody().onload=null,e.dom.unbind(e.getWin()),e.dom.unbind(e.getDoc())),e.dom.unbind(e.getBody()),e.dom.unbind(e.getContainer())}};return s=n.extend({},e,s)}),r(ue,[d,u],function(e,t){var n=e.each,r=e.explode,i={f9:120,f10:121,f11:122},o=e.makeMap(\"alt,ctrl,shift,meta,access\");return function(a){function s(e,s,l,c){var u,d,f;f={func:l,scope:c||a,desc:a.translate(s)},n(r(e,\"+\"),function(e){e in o?f[e]=!0:/^[0-9]{2,}$/.test(e)?f.keyCode=parseInt(e,10):(f.charCode=e.charCodeAt(0),f.keyCode=i[e]||e.toUpperCase().charCodeAt(0))}),u=[f.keyCode];for(d in o)f[d]?u.push(d):f[d]=!1;return f.id=u.join(\",\"),f.access&&(f.alt=!0,t.mac?f.ctrl=!0:f.shift=!0),f.meta&&(t.mac?f.meta=!0:(f.ctrl=!0,f.meta=!1)),f}var l=this,c={};a.on(\"keyup keypress keydown\",function(e){(e.altKey||e.ctrlKey||e.metaKey)&&!e.isDefaultPrevented()&&n(c,function(t){return t.ctrl==e.ctrlKey&&t.meta==e.metaKey&&t.alt==e.altKey&&t.shift==e.shiftKey&&(e.keyCode==t.keyCode||e.charCode&&e.charCode==t.charCode)?(e.preventDefault(),\"keydown\"==e.type&&t.func.call(t.scope),!0):void 0})}),l.add=function(t,i,o,l){var u;return u=o,\"string\"==typeof o?o=function(){a.execCommand(u,!1,null)}:e.isArray(u)&&(o=function(){a.execCommand(u[0],u[1],u[2])}),n(r(t.toLowerCase()),function(e){var t=s(e,i,o,l);c[t.id]=t}),!0},l.remove=function(e){var t=s(e);return c[t.id]?(delete c[t.id],!0):!1}}}),r(de,[y,f,C,w,_,R,T,H,O,I,F,z,W,V,b,l,ae,E,k,se,u,d,ce,ue],function(e,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v,y,b,C,x,w,_,E){function N(e,t,i){var o=this,a,s;a=o.documentBaseUrl=i.documentBaseURL,s=i.baseURI,o.settings=t=R({id:e,theme:\"modern\",delta_width:0,delta_height:0,popup_css:\"\",plugins:\"\",document_base_url:a,add_form_submit_trigger:!0,submit_patch:!0,add_unload_trigger:!0,convert_urls:!0,relative_urls:!0,remove_script_host:!0,object_resizing:!0,doctype:\"\",visual:!0,font_size_style_values:\"xx-small,x-small,small,medium,large,x-large,xx-large\",font_size_legacy_values:\"xx-small,small,medium,large,x-large,xx-large,300%\",forced_root_block:\"p\",hidden_input:!0,padd_empty_editor:!0,render_ui:!0,indentation:\"30px\",inline_styles:!0,convert_fonts_to_spans:!0,indent:\"simple\",indent_before:\"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist\",indent_after:\"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist\",validate:!0,entity_encoding:\"named\",url_converter:o.convertURL,url_converter_scope:o,ie7_compat:!0},t),r.language=t.language||\"en\",r.languageLoad=t.language_load,r.baseURL=i.baseURL,o.id=t.id=e,o.isNotDirty=!0,o.plugins={},o.documentBaseURI=new h(t.document_base_url||a,{base_uri:s}),o.baseURI=s,o.contentCSS=[],o.contentStyles=[],o.shortcuts=new E(o),o.loadedCSS={},o.editorCommands=new p(o),t.target&&(o.targetElm=t.target),o.suffix=i.suffix,o.editorManager=i,o.inline=t.inline,t.cache_suffix&&(x.cacheSuffix=t.cache_suffix.replace(/^[\\?\\&]+/,\"\")),i.fire(\"SetupEditor\",o),o.execCallback(\"setup\",o),o.$=n.overrideDefaults(function(){return{context:o.inline?o.getBody():o.getDoc(),element:o.getBody()}})}var k=e.DOM,S=r.ThemeManager,T=r.PluginManager,R=w.extend,A=w.each,B=w.explode,D=w.inArray,M=w.trim,H=w.resolve,L=g.Event,P=x.gecko,O=x.ie;return N.prototype={render:function(){function e(){k.unbind(window,\"ready\",e),n.render()}function t(){var e=m.ScriptLoader;if(r.language&&\"en\"!=r.language&&!r.language_url&&(r.language_url=n.editorManager.baseURL+\"/langs/\"+r.language+\".js\"),r.language_url&&e.add(r.language_url),r.theme&&\"function\"!=typeof r.theme&&\"-\"!=r.theme.charAt(0)&&!S.urls[r.theme]){var t=r.theme_url;t=t?n.documentBaseURI.toAbsolute(t):\"themes/\"+r.theme+\"/theme\"+o+\".js\",S.load(r.theme,t)}w.isArray(r.plugins)&&(r.plugins=r.plugins.join(\" \")),A(r.external_plugins,function(e,t){T.load(t,e),r.plugins+=\" \"+t}),A(r.plugins.split(/[ ,]/),function(e){if(e=M(e),e&&!T.urls[e])if(\"-\"==e.charAt(0)){e=e.substr(1,e.length);var t=T.dependencies(e);A(t,function(e){var t={prefix:\"plugins/\",resource:e,suffix:\"/plugin\"+o+\".js\"};e=T.createUrl(t,e),T.load(e.resource,e)})}else T.load(e,{prefix:\"plugins/\",resource:e,suffix:\"/plugin\"+o+\".js\"})}),e.loadQueue(function(){n.removed||n.init()})}var n=this,r=n.settings,i=n.id,o=n.suffix;if(!L.domLoaded)return void k.bind(window,\"ready\",e);if(n.getElement()&&x.contentEditable){r.inline?n.inline=!0:(n.orgVisibility=n.getElement().style.visibility,n.getElement().style.visibility=\"hidden\");var a=n.getElement().form||k.getParent(i,\"form\");a&&(n.formElement=a,r.hidden_input&&!/TEXTAREA|INPUT/i.test(n.getElement().nodeName)&&(k.insertAfter(k.create(\"input\",{type:\"hidden\",name:i}),i),n.hasHiddenInput=!0),n.formEventDelegate=function(e){n.fire(e.type,e)},k.bind(a,\"submit reset\",n.formEventDelegate),n.on(\"reset\",function(){n.setContent(n.startContent,{format:\"raw\"})}),!r.submit_patch||a.submit.nodeType||a.submit.length||a._mceOldSubmit||(a._mceOldSubmit=a.submit,a.submit=function(){return n.editorManager.triggerSave(),n.isNotDirty=!0,a._mceOldSubmit(a)})),n.windowManager=new v(n),\"xml\"==r.encoding&&n.on(\"GetContent\",function(e){e.save&&(e.content=k.encode(e.content))}),r.add_form_submit_trigger&&n.on(\"submit\",function(){n.initialized&&n.save()}),r.add_unload_trigger&&(n._beforeUnload=function(){!n.initialized||n.destroyed||n.isHidden()||n.save({format:\"raw\",no_events:!0,set_dirty:!1})},n.editorManager.on(\"BeforeUnload\",n._beforeUnload)),t()}},init:function(){function e(n){var r=T.get(n),i,o;i=T.urls[n]||t.documentBaseUrl.replace(/\\/$/,\"\"),n=M(n),r&&-1===D(m,n)&&(A(T.dependencies(n),function(t){e(t)}),o=new r(t,i,t.$),t.plugins[n]=o,o.init&&(o.init(t,i),m.push(n)))}var t=this,n=t.settings,r=t.getElement(),i,o,a,s,l,c,u,d,f,p,h,m=[];if(this.editorManager.i18n.setCode(n.language),t.rtl=this.editorManager.i18n.rtl,t.editorManager.add(t),n.aria_label=n.aria_label||k.getAttrib(r,\"aria-label\",t.getLang(\"aria.rich_text_area\")),n.theme&&(\"function\"!=typeof n.theme?(n.theme=n.theme.replace(/-/,\"\"),c=S.get(n.theme),t.theme=new c(t,S.urls[n.theme]),t.theme.init&&t.theme.init(t,S.urls[n.theme]||t.documentBaseUrl.replace(/\\/$/,\"\"),t.$)):t.theme=n.theme),A(n.plugins.replace(/\\-/g,\"\").split(/[ ,]/),e),n.render_ui&&t.theme&&(t.orgDisplay=r.style.display,\"function\"!=typeof n.theme?(i=n.width||r.style.width||r.offsetWidth,o=n.height||r.style.height||r.offsetHeight,a=n.min_height||100,p=/^[0-9\\.]+(|px)$/i,p.test(\"\"+i)&&(i=Math.max(parseInt(i,10),100)),p.test(\"\"+o)&&(o=Math.max(parseInt(o,10),a)),l=t.theme.renderUI({targetNode:r,width:i,height:o,deltaWidth:n.delta_width,deltaHeight:n.delta_height}),n.content_editable||(o=(l.iframeHeight||o)+(\"number\"==typeof o?l.deltaHeight||0:\"\"),a>o&&(o=a))):(l=n.theme(t,r),l.editorContainer.nodeType&&(l.editorContainer=l.editorContainer.id=l.editorContainer.id||t.id+\"_parent\"),l.iframeContainer.nodeType&&(l.iframeContainer=l.iframeContainer.id=l.iframeContainer.id||t.id+\"_iframecontainer\"),o=l.iframeHeight||r.offsetHeight),t.editorContainer=l.editorContainer),n.content_css&&A(B(n.content_css),function(e){t.contentCSS.push(t.documentBaseURI.toAbsolute(e))}),n.content_style&&t.contentStyles.push(n.content_style),n.content_editable)return r=s=l=null,t.initContentBody();for(t.iframeHTML=n.doctype+\"\",n.document_base_url!=t.documentBaseUrl&&(t.iframeHTML+=' '),!x.caretAfter&&n.ie7_compat&&(t.iframeHTML+=' '),t.iframeHTML+=' ',h=0;h ',t.loadedCSS[g]=!0}d=n.body_id||\"tinymce\",-1!=d.indexOf(\"=\")&&(d=t.getParam(\"body_id\",\"\",\"hash\"),d=d[t.id]||d),f=n.body_class||\"\",-1!=f.indexOf(\"=\")&&(f=t.getParam(\"body_class\",\"\",\"hash\"),f=f[t.id]||\"\"),n.content_security_policy&&(t.iframeHTML+=' '),t.iframeHTML+=' ';var v='javascript:(function(){document.open();document.domain=\"'+document.domain+'\";var ed = window.parent.tinymce.get(\"'+t.id+'\");document.write(ed.iframeHTML);document.close();ed.initContentBody(true);})()';document.domain!=location.hostname&&(u=v);var y=k.create(\"iframe\",{id:t.id+\"_ifr\",frameBorder:\"0\",allowTransparency:\"true\",title:t.editorManager.translate(\"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help\"),style:{width:\"100%\",height:o,display:\"block\"}});if(y.onload=function(){y.onload=null,t.fire(\"load\")},k.setAttrib(y,\"src\",u||'javascript:\"\"'),t.contentAreaContainer=l.iframeContainer,t.iframeElement=y,s=k.add(l.iframeContainer,y),O)try{t.getDoc()}catch(b){s.src=u=v}l.editorContainer&&(k.get(l.editorContainer).style.display=t.orgDisplay,t.hidden=k.isHidden(l.editorContainer)),t.getElement().style.display=\"none\",k.setAttrib(t.id,\"aria-hidden\",!0),u||t.initContentBody(),r=s=l=null},initContentBody:function(t){var n=this,r=n.settings,s=n.getElement(),p=n.getDoc(),h,m;r.inline||(n.getElement().style.visibility=n.orgVisibility),t||r.content_editable||(p.open(),p.write(n.iframeHTML),p.close()),r.content_editable&&(n.on(\"remove\",function(){var e=this.getBody();k.removeClass(e,\"mce-content-body\"),k.removeClass(e,\"mce-edit-focus\"),k.setAttrib(e,\"contentEditable\",null)}),k.addClass(s,\"mce-content-body\"),n.contentDocument=p=r.content_document||document,n.contentWindow=r.content_window||window,n.bodyElement=s,r.content_document=r.content_window=null,r.root_name=s.nodeName.toLowerCase()),h=n.getBody(),h.disabled=!0,r.readonly||(n.inline&&\"static\"==k.getStyle(h,\"position\",!0)&&(h.style.position=\"relative\"),h.contentEditable=n.getParam(\"content_editable_state\",!0)),h.disabled=!1,n.schema=new y(r),n.dom=new e(p,{keep_values:!0,url_converter:n.convertURL,url_converter_scope:n,hex_colors:r.force_hex_style_colors,class_filter:r.class_filter,update_styles:!0,root_element:n.inline?n.getBody():null,collect:r.content_editable,schema:n.schema,onSetAttrib:function(e){n.fire(\"SetAttrib\",e)}}),n.parser=new b(r,n.schema),n.parser.addAttributeFilter(\"src,href,style,tabindex\",function(e,t){for(var r=e.length,i,o=n.dom,a,s;r--;)i=e[r],a=i.attr(t),s=\"data-mce-\"+t,i.attributes.map[s]||(\"style\"===t?(a=o.serializeStyle(o.parseStyle(a),i.name),a.length||(a=null),i.attr(s,a),i.attr(t,a)):\"tabindex\"===t?(i.attr(s,a),i.attr(t,null)):i.attr(s,n.convertURL(a,t,i.name)))}),n.parser.addNodeFilter(\"script\",function(e){for(var t=e.length,n;t--;)n=e[t],n.attr(\"type\",\"mce-\"+(n.attr(\"type\")||\"no/type\"))}),n.parser.addNodeFilter(\"#cdata\",function(e){for(var t=e.length,n;t--;)n=e[t],n.type=8,n.name=\"#comment\",n.value=\"[CDATA[\"+n.value+\"]]\"}),n.parser.addNodeFilter(\"p,h1,h2,h3,h4,h5,h6,div\",function(e){for(var t=e.length,r,i=n.schema.getNonEmptyElements();t--;)r=e[t],r.isEmpty(i)&&(r.append(new o(\"br\",1)).shortEnded=!0)}),n.serializer=new a(r,n),n.selection=new l(n.dom,n.getWin(),n.serializer,n),n.formatter=new c(n),n.undoManager=new u(n),n.forceBlocks=new f(n),n.enterKey=new d(n),n._nodeChangeDispatcher=new i(n),n.fire(\"PreInit\"),r.browser_spellcheck||r.gecko_spellcheck||(p.body.spellcheck=!1,k.setAttrib(h,\"spellcheck\",\"false\")),n.fire(\"PostRender\"),n.quirks=new C(n),r.directionality&&(h.dir=r.directionality),r.nowrap&&(h.style.whiteSpace=\"nowrap\"),r.protect&&n.on(\"BeforeSetContent\",function(e){A(r.protect,function(t){e.content=e.content.replace(t,function(e){return\"\"})})}),n.on(\"SetContent\",function(){n.addVisual(n.getBody())}),r.padd_empty_editor&&n.on(\"PostProcess\",function(e){e.content=e.content.replace(/^(]*>( | |\\s|\\u00a0|)<\\/p>[\\r\\n]*| [\\r\\n]*)$/,\"\")}),n.load({initial:!0,format:\"html\"}),n.startContent=n.getContent({format:\"raw\"}),n.initialized=!0,n.bindPendingEventDelegates(),n.fire(\"init\"),n.focus(!0),n.nodeChanged({initial:!0}),n.execCallback(\"init_instance_callback\",n),n.contentStyles.length>0&&(m=\"\",A(n.contentStyles,function(e){m+=e+\"\\r\\n\"}),n.dom.addStyle(m)),A(n.contentCSS,function(e){n.loadedCSS[e]||(n.dom.loadCSS(e),n.loadedCSS[e]=!0)}),r.auto_focus&&setTimeout(function(){var e;e=r.auto_focus===!0?n:n.editorManager.get(r.auto_focus),e.destroyed||e.focus()},100),s=p=h=null},focus:function(e){var t=this,n=t.selection,r=t.settings.content_editable,i,o,a=t.getDoc(),s;if(!e){if(i=n.getRng(),i.item&&(o=i.item(0)),t._refreshContentEditable(),r||(x.opera||t.getBody().focus(),t.getWin().focus()),P||r){if(s=t.getBody(),s.setActive)try{s.setActive()}catch(l){s.focus()}else s.focus();r&&n.normalize()}o&&o.ownerDocument==a&&(i=a.body.createControlRange(),i.addElement(o),i.select())}t.editorManager.setActive(t)},execCallback:function(e){var t=this,n=t.settings[e],r;if(n)return t.callbackLookup&&(r=t.callbackLookup[e])&&(n=r.func,r=r.scope),\"string\"==typeof n&&(r=n.replace(/\\.\\w+$/,\"\"),r=r?H(r):0,n=H(n),t.callbackLookup=t.callbackLookup||{},t.callbackLookup[e]={func:n,scope:r}),n.apply(r||t,Array.prototype.slice.call(arguments,1))},translate:function(e){var t=this.settings.language||\"en\",n=this.editorManager.i18n;return e?n.data[t+\".\"+e]||e.replace(/\\{\\#([^\\}]+)\\}/g,function(e,r){return n.data[t+\".\"+r]||\"{#\"+r+\"}\"}):\"\"},getLang:function(e,n){return this.editorManager.i18n.data[(this.settings.language||\"en\")+\".\"+e]||(n!==t?n:\"{#\"+e+\"}\")},getParam:function(e,t,n){var r=e in this.settings?this.settings[e]:t,i;return\"hash\"===n?(i={},\"string\"==typeof r?A(r.split(r.indexOf(\"=\")>0?/[;,](?![^=;,]*(?:[;,]|$))/:\",\"),function(e){e=e.split(\"=\"),e.length>1?i[M(e[0])]=M(e[1]):i[M(e[0])]=M(e)}):i=r,i):r},nodeChanged:function(e){this._nodeChangeDispatcher.nodeChanged(e);\n\n},addButton:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),t.text||t.icon||(t.icon=e),n.buttons=n.buttons||{},t.tooltip=t.tooltip||t.title,n.buttons[e]=t},addMenuItem:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),n.menuItems=n.menuItems||{},n.menuItems[e]=t},addCommand:function(e,t,n){this.editorCommands.addCommand(e,t,n)},addQueryStateHandler:function(e,t,n){this.editorCommands.addQueryStateHandler(e,t,n)},addQueryValueHandler:function(e,t,n){this.editorCommands.addQueryValueHandler(e,t,n)},addShortcut:function(e,t,n,r){this.shortcuts.add(e,t,n,r)},execCommand:function(e,t,n,r){return this.editorCommands.execCommand(e,t,n,r)},queryCommandState:function(e){return this.editorCommands.queryCommandState(e)},queryCommandValue:function(e){return this.editorCommands.queryCommandValue(e)},queryCommandSupported:function(e){return this.editorCommands.queryCommandSupported(e)},show:function(){var e=this;e.hidden&&(e.hidden=!1,e.inline?e.getBody().contentEditable=!0:(k.show(e.getContainer()),k.hide(e.id)),e.load(),e.fire(\"show\"))},hide:function(){var e=this,t=e.getDoc();e.hidden||(O&&t&&!e.inline&&t.execCommand(\"SelectAll\"),e.save(),e.inline?(e.getBody().contentEditable=!1,e==e.editorManager.focusedEditor&&(e.editorManager.focusedEditor=null)):(k.hide(e.getContainer()),k.setStyle(e.id,\"display\",e.orgDisplay)),e.hidden=!0,e.fire(\"hide\"))},isHidden:function(){return!!this.hidden},setProgressState:function(e,t){this.fire(\"ProgressState\",{state:e,time:t})},load:function(e){var n=this,r=n.getElement(),i;return r?(e=e||{},e.load=!0,i=n.setContent(r.value!==t?r.value:r.innerHTML,e),e.element=r,e.no_events||n.fire(\"LoadContent\",e),e.element=r=null,i):void 0},save:function(e){var t=this,n=t.getElement(),r,i;if(n&&t.initialized)return e=e||{},e.save=!0,e.element=n,r=e.content=t.getContent(e),e.no_events||t.fire(\"SaveContent\",e),r=e.content,/TEXTAREA|INPUT/i.test(n.nodeName)?n.value=r:(t.inline||(n.innerHTML=r),(i=k.getParent(t.id,\"form\"))&&A(i.elements,function(e){return e.name==t.id?(e.value=r,!1):void 0})),e.element=n=null,e.set_dirty!==!1&&(t.isNotDirty=!0),r},setContent:function(e,t){var n=this,r=n.getBody(),i;return t=t||{},t.format=t.format||\"html\",t.set=!0,t.content=e,t.no_events||n.fire(\"BeforeSetContent\",t),e=t.content,0===e.length||/^\\s+$/.test(e)?(i=n.settings.forced_root_block,i&&n.schema.isValidChild(r.nodeName.toLowerCase(),i.toLowerCase())?(e=O&&11>O?\"\":' ',e=n.dom.createHTML(i,n.settings.forced_root_block_attrs,e)):O||(e=' '),n.dom.setHTML(r,e),n.fire(\"SetContent\",t)):(\"raw\"!==t.format&&(e=new s({},n.schema).serialize(n.parser.parse(e,{isRootContent:!0}))),t.content=M(e),n.dom.setHTML(r,t.content),t.no_events||n.fire(\"SetContent\",t)),t.content},getContent:function(e){var t=this,n,r=t.getBody();return e=e||{},e.format=e.format||\"html\",e.get=!0,e.getInner=!0,e.no_events||t.fire(\"BeforeGetContent\",e),n=\"raw\"==e.format?r.innerHTML:\"text\"==e.format?r.innerText||r.textContent:t.serializer.serialize(r,e),\"text\"!=e.format?e.content=M(n):e.content=n,e.no_events||t.fire(\"GetContent\",e),e.content},insertContent:function(e,t){t&&(e=R({content:e},t)),this.execCommand(\"mceInsertContent\",!1,e)},isDirty:function(){return!this.isNotDirty},getContainer:function(){var e=this;return e.container||(e.container=k.get(e.editorContainer||e.id+\"_parent\")),e.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return this.targetElm||(this.targetElm=k.get(this.id)),this.targetElm},getWin:function(){var e=this,t;return e.contentWindow||(t=e.iframeElement,t&&(e.contentWindow=t.contentWindow)),e.contentWindow},getDoc:function(){var e=this,t;return e.contentDocument||(t=e.getWin(),t&&(e.contentDocument=t.document)),e.contentDocument},getBody:function(){return this.bodyElement||this.getDoc().body},convertURL:function(e,t,n){var r=this,i=r.settings;return i.urlconverter_callback?r.execCallback(\"urlconverter_callback\",e,n,!0,t):!i.convert_urls||n&&\"LINK\"==n.nodeName||0===e.indexOf(\"file:\")||0===e.length?e:i.relative_urls?r.documentBaseURI.toRelative(e):e=r.documentBaseURI.toAbsolute(e,i.remove_script_host)},addVisual:function(e){var n=this,r=n.settings,i=n.dom,o;e=e||n.getBody(),n.hasVisual===t&&(n.hasVisual=r.visual),A(i.select(\"table,a\",e),function(e){var t;switch(e.nodeName){case\"TABLE\":return o=r.visual_table_class||\"mce-item-table\",t=i.getAttrib(e,\"border\"),void(t&&\"0\"!=t||!n.hasVisual?i.removeClass(e,o):i.addClass(e,o));case\"A\":return void(i.getAttrib(e,\"href\",!1)||(t=i.getAttrib(e,\"name\")||e.id,o=r.visual_anchor_class||\"mce-item-anchor\",t&&n.hasVisual?i.addClass(e,o):i.removeClass(e,o)))}}),n.fire(\"VisualAid\",{element:e,hasVisual:n.hasVisual})},remove:function(){var e=this;e.removed||(e.save(),e.removed=1,e.unbindAllNativeEvents(),e.hasHiddenInput&&k.remove(e.getElement().nextSibling),e.inline||(O&&10>O&&e.getDoc().execCommand(\"SelectAll\",!1,null),k.setStyle(e.id,\"display\",e.orgDisplay),e.getBody().onload=null),e.fire(\"remove\"),e.editorManager.remove(e),k.remove(e.getContainer()),e.destroy())},destroy:function(e){var t=this,n;if(!t.destroyed){if(!e&&!t.removed)return void t.remove();e||(t.editorManager.off(\"beforeunload\",t._beforeUnload),t.theme&&t.theme.destroy&&t.theme.destroy(),t.selection.destroy(),t.dom.destroy()),n=t.formElement,n&&(n._mceOldSubmit&&(n.submit=n._mceOldSubmit,n._mceOldSubmit=null),k.unbind(n,\"submit reset\",t.formEventDelegate)),t.contentAreaContainer=t.formElement=t.container=t.editorContainer=null,t.bodyElement=t.contentDocument=t.contentWindow=null,t.iframeElement=t.targetElm=null,t.selection&&(t.selection=t.selection.win=t.selection.dom=t.selection.dom.doc=null),t.destroyed=1}},_refreshContentEditable:function(){var e=this,t,n;e._isHidden()&&(t=e.getBody(),n=t.parentNode,n.removeChild(t),n.appendChild(t),t.focus())},_isHidden:function(){var e;return P?(e=this.selection.getSel(),!e||!e.rangeCount||0===e.rangeCount):0}},R(N.prototype,_),N}),r(fe,[],function(){var e={},t=\"en\";return{setCode:function(e){e&&(t=e,this.rtl=this.data[e]?\"rtl\"===this.data[e]._dir:!1)},getCode:function(){return t},rtl:!1,add:function(t,n){var r=e[t];r||(e[t]=r={});for(var i in n)r[i]=n[i];this.setCode(t)},translate:function(n){var r;if(r=e[t],r||(r={}),\"undefined\"==typeof n)return n;if(\"string\"!=typeof n&&n.raw)return n.raw;if(n.push){var i=n.slice(1);n=(r[n[0]]||n[0]).replace(/\\{([0-9]+)\\}/g,function(e,t){return i[t]})}return(r[n]||n).replace(/{context:\\w+}$/,\"\")},data:e}}),r(pe,[y,u],function(e,t){function n(e){function s(){try{return document.activeElement}catch(e){return document.body}}function l(e,t){if(t&&t.startContainer){if(!e.isChildOf(t.startContainer,e.getRoot())||!e.isChildOf(t.endContainer,e.getRoot()))return;return{startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset}}return t}function c(e,t){var n;return t.startContainer?(n=e.getDoc().createRange(),n.setStart(t.startContainer,t.startOffset),n.setEnd(t.endContainer,t.endOffset)):n=t,n}function u(e){return!!a.getParent(e,n.isEditorUIElement)}function d(n){var d=n.editor;d.on(\"init\",function(){(d.inline||t.ie)&&(\"onbeforedeactivate\"in document&&t.ie<9?d.dom.bind(d.getBody(),\"beforedeactivate\",function(e){if(e.target==d.getBody())try{d.lastRng=d.selection.getRng()}catch(t){}}):d.on(\"nodechange mouseup keyup\",function(e){var t=s();\"nodechange\"==e.type&&e.selectionChange||(t&&t.id==d.id+\"_ifr\"&&(t=d.getBody()),d.dom.isChildOf(t,d.getBody())&&(d.lastRng=d.selection.getRng()))}),t.webkit&&!r&&(r=function(){var t=e.activeEditor;if(t&&t.selection){var n=t.selection.getRng();n&&!n.collapsed&&(d.lastRng=n)}},a.bind(document,\"selectionchange\",r)))}),d.on(\"setcontent\",function(){d.lastRng=null}),d.on(\"mousedown\",function(){d.selection.lastFocusBookmark=null}),d.on(\"focusin\",function(){var t=e.focusedEditor;d.selection.lastFocusBookmark&&(d.selection.setRng(c(d,d.selection.lastFocusBookmark)),d.selection.lastFocusBookmark=null),t!=d&&(t&&t.fire(\"blur\",{focusedEditor:d}),e.setActive(d),e.focusedEditor=d,d.fire(\"focus\",{blurredEditor:t}),d.focus(!0)),d.lastRng=null}),d.on(\"focusout\",function(){window.setTimeout(function(){var t=e.focusedEditor;u(s())||t!=d||(d.fire(\"blur\",{focusedEditor:null}),e.focusedEditor=null,d.selection&&(d.selection.lastFocusBookmark=null))},0)}),i||(i=function(t){var n=e.activeEditor;n&&t.target.ownerDocument==document&&(n.selection&&t.target!=n.getBody()&&(n.selection.lastFocusBookmark=l(n.dom,n.lastRng)),t.target==document.body||u(t.target)||e.focusedEditor!=n||(n.fire(\"blur\",{focusedEditor:null}),e.focusedEditor=null))},a.bind(document,\"focusin\",i)),d.inline&&!o&&(o=function(t){var n=e.activeEditor;if(n.inline&&!n.dom.isChildOf(t.target,n.getBody())){var r=n.selection.getRng();r.collapsed||(n.lastRng=r)}},a.bind(document,\"mouseup\",o))}function f(t){e.focusedEditor==t.editor&&(e.focusedEditor=null),e.activeEditor||(a.unbind(document,\"selectionchange\",r),a.unbind(document,\"focusin\",i),a.unbind(document,\"mouseup\",o),r=i=o=null)}e.on(\"AddEditor\",d),e.on(\"RemoveEditor\",f)}var r,i,o,a=e.DOM;return n.isEditorUIElement=function(e){return-1!==e.className.toString().indexOf(\"mce-\")},n}),r(he,[de,f,y,V,u,d,le,fe,pe],function(e,t,n,r,i,o,a,s,l){function c(e){var t=v.editors,n;delete t[e.id];for(var r=0;r0&&p(f(e),function(e){var n;(n=d.get(e))?r(e,t,n):p(document.forms,function(n){p(n.elements,function(n){n.name===e&&(e=\"mce_editor_\"+m++,d.setAttrib(n,\"id\",e),r(e,t,n))})})});break;case\"textareas\":case\"specific_textareas\":p(d.select(\"textarea\"),function(e){t.editor_deselector&&o(e,t.editor_deselector)||(!t.editor_selector||o(e,t.editor_selector))&&r(n(e),t,e)})}t.oninit&&(e=s=0,p(l,function(t){s++,t.initialized?e++:t.on(\"init\",function(){e++,e==s&&i(\"oninit\")}),e==s&&i(\"oninit\")}))}var s=this,l=[];s.settings=t,d.bind(window,\"ready\",a)},get:function(e){return arguments.length?e in this.editors?this.editors[e]:null:this.editors},add:function(e){var t=this,n=t.editors;return n[e.id]=e,n.push(e),t.activeEditor=e,t.fire(\"AddEditor\",{editor:e}),g||(g=function(){t.fire(\"BeforeUnload\")},d.bind(window,\"beforeunload\",g)),e},createEditor:function(t,n){return this.add(new e(t,n,this))},remove:function(e){var t=this,n,r=t.editors,i;{if(e)return\"string\"==typeof e?(e=e.selector||e,void p(d.select(e),function(e){i=r[e.id],i&&t.remove(i)})):(i=e,r[i.id]?(c(i)&&t.fire(\"RemoveEditor\",{editor:i}),r.length||d.unbind(window,\"beforeunload\",g),i.remove(),i):null);for(n=r.length-1;n>=0;n--)t.remove(r[n])}},execCommand:function(t,n,r){var i=this,o=i.get(r);switch(t){case\"mceAddEditor\":return i.get(r)||new e(r,i.settings,i).render(),!0;case\"mceRemoveEditor\":return o&&o.remove(),!0;case\"mceToggleEditor\":return o?(o.isHidden()?o.show():o.hide(),!0):(i.execCommand(\"mceAddEditor\",0,r),!0)}return i.activeEditor?i.activeEditor.execCommand(t,n,r):!1},triggerSave:function(){p(this.editors,function(e){e.save()})},addI18n:function(e,t){s.add(e,t)},translate:function(e){return s.translate(e)},setActive:function(e){var t=this.activeEditor;this.activeEditor!=e&&(t&&t.fire(\"deactivate\",{relatedTarget:e}),e.fire(\"activate\",{relatedTarget:t})),this.activeEditor=e}},h(v,a),v.setup(),window.tinymce=window.tinyMCE=v,v}),r(me,[he,d],function(e,t){var n=t.each,r=t.explode;e.on(\"AddEditor\",function(e){var t=e.editor;t.on(\"preInit\",function(){function e(e,t){n(t,function(t,n){t&&s.setStyle(e,n,t)}),s.rename(e,\"span\")}function i(e){s=t.dom,l.convert_fonts_to_spans&&n(s.select(\"font,u,strike\",e.node),function(e){o[e.nodeName.toLowerCase()](s,e)})}var o,a,s,l=t.settings;l.inline_styles&&(a=r(l.font_size_legacy_values),o={font:function(t,n){e(n,{backgroundColor:n.style.backgroundColor,color:n.color,fontFamily:n.face,fontSize:a[parseInt(n.size,10)-1]})},u:function(n,r){\"html4\"===t.settings.schema&&e(r,{textDecoration:\"underline\"})},strike:function(t,n){e(n,{textDecoration:\"line-through\"})}},t.on(\"PreProcess SetContent\",i))})})}),r(ge,[le,d],function(e,t){var n={send:function(e){function t(){!e.async||4==r.readyState||i++>1e4?(e.success&&1e4>i&&200==r.status?e.success.call(e.success_scope,\"\"+r.responseText,r,e):e.error&&e.error.call(e.error_scope,i>1e4?\"TIMED_OUT\":\"GENERAL\",r,e),r=null):setTimeout(t,10)}var r,i=0;if(e.scope=e.scope||this,e.success_scope=e.success_scope||e.scope,e.error_scope=e.error_scope||e.scope,e.async=e.async===!1?!1:!0,e.data=e.data||\"\",r=new XMLHttpRequest){if(r.overrideMimeType&&r.overrideMimeType(e.content_type),r.open(e.type||(e.data?\"POST\":\"GET\"),e.url,e.async),e.crossDomain&&(r.withCredentials=!0),e.content_type&&r.setRequestHeader(\"Content-Type\",e.content_type),r.setRequestHeader(\"X-Requested-With\",\"XMLHttpRequest\"),r=n.fire(\"beforeSend\",{xhr:r,settings:e}).xhr,r.send(e.data),!e.async)return t();setTimeout(t,10)}}};return t.extend(n,e),n}),r(ve,[],function(){function e(t,n){var r,i,o,a;if(n=n||'\"',null===t)return\"null\";if(o=typeof t,\"string\"==o)return i=\"\\bb\tt\\nn\\ff\\rr\\\"\\\"''\\\\\\\\\",n+t.replace(/([\\u0080-\\uFFFF\\x00-\\x1f\\\"\\'\\\\])/g,function(e,t){return'\"'===n&&\"'\"===e?e:(r=i.indexOf(t),r+1?\"\\\\\"+i.charAt(r+1):(e=t.charCodeAt().toString(16),\"\\\\u\"+\"0000\".substring(e.length)+e))})+n;if(\"object\"==o){if(t.hasOwnProperty&&\"[object Array]\"===Object.prototype.toString.call(t)){for(r=0,i=\"[\";r0?\",\":\"\")+e(t[r],n);return i+\"]\"}i=\"{\";for(a in t)t.hasOwnProperty(a)&&(i+=\"function\"!=typeof t[a]?(i.length>1?\",\"+n:n)+a+n+\":\"+e(t[a],n):\"\");return i+\"}\"}return\"\"+t}return{serialize:e,parse:function(e){try{return window[String.fromCharCode(101)+\"val\"](\"(\"+e+\")\")}catch(t){}}}}),r(ye,[ve,ge,d],function(e,t,n){function r(e){this.settings=i({},e),this.count=0}var i=n.extend;return r.sendRPC=function(e){return(new r).send(e)},r.prototype={send:function(n){var r=n.error,o=n.success;n=i(this.settings,n),n.success=function(t,i){t=e.parse(t),\"undefined\"==typeof t&&(t={error:\"JSON Parse error.\"}),t.error?r.call(n.error_scope||n.scope,t.error,i):o.call(n.success_scope||n.scope,t.result)},n.error=function(e,t){r&&r.call(n.error_scope||n.scope,e,t)},n.data=e.serialize({id:n.id||\"c\"+this.count++,method:n.method,params:n.params}),n.content_type=\"application/json\",t.send(n)}},r}),r(be,[y],function(e){return{callbacks:{},count:0,send:function(n){var r=this,i=e.DOM,o=n.count!==t?n.count:r.count,a=\"tinymce_jsonp_\"+o;r.callbacks[o]=function(e){i.remove(a),delete r.callbacks[o],n.callback(e)},i.add(i.doc.body,\"script\",{id:a,src:n.url,type:\"text/javascript\"}),r.count++}}}),r(Ce,[],function(){function e(){s=[];for(var e in a)s.push(e);i.length=s.length}function n(){function n(e){var n,r;return r=e!==t?u+e:i.indexOf(\",\",u),-1===r||r>i.length?null:(n=i.substring(u,r),u=r+1,n)}var r,i,s,u=0;if(a={},c){o.load(l),i=o.getAttribute(l)||\"\";do{var d=n();if(null===d)break;if(r=n(parseInt(d,32)||0),null!==r){if(d=n(),null===d)break;s=n(parseInt(d,32)||0),r&&(a[r]=s)}}while(null!==r);e()}}function r(){var t,n=\"\";if(c){for(var r in a)t=a[r],n+=(n?\",\":\"\")+r.length.toString(32)+\",\"+r+\",\"+t.length.toString(32)+\",\"+t;o.setAttribute(l,n);try{o.save(l)}catch(i){}e()}}var i,o,a,s,l,c;try{if(window.localStorage)return localStorage}catch(u){}return l=\"tinymce\",o=document.documentElement,c=!!o.addBehavior,c&&o.addBehavior(\"#default#userData\"),i={key:function(e){return s[e]},getItem:function(e){return e in a?a[e]:null},setItem:function(e,t){a[e]=\"\"+t,r()},removeItem:function(e){delete a[e],r()},clear:function(){a={},r()}},n(),i}),r(xe,[y,l,b,C,d,u],function(e,t,n,r,i,o){var a=window.tinymce;return a.DOM=e.DOM,a.ScriptLoader=n.ScriptLoader,a.PluginManager=r.PluginManager,a.ThemeManager=r.ThemeManager,a.dom=a.dom||{},a.dom.Event=t.Event,i.each(i,function(e,t){a[t]=e}),i.each(\"isOpera isWebKit isIE isGecko isMac\".split(\" \"),function(e){a[e]=o[e.substr(2).toLowerCase()]}),{}}),r(we,[U,d],function(e,t){return e.extend({Defaults:{firstControlClass:\"first\",lastControlClass:\"last\"},init:function(e){this.settings=t.extend({},this.Defaults,e)},preRender:function(e){e.addClass(this.settings.containerClass,\"body\")},applyClasses:function(e){var t=this,n=t.settings,r,i,o;r=e.items().filter(\":visible\"),i=n.firstControlClass,o=n.lastControlClass,r.each(function(e){e.removeClass(i).removeClass(o),n.controlClass&&e.addClass(n.controlClass)}),r.eq(0).addClass(i),r.eq(-1).addClass(o)},renderHtml:function(e){var t=this,n=t.settings,r,i=\"\";return r=e.items(),r.eq(0).addClass(n.firstControlClass),r.eq(-1).addClass(n.lastControlClass),r.each(function(e){n.controlClass&&e.addClass(n.controlClass),i+=e.renderHtml()}),i},recalc:function(){},postRender:function(){}})}),r(_e,[we],function(e){return e.extend({Defaults:{containerClass:\"abs-layout\",controlClass:\"abs-layout-item\"},recalc:function(e){e.items().filter(\":visible\").each(function(e){var t=e.settings;e.layoutRect({x:t.x,y:t.y,w:t.w,h:t.h}),e.recalc&&e.recalc()})},renderHtml:function(e){return'
'+this._super(e)}})}),r(Ee,[Y,te],function(e,t){return e.extend({Mixins:[t],Defaults:{classes:\"widget tooltip tooltip-n\"},text:function(e){var t=this;return\"undefined\"!=typeof e?(t._value=e,t._rendered&&(t.getEl().lastChild.innerHTML=t.encode(e)),t):t._value},renderHtml:function(){var e=this,t=e.classPrefix;return'\"},repaint:function(){var e=this,t,n;t=e.getEl().style,n=e._layoutRect,t.left=n.x+\"px\",t.top=n.y+\"px\",t.zIndex=131070}})}),r(Ne,[Y,Ee],function(e,t){var n,r=e.extend({init:function(e){var t=this;t._super(e),e=t.settings,t.canFocus=!0,e.tooltip&&r.tooltips!==!1&&(t.on(\"mouseenter\",function(n){var r=t.tooltip().moveTo(-65535);if(n.control==t){var i=r.text(e.tooltip).show().testMoveRel(t.getEl(),[\"bc-tc\",\"bc-tl\",\"bc-tr\"]);r.toggleClass(\"tooltip-n\",\"bc-tc\"==i),r.toggleClass(\"tooltip-nw\",\"bc-tl\"==i),r.toggleClass(\"tooltip-ne\",\"bc-tr\"==i),r.moveRel(t.getEl(),i)}else r.hide()}),t.on(\"mouseleave mousedown click\",function(){t.tooltip().hide()})),t.aria(\"label\",e.ariaLabel||e.tooltip)},tooltip:function(){return n||(n=new t({type:\"tooltip\"}),n.renderTo()),n},active:function(e){var t=this,n;return e!==n&&(t.aria(\"pressed\",e),t.toggleClass(\"active\",e)),t._super(e)},disabled:function(e){var t=this,n;return e!==n&&(t.aria(\"disabled\",e),t.toggleClass(\"disabled\",e)),t._super(e)},postRender:function(){var e=this,t=e.settings;e._rendered=!0,e._super(),e.parent()||!t.width&&!t.height||(e.initLayoutRect(),e.repaint()),t.autofocus&&e.focus()},remove:function(){this._super(),n&&(n.remove(),n=null)}});return r}),r(ke,[Ne],function(e){return e.extend({Defaults:{classes:\"widget btn\",role:\"button\"},init:function(e){var t=this,n;t.on(\"click mousedown\",function(e){e.preventDefault()}),t._super(e),n=e.size,e.subtype&&t.addClass(e.subtype),n&&t.addClass(\"btn-\"+n)},icon:function(e){var t=this,n=t.classPrefix;if(\"undefined\"==typeof e)return t.settings.icon;if(t.settings.icon=e,e=e?n+\"ico \"+n+\"i-\"+t.settings.icon:\"\",t._rendered){var r=t.getEl().firstChild,i=r.getElementsByTagName(\"i\")[0];e?(i&&i==r.firstChild||(i=document.createElement(\"i\"),r.insertBefore(i,r.firstChild)),i.className=e):i&&r.removeChild(i),t.text(t._text)}return t},repaint:function(){var e=this.getEl().firstChild.style;e.width=e.height=\"100%\",this._super()},text:function(e){var t=this;if(t._rendered){var n=t.getEl().lastChild.lastChild;n&&(n.data=t.translate(e))}return t._super(e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon,i;return i=e.settings.image,i?(r=\"none\",\"string\"!=typeof i&&(i=window.getSelection?i[0]:i[1]),i=\" style=\\\"background-image: url('\"+i+\"')\\\"\"):i=\"\",r=e.settings.icon?n+\"ico \"+n+\"i-\"+r:\"\",''+(r?' \":\"\")+(e._text?(r?\"\\xa0\":\"\")+e.encode(e._text):\"\")+\"
\"}})}),r(Se,[J],function(e){return e.extend({Defaults:{defaultType:\"button\",role:\"group\"},renderHtml:function(){var e=this,t=e._layout;return e.addClass(\"btn-group\"),e.preRender(),t.preRender(e),''+(e.settings.html||\"\")+t.renderHtml(e)+\"
\"}})}),r(Te,[Ne],function(e){return e.extend({Defaults:{classes:\"checkbox\",role:\"checkbox\",checked:!1},init:function(e){var t=this;t._super(e),t.on(\"click mousedown\",function(e){e.preventDefault()}),t.on(\"click\",function(e){e.preventDefault(),t.disabled()||t.checked(!t.checked())}),t.checked(t.settings.checked)},checked:function(e){var t=this;return\"undefined\"!=typeof e?(e?t.addClass(\"checked\"):t.removeClass(\"checked\"),t._checked=e,t.aria(\"checked\",e),t):t._checked},value:function(e){return this.checked(e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix;return''+e.encode(e._text)+\"
\"}})}),r(Re,[Ne,G,K],function(e,t,n){return e.extend({init:function(e){var t=this;t._super(e),t.addClass(\"combobox\"),t.subinput=!0,t.ariaTarget=\"inp\",e=t.settings,e.menu=e.menu||e.values,e.menu&&(e.icon=\"caret\"),t.on(\"click\",function(n){for(var r=n.target,i=t.getEl();r&&r!=i;)r.id&&-1!=r.id.indexOf(\"-open\")&&(t.fire(\"action\"),e.menu&&(t.showMenu(),n.aria&&t.menu.items()[0].focus())),r=r.parentNode}),t.on(\"keydown\",function(e){\"INPUT\"==e.target.nodeName&&13==e.keyCode&&t.parents().reverse().each(function(n){return e.preventDefault(),t.fire(\"change\"),n.hasEventListeners(\"submit\")&&n.toJSON?(n.fire(\"submit\",{data:n.toJSON()}),!1):void 0})}),e.placeholder&&(t.addClass(\"placeholder\"),t.on(\"focusin\",function(){t._hasOnChange||(n.on(t.getEl(\"inp\"),\"change\",function(){t.fire(\"change\")}),t._hasOnChange=!0),t.hasClass(\"placeholder\")&&(t.getEl(\"inp\").value=\"\",t.removeClass(\"placeholder\"))}),t.on(\"focusout\",function(){0===t.value().length&&(t.getEl(\"inp\").value=e.placeholder,t.addClass(\"placeholder\"))}))},showMenu:function(){var e=this,n=e.settings,r;e.menu||(r=n.menu||[],r.length?r={type:\"menu\",items:r}:r.type=r.type||\"menu\",e.menu=t.create(r).parent(e).renderTo(e.getContainerElm()),e.fire(\"createmenu\"),e.menu.reflow(),e.menu.on(\"cancel\",function(t){t.control===e.menu&&e.focus()}),e.menu.on(\"show hide\",function(t){t.control.items().each(function(t){t.active(t.value()==e.value())})}).fire(\"show\"),e.menu.on(\"select\",function(t){e.value(t.control.value())}),e.on(\"focusin\",function(t){\"INPUT\"==t.target.tagName.toUpperCase()&&e.menu.hide()}),e.aria(\"expanded\",!0)),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),e.menu.moveRel(e.getEl(),e.isRtl()?[\"br-tr\",\"tr-br\"]:[\"bl-tl\",\"tl-bl\"])},value:function(e){var t=this;return\"undefined\"!=typeof e?(t._value=e,t.removeClass(\"placeholder\"),t._rendered&&(t.getEl(\"inp\").value=e),t):t._rendered?(e=t.getEl(\"inp\").value,e!=t.settings.placeholder?e:\"\"):t._value},disabled:function(e){var t=this;return t._rendered&&\"undefined\"!=typeof e&&(t.getEl(\"inp\").disabled=e),t._super(e)},focus:function(){this.getEl(\"inp\").focus()},repaint:function(){var e=this,t=e.getEl(),r=e.getEl(\"open\"),i=e.layoutRect(),o,a;o=r?i.w-n.getSize(r).width-10:i.w-10;var s=document;return s.all&&(!s.documentMode||s.documentMode<=8)&&(a=e.layoutRect().h-2+\"px\"),n.css(t.firstChild,{width:o,lineHeight:a}),e._super(),e},postRender:function(){var e=this;return n.on(this.getEl(\"inp\"),\"change\",function(){e.fire(\"change\")}),e._super()},remove:function(){n.off(this.getEl(\"inp\")),this._super()},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.classPrefix,i=n.value||n.placeholder||\"\",o,a,s=\"\",l=\"\";return\"spellcheck\"in n&&(l+=' spellcheck=\"'+n.spellcheck+'\"'),n.maxLength&&(l+=' maxlength=\"'+n.maxLength+'\"'),n.size&&(l+=' size=\"'+n.size+'\"'),n.subtype&&(l+=' type=\"'+n.subtype+'\"'),e.disabled()&&(l+=' disabled=\"disabled\"'),o=n.icon,o&&\"caret\"!=o&&(o=r+\"ico \"+r+\"i-\"+n.icon),a=e._text,(o||a)&&(s=''+(\"caret\"!=o?' ':' ')+(a?(o?\" \":\"\")+a:\"\")+\"
\",e.addClass(\"has-open\")),' \"+s+\"
\"}})}),r(Ae,[Re],function(e){return e.extend({init:function(e){var t=this;e.spellcheck=!1,e.onaction&&(e.icon=\"none\"),t._super(e),t.addClass(\"colorbox\"),t.on(\"change keyup postrender\",function(){t.repaintColor(t.value())})},repaintColor:function(e){var t=this.getEl().getElementsByTagName(\"i\")[0];if(t)try{t.style.background=e}catch(n){}},value:function(e){var t=this;return\"undefined\"!=typeof e&&t._rendered&&t.repaintColor(e),t._super(e)}})}),r(Be,[ke,re],function(e,t){return e.extend({showPanel:function(){var e=this,n=e.settings;if(e.active(!0),e.panel)e.panel.show();else{var r=n.panel;r.type&&(r={layout:\"grid\",items:r}),r.role=r.role||\"dialog\",r.popover=!0,r.autohide=!0,r.ariaRoot=!0,e.panel=new t(r).on(\"hide\",function(){e.active(!1)}).on(\"cancel\",function(t){t.stopPropagation(),e.focus(),e.hidePanel()}).parent(e).renderTo(e.getContainerElm()),e.panel.fire(\"show\"),e.panel.reflow()}e.panel.moveRel(e.getEl(),n.popoverAlign||(e.isRtl()?[\"bc-tr\",\"bc-tc\"]:[\"bc-tl\",\"bc-tc\"]))},hidePanel:function(){var e=this;e.panel&&e.panel.hide()},postRender:function(){var e=this;return e.aria(\"haspopup\",!0),e.on(\"click\",function(t){t.control===e&&(e.panel&&e.panel.visible()?e.hidePanel():(e.showPanel(),e.panel.focus(!!t.aria)))}),e._super()},remove:function(){return this.panel&&(this.panel.remove(),this.panel=null),this._super()}})}),r(De,[Be,y],function(e,t){var n=t.DOM;return e.extend({init:function(e){this._super(e),this.addClass(\"colorbutton\")},color:function(e){return e?(this._color=e,this.getEl(\"preview\").style.backgroundColor=e,this):this._color},resetColor:function(){return this._color=null,this.getEl(\"preview\").style.backgroundColor=null,this},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+\"ico \"+n+\"i-\"+e.settings.icon:\"\",i=e.settings.image?\" style=\\\"background-image: url('\"+e.settings.image+\"')\\\"\":\"\";return''+(r?' \":\"\")+' '+(e._text?(r?\" \":\"\")+e._text:\"\")+'
'},postRender:function(){var e=this,t=e.settings.onclick;return e.on(\"click\",function(r){r.aria&&\"down\"==r.aria.key||r.control!=e||n.getParent(r.target,\".\"+e.classPrefix+\"open\")||(r.stopImmediatePropagation(),t.call(e,r))}),delete e.settings.onclick,e._super()}})}),r(Me,[],function(){function e(e){function i(e,i,o){var a,s,l,c,u,d;return a=0,s=0,l=0,e/=255,i/=255,o/=255,u=t(e,t(i,o)),d=n(e,n(i,o)),u==d?(l=u,{h:0,s:0,v:100*l}):(c=e==u?i-o:o==u?e-i:o-e,a=e==u?3:o==u?1:5,a=60*(a-c/(d-u)),s=(d-u)/d,l=d,{h:r(a),s:r(100*s),v:r(100*l)})}function o(e,i,o){var a,s,l,c;if(e=(parseInt(e,10)||0)%360,i=parseInt(i,10)/100,o=parseInt(o,10)/100,i=n(0,t(i,1)),o=n(0,t(o,1)),0===i)return void(d=f=p=r(255*o));switch(a=e/60,s=o*i,l=s*(1-Math.abs(a%2-1)),c=o-s,Math.floor(a)){case 0:d=s,f=l,p=0;break;case 1:d=l,f=s,p=0;break;case 2:d=0,f=s,p=l;break;case 3:d=0,f=l,p=s;break;case 4:d=l,f=0,p=s;break;case 5:d=s,f=0,p=l;break;default:d=f=p=0}d=r(255*(d+c)),f=r(255*(f+c)),p=r(255*(p+c))}function a(){function e(e){return e=parseInt(e,10).toString(16),e.length>1?e:\"0\"+e}return\"#\"+e(d)+e(f)+e(p)}function s(){return{r:d,g:f,b:p}}function l(){return i(d,f,p)}function c(e){var t;return\"object\"==typeof e?\"r\"in e?(d=e.r,f=e.g,p=e.b):\"v\"in e&&o(e.h,e.s,e.v):(t=/rgb\\s*\\(\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)[^\\)]*\\)/gi.exec(e))?(d=parseInt(t[1],10),f=parseInt(t[2],10),p=parseInt(t[3],10)):(t=/#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(e))?(d=parseInt(t[1],16),f=parseInt(t[2],16),p=parseInt(t[3],16)):(t=/#([0-F])([0-F])([0-F])/gi.exec(e))&&(d=parseInt(t[1]+t[1],16),f=parseInt(t[2]+t[2],16),p=parseInt(t[3]+t[3],16)),d=0>d?0:d>255?255:d,f=0>f?0:f>255?255:f,p=0>p?0:p>255?255:p,u}var u=this,d=0,f=0,p=0;e&&c(e),u.toRgb=s,u.toHsv=l,u.toHex=a,u.parse=c}var t=Math.min,n=Math.max,r=Math.round;return e}),r(He,[Ne,Q,K,Me],function(e,t,n,r){return e.extend({Defaults:{classes:\"widget colorpicker\"},init:function(e){this._super(e)},postRender:function(){function e(e,t){var r=n.getPos(e),i,o;return i=t.pageX-r.x,o=t.pageY-r.y,i=Math.max(0,Math.min(i/e.clientWidth,1)),o=Math.max(0,Math.min(o/e.clientHeight,1)),{x:i,y:o}}function i(e,t){var i=(360-e.h)/360;n.css(d,{top:100*i+\"%\"}),t||n.css(p,{left:e.s+\"%\",top:100-e.v+\"%\"}),f.style.background=new r({s:100,v:100,h:e.h}).toHex(),s.color().parse({s:e.s,v:e.v,h:e.h})}function o(t){var n;n=e(f,t),c.s=100*n.x,c.v=100*(1-n.y),i(c),s.fire(\"change\")}function a(t){var n;n=e(u,t),c=l.toHsv(),c.h=360*(1-n.y),i(c,!0),s.fire(\"change\")}var s=this,l=s.color(),c,u,d,f,p;u=s.getEl(\"h\"),d=s.getEl(\"hp\"),f=s.getEl(\"sv\"),p=s.getEl(\"svp\"),s._repaint=function(){c=l.toHsv(),i(c)},s._super(),s._svdraghelper=new t(s._id+\"-sv\",{start:o,drag:o}),s._hdraghelper=new t(s._id+\"-h\",{start:a,drag:a}),s._repaint()},rgb:function(){return this.color().toRgb()},value:function(e){var t=this;return arguments.length?(t.color().parse(e),void(t._rendered&&t._repaint())):t.color().toHex()},color:function(){return this._color||(this._color=new r),this._color},renderHtml:function(){function e(){var e,t,n=\"\",i,a;for(i=\"filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=\",a=o.split(\",\"),e=0,t=a.length-1;t>e;e++)n+='
';return n}var t=this,n=t._id,r=t.classPrefix,i,o=\"#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000\",a=\"background: -ms-linear-gradient(top,\"+o+\");background: linear-gradient(to bottom,\"+o+\");\";\n\nreturn i='','\"}})}),r(Le,[Ne],function(e){return e.extend({init:function(e){var t=this;e.delimiter||(e.delimiter=\"\\xbb\"),t._super(e),t.addClass(\"path\"),t.canFocus=!0,t.on(\"click\",function(e){var n,r=e.target;(n=r.getAttribute(\"data-index\"))&&t.fire(\"select\",{value:t.data()[n],index:n})})},focus:function(){var e=this;return e.getEl().firstChild.focus(),e},data:function(e){var t=this;return\"undefined\"!=typeof e?(t._data=e,t.update(),t):t._data},update:function(){this.innerHtml(this._getPathHtml())},postRender:function(){var e=this;e._super(),e.data(e.settings.data)},renderHtml:function(){var e=this;return''+e._getPathHtml()+\"
\"},_getPathHtml:function(){var e=this,t=e._data||[],n,r,i=\"\",o=e.classPrefix;for(n=0,r=t.length;r>n;n++)i+=(n>0?' '+e.settings.delimiter+\"
\":\"\")+''+t[n].name+\"
\";return i||(i='\\xa0
'),i}})}),r(Pe,[Le,he],function(e,t){return e.extend({postRender:function(){function e(e){if(1===e.nodeType){if(\"BR\"==e.nodeName||e.getAttribute(\"data-mce-bogus\"))return!0;if(\"bookmark\"===e.getAttribute(\"data-mce-type\"))return!0}return!1}var n=this,r=t.activeEditor;return r.settings.elementpath!==!1&&(n.on(\"select\",function(e){r.focus(),r.selection.select(this.data()[e.index].element),r.nodeChanged()}),r.on(\"nodeChange\",function(t){for(var i=[],o=t.parents,a=o.length;a--;)if(1==o[a].nodeType&&!e(o[a])){var s=r.fire(\"ResolveName\",{name:o[a].nodeName.toLowerCase(),target:o[a]});if(s.isDefaultPrevented()||i.push({name:s.name,element:o[a]}),s.isPropagationStopped())break}n.data(i)})),n._super()}})}),r(Oe,[J],function(e){return e.extend({Defaults:{layout:\"flex\",align:\"center\",defaults:{flex:1}},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.addClass(\"formitem\"),t.preRender(e),''+(e.settings.title?'
'+e.settings.title+\"
\":\"\")+'
'+(e.settings.html||\"\")+t.renderHtml(e)+\"
\"}})}),r(Ie,[J,Oe,d],function(e,t,n){return e.extend({Defaults:{containerCls:\"form\",layout:\"flex\",direction:\"column\",align:\"stretch\",flex:1,padding:20,labelGap:30,spacing:10,callbacks:{submit:function(){this.submit()}}},preRender:function(){var e=this,r=e.items();e.settings.formItemDefaults||(e.settings.formItemDefaults={layout:\"flex\",autoResize:\"overflow\",defaults:{flex:1}}),r.each(function(r){var i,o=r.settings.label;o&&(i=new t(n.extend({items:{type:\"label\",id:r._id+\"-l\",text:o,flex:0,forId:r._id,disabled:r.disabled()}},e.settings.formItemDefaults)),i.type=\"formitem\",r.aria(\"labelledby\",r._id+\"-l\"),\"undefined\"==typeof r.settings.flex&&(r.settings.flex=1),e.replace(r,i),i.add(r))})},recalcLabels:function(){var e=this,t=0,n=[],r,i,o;if(e.settings.labelGapCalc!==!1)for(o=\"children\"==e.settings.labelGapCalc?e.find(\"formitem\"):e.items(),o.filter(\"formitem\").each(function(e){var r=e.items()[0],i=r.getEl().clientWidth;t=i>t?i:t,n.push(r)}),i=e.settings.labelGap||0,r=n.length;r--;)n[r].settings.minWidth=t+i},visible:function(e){var t=this._super(e);return e===!0&&this._rendered&&this.recalcLabels(),t},submit:function(){return this.fire(\"submit\",{data:this.toJSON()})},postRender:function(){var e=this;e._super(),e.recalcLabels(),e.fromJSON(e.settings.data)}})}),r(Fe,[Ie],function(e){return e.extend({Defaults:{containerCls:\"fieldset\",layout:\"flex\",direction:\"column\",align:\"stretch\",flex:1,padding:\"25 15 5 15\",labelGap:30,spacing:10,border:1},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.preRender(),t.preRender(e),''+(e.settings.title?''+e.settings.title+\" \":\"\")+''+(e.settings.html||\"\")+t.renderHtml(e)+\"
\"}})}),r(ze,[Re,d],function(e,t){return e.extend({init:function(e){var n=this,r=tinymce.activeEditor,i=r.settings,o,a,s;e.spellcheck=!1,s=i.file_picker_types||i.file_browser_callback_types,s&&(s=t.makeMap(s,/[, ]/)),(!s||s[e.filetype])&&(a=i.file_picker_callback,!a||s&&!s[e.filetype]?(a=i.file_browser_callback,!a||s&&!s[e.filetype]||(o=function(){a(n.getEl(\"inp\").id,n.value(),e.filetype,window)})):o=function(){var i=n.fire(\"beforecall\").meta;i=t.extend({filetype:e.filetype},i),a.call(r,function(e,t){n.value(e).fire(\"change\",{meta:t})},n.value(),i)}),o&&(e.icon=\"browse\",e.onaction=o),n._super(e)}})}),r(We,[_e],function(e){return e.extend({recalc:function(e){var t=e.layoutRect(),n=e.paddingBox();e.items().filter(\":visible\").each(function(e){e.layoutRect({x:n.left,y:n.top,w:t.innerW-n.right-n.left,h:t.innerH-n.top-n.bottom}),e.recalc&&e.recalc()})}})}),r(Ve,[_e],function(e){return e.extend({recalc:function(e){var t,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v=[],y,b,C,x,w,_,E,N,k,S,T,R,A,B,D,M,H,L,P,O,I,F,z=Math.max,W=Math.min;for(r=e.items().filter(\":visible\"),i=e.layoutRect(),o=e._paddingBox,a=e.settings,f=e.isRtl()?a.direction||\"row-reversed\":a.direction,s=a.align,l=e.isRtl()?a.pack||\"end\":a.pack,c=a.spacing||0,(\"row-reversed\"==f||\"column-reverse\"==f)&&(r=r.set(r.toArray().reverse()),f=f.split(\"-\")[0]),\"column\"==f?(k=\"y\",E=\"h\",N=\"minH\",S=\"maxH\",R=\"innerH\",T=\"top\",A=\"deltaH\",B=\"contentH\",P=\"left\",H=\"w\",D=\"x\",M=\"innerW\",L=\"minW\",O=\"right\",I=\"deltaW\",F=\"contentW\"):(k=\"x\",E=\"w\",N=\"minW\",S=\"maxW\",R=\"innerW\",T=\"left\",A=\"deltaW\",B=\"contentW\",P=\"top\",H=\"h\",D=\"y\",M=\"innerH\",L=\"minH\",O=\"bottom\",I=\"deltaH\",F=\"contentH\"),d=i[R]-o[T]-o[T],_=u=0,t=0,n=r.length;n>t;t++)p=r[t],h=p.layoutRect(),m=p.settings,g=m.flex,d-=n-1>t?c:0,g>0&&(u+=g,h[S]&&v.push(p),h.flex=g),d-=h[N],y=o[P]+h[L]+o[O],y>_&&(_=y);if(x={},0>d?x[N]=i[N]-d+i[A]:x[N]=i[R]-d+i[A],x[L]=_+i[I],x[B]=i[R]-d,x[F]=_,x.minW=W(x.minW,i.maxW),x.minH=W(x.minH,i.maxH),x.minW=z(x.minW,i.startMinWidth),x.minH=z(x.minH,i.startMinHeight),!i.autoResize||x.minW==i.minW&&x.minH==i.minH){for(C=d/u,t=0,n=v.length;n>t;t++)p=v[t],h=p.layoutRect(),b=h[S],y=h[N]+h.flex*C,y>b?(d-=h[S]-h[N],u-=h.flex,h.flex=0,h.maxFlexSize=b):h.maxFlexSize=0;for(C=d/u,w=o[T],x={},0===u&&(\"end\"==l?w=d+o[T]:\"center\"==l?(w=Math.round(i[R]/2-(i[R]-d)/2)+o[T],0>w&&(w=o[T])):\"justify\"==l&&(w=o[T],c=Math.floor(d/(r.length-1)))),x[D]=o[P],t=0,n=r.length;n>t;t++)p=r[t],h=p.layoutRect(),y=h.maxFlexSize||h[N],\"center\"===s?x[D]=Math.round(i[M]/2-h[H]/2):\"stretch\"===s?(x[H]=z(h[L]||0,i[M]-o[P]-o[O]),x[D]=o[P]):\"end\"===s&&(x[D]=i[M]-h[H]-o.top),h.flex>0&&(y+=h.flex*C),x[E]=y,x[k]=w,p.layoutRect(x),p.recalc&&p.recalc(),w+=y+c}else if(x.w=x.minW,x.h=x.minH,e.layoutRect(x),this.recalc(e),null===e._lastRect){var V=e.parent();V&&(V._lastRect=null,V.recalc())}}})}),r(Ue,[we],function(e){return e.extend({Defaults:{containerClass:\"flow-layout\",controlClass:\"flow-layout-item\",endClass:\"break\"},recalc:function(e){e.items().filter(\":visible\").each(function(e){e.recalc&&e.recalc()})}})}),r($e,[Y,Ne,re,d,he,u],function(e,t,n,r,i,o){function a(e){function t(t,n){return function(){var r=this;e.on(\"nodeChange\",function(i){var o=e.formatter,a=null;s(i.parents,function(e){return s(t,function(t){return n?o.matchNode(e,n,{value:t.value})&&(a=t.value):o.matchNode(e,t.value)&&(a=t.value),a?!1:void 0}),a?!1:void 0}),r.value(a)})}}function r(e){e=e.replace(/;$/,\"\").split(\";\");for(var t=e.length;t--;)e[t]=e[t].split(\"=\");return e}function i(){function t(e){var n=[];if(e)return s(e,function(e){var o={text:e.title,icon:e.icon};if(e.items)o.menu=t(e.items);else{var a=e.format||\"custom\"+r++;e.format||(e.name=a,i.push(e)),o.format=a,o.cmd=e.cmd}n.push(o)}),n}function n(){var n;return n=t(e.settings.style_formats_merge?e.settings.style_formats?o.concat(e.settings.style_formats):o:e.settings.style_formats||o)}var r=0,i=[],o=[{title:\"Headings\",items:[{title:\"Heading 1\",format:\"h1\"},{title:\"Heading 2\",format:\"h2\"},{title:\"Heading 3\",format:\"h3\"},{title:\"Heading 4\",format:\"h4\"},{title:\"Heading 5\",format:\"h5\"},{title:\"Heading 6\",format:\"h6\"}]},{title:\"Inline\",items:[{title:\"Bold\",icon:\"bold\",format:\"bold\"},{title:\"Italic\",icon:\"italic\",format:\"italic\"},{title:\"Underline\",icon:\"underline\",format:\"underline\"},{title:\"Strikethrough\",icon:\"strikethrough\",format:\"strikethrough\"},{title:\"Superscript\",icon:\"superscript\",format:\"superscript\"},{title:\"Subscript\",icon:\"subscript\",format:\"subscript\"},{title:\"Code\",icon:\"code\",format:\"code\"}]},{title:\"Blocks\",items:[{title:\"Paragraph\",format:\"p\"},{title:\"Blockquote\",format:\"blockquote\"},{title:\"Div\",format:\"div\"},{title:\"Pre\",format:\"pre\"}]},{title:\"Alignment\",items:[{title:\"Left\",icon:\"alignleft\",format:\"alignleft\"},{title:\"Center\",icon:\"aligncenter\",format:\"aligncenter\"},{title:\"Right\",icon:\"alignright\",format:\"alignright\"},{title:\"Justify\",icon:\"alignjustify\",format:\"alignjustify\"}]}];return e.on(\"init\",function(){s(i,function(t){e.formatter.register(t.name,t)})}),{type:\"menu\",items:n(),onPostRender:function(t){e.fire(\"renderFormatsMenu\",{control:t.control})},itemDefaults:{preview:!0,textStyle:function(){return this.settings.format?e.formatter.getCssText(this.settings.format):void 0},onPostRender:function(){var t=this;t.parent().on(\"show\",function(){var n,r;n=t.settings.format,n&&(t.disabled(!e.formatter.canApply(n)),t.active(e.formatter.match(n))),r=t.settings.cmd,r&&t.active(e.queryCommandState(r))})},onclick:function(){this.settings.format&&l(this.settings.format),this.settings.cmd&&e.execCommand(this.settings.cmd)}}}}function o(t){return function(){function n(){return e.undoManager?e.undoManager[t]():!1}var r=this;t=\"redo\"==t?\"hasRedo\":\"hasUndo\",r.disabled(!n()),e.on(\"Undo Redo AddUndo TypingUndo ClearUndos\",function(){r.disabled(!n())})}}function a(){var t=this;e.on(\"VisualAid\",function(e){t.active(e.hasVisual)}),t.active(e.hasVisual)}function l(t){t.control&&(t=t.control.value()),t&&e.execCommand(\"mceToggleFormat\",!1,t)}var c;c=i(),s({bold:\"Bold\",italic:\"Italic\",underline:\"Underline\",strikethrough:\"Strikethrough\",subscript:\"Subscript\",superscript:\"Superscript\"},function(t,n){e.addButton(n,{tooltip:t,onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on(\"init\",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})},onclick:function(){l(n)}})}),s({outdent:[\"Decrease indent\",\"Outdent\"],indent:[\"Increase indent\",\"Indent\"],cut:[\"Cut\",\"Cut\"],copy:[\"Copy\",\"Copy\"],paste:[\"Paste\",\"Paste\"],help:[\"Help\",\"mceHelp\"],selectall:[\"Select all\",\"SelectAll\"],removeformat:[\"Clear formatting\",\"RemoveFormat\"],visualaid:[\"Visual aids\",\"mceToggleVisualAid\"],newdocument:[\"New document\",\"mceNewDocument\"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1]})}),s({blockquote:[\"Blockquote\",\"mceBlockQuote\"],numlist:[\"Numbered list\",\"InsertOrderedList\"],bullist:[\"Bullet list\",\"InsertUnorderedList\"],subscript:[\"Subscript\",\"Subscript\"],superscript:[\"Superscript\",\"Superscript\"],alignleft:[\"Align left\",\"JustifyLeft\"],aligncenter:[\"Align center\",\"JustifyCenter\"],alignright:[\"Align right\",\"JustifyRight\"],alignjustify:[\"Justify\",\"JustifyFull\"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1],onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on(\"init\",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})}})}),e.addButton(\"undo\",{tooltip:\"Undo\",onPostRender:o(\"undo\"),cmd:\"undo\"}),e.addButton(\"redo\",{tooltip:\"Redo\",onPostRender:o(\"redo\"),cmd:\"redo\"}),e.addMenuItem(\"newdocument\",{text:\"New document\",icon:\"newdocument\",cmd:\"mceNewDocument\"}),e.addMenuItem(\"undo\",{text:\"Undo\",icon:\"undo\",shortcut:\"Meta+Z\",onPostRender:o(\"undo\"),cmd:\"undo\"}),e.addMenuItem(\"redo\",{text:\"Redo\",icon:\"redo\",shortcut:\"Meta+Y\",onPostRender:o(\"redo\"),cmd:\"redo\"}),e.addMenuItem(\"visualaid\",{text:\"Visual aids\",selectable:!0,onPostRender:a,cmd:\"mceToggleVisualAid\"}),s({cut:[\"Cut\",\"Cut\",\"Meta+X\"],copy:[\"Copy\",\"Copy\",\"Meta+C\"],paste:[\"Paste\",\"Paste\",\"Meta+V\"],selectall:[\"Select all\",\"SelectAll\",\"Meta+A\"],bold:[\"Bold\",\"Bold\",\"Meta+B\"],italic:[\"Italic\",\"Italic\",\"Meta+I\"],underline:[\"Underline\",\"Underline\"],strikethrough:[\"Strikethrough\",\"Strikethrough\"],subscript:[\"Subscript\",\"Subscript\"],superscript:[\"Superscript\",\"Superscript\"],removeformat:[\"Clear formatting\",\"RemoveFormat\"]},function(t,n){e.addMenuItem(n,{text:t[0],icon:n,shortcut:t[2],cmd:t[1]})}),e.on(\"mousedown\",function(){n.hideAll()}),e.addButton(\"styleselect\",{type:\"menubutton\",text:\"Formats\",menu:c}),e.addButton(\"formatselect\",function(){var n=[],i=r(e.settings.block_formats||\"Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre\");return s(i,function(t){n.push({text:t[0],value:t[1],textStyle:function(){return e.formatter.getCssText(t[1])}})}),{type:\"listbox\",text:i[0][0],values:n,fixedWidth:!0,onselect:l,onPostRender:t(n)}}),e.addButton(\"fontselect\",function(){var n=\"Andale Mono=andale mono,monospace;Arial=arial,helvetica,sans-serif;Arial Black=arial black,sans-serif;Book Antiqua=book antiqua,palatino,serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,palatino,serif;Helvetica=helvetica,arial,sans-serif;Impact=impact,sans-serif;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco,monospace;Times New Roman=times new roman,times,serif;Trebuchet MS=trebuchet ms,geneva,sans-serif;Verdana=verdana,geneva,sans-serif;Webdings=webdings;Wingdings=wingdings,zapf dingbats\",i=[],o=r(e.settings.font_formats||n);return s(o,function(e){i.push({text:{raw:e[0]},value:e[1],textStyle:-1==e[1].indexOf(\"dings\")?\"font-family:\"+e[1]:\"\"})}),{type:\"listbox\",text:\"Font Family\",tooltip:\"Font Family\",values:i,fixedWidth:!0,onPostRender:t(i,\"fontname\"),onselect:function(t){t.control.settings.value&&e.execCommand(\"FontName\",!1,t.control.settings.value)}}}),e.addButton(\"fontsizeselect\",function(){var n=[],r=\"8pt 10pt 12pt 14pt 18pt 24pt 36pt\",i=e.settings.fontsize_formats||r;return s(i.split(\" \"),function(e){var t=e,r=e,i=e.split(\"=\");i.length>1&&(t=i[0],r=i[1]),n.push({text:t,value:r})}),{type:\"listbox\",text:\"Font Sizes\",tooltip:\"Font Sizes\",values:n,fixedWidth:!0,onPostRender:t(n,\"fontsize\"),onclick:function(t){t.control.settings.value&&e.execCommand(\"FontSize\",!1,t.control.settings.value)}}}),e.addMenuItem(\"formats\",{text:\"Formats\",menu:c})}var s=r.each;i.on(\"AddEditor\",function(t){t.editor.rtl&&(e.rtl=!0),a(t.editor)}),e.translate=function(e){return i.translate(e)},t.tooltips=!o.iOS}),r(qe,[_e],function(e){return e.extend({recalc:function(e){var t=e.settings,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v,y,b,C,x,w,_,E=[],N=[],k,S,T,R,A,B;t=e.settings,i=e.items().filter(\":visible\"),o=e.layoutRect(),r=t.columns||Math.ceil(Math.sqrt(i.length)),n=Math.ceil(i.length/r),y=t.spacingH||t.spacing||0,b=t.spacingV||t.spacing||0,C=t.alignH||t.align,x=t.alignV||t.align,g=e._paddingBox,A=\"reverseRows\"in t?t.reverseRows:e.isRtl(),C&&\"string\"==typeof C&&(C=[C]),x&&\"string\"==typeof x&&(x=[x]);for(d=0;r>d;d++)E.push(0);for(f=0;n>f;f++)N.push(0);for(f=0;n>f;f++)for(d=0;r>d&&(u=i[f*r+d],u);d++)c=u.layoutRect(),k=c.minW,S=c.minH,E[d]=k>E[d]?k:E[d],N[f]=S>N[f]?S:N[f];for(T=o.innerW-g.left-g.right,w=0,d=0;r>d;d++)w+=E[d]+(d>0?y:0),T-=(d>0?y:0)+E[d];for(R=o.innerH-g.top-g.bottom,_=0,f=0;n>f;f++)_+=N[f]+(f>0?b:0),R-=(f>0?b:0)+N[f];if(w+=g.left+g.right,_+=g.top+g.bottom,l={},l.minW=w+(o.w-o.innerW),l.minH=_+(o.h-o.innerH),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH,l.minW=Math.min(l.minW,o.maxW),l.minH=Math.min(l.minH,o.maxH),l.minW=Math.max(l.minW,o.startMinWidth),l.minH=Math.max(l.minH,o.startMinHeight),!o.autoResize||l.minW==o.minW&&l.minH==o.minH){o.autoResize&&(l=e.layoutRect(l),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH);var D;D=\"start\"==t.packV?0:R>0?Math.floor(R/n):0;var M=0,H=t.flexWidths;if(H)for(d=0;dd;d++)E[d]+=H?H[d]*L:L;for(h=g.top,f=0;n>f;f++){for(p=g.left,s=N[f]+D,d=0;r>d&&(B=A?f*r+r-1-d:f*r+d,u=i[B],u);d++)m=u.settings,c=u.layoutRect(),a=Math.max(E[d],c.startMinWidth),c.x=p,c.y=h,v=m.alignH||(C?C[d]||C[0]:null),\"center\"==v?c.x=p+a/2-c.w/2:\"right\"==v?c.x=p+a-c.w:\"stretch\"==v&&(c.w=a),v=m.alignV||(x?x[d]||x[0]:null),\"center\"==v?c.y=h+s/2-c.h/2:\"bottom\"==v?c.y=h+s-c.h:\"stretch\"==v&&(c.h=s),u.layoutRect(c),p+=a+y,u.recalc&&u.recalc();h+=s+b}}else if(l.w=l.minW,l.h=l.minH,e.layoutRect(l),this.recalc(e),null===e._lastRect){var P=e.parent();P&&(P._lastRect=null,P.recalc())}}})}),r(je,[Ne],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass(\"iframe\"),e.canFocus=!1,''},src:function(e){this.getEl().src=e},html:function(e,t){var n=this,r=this.getEl().contentWindow.document.body;return r?(r.innerHTML=e,t&&t()):setTimeout(function(){n.html(e)},0),this}})}),r(Ke,[Ne,K],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t.addClass(\"widget\"),t.addClass(\"label\"),t.canFocus=!1,e.multiline&&t.addClass(\"autoscroll\"),e.strong&&t.addClass(\"strong\")},initLayoutRect:function(){var e=this,n=e._super();if(e.settings.multiline){var r=t.getSize(e.getEl());r.width>n.maxW&&(n.minW=n.maxW,e.addClass(\"multiline\")),e.getEl().style.width=n.minW+\"px\",n.startMinH=n.h=n.minH=Math.min(n.maxH,t.getSize(e.getEl()).height)}return n},repaint:function(){var e=this;return e.settings.multiline||(e.getEl().style.lineHeight=e.layoutRect().h+\"px\"),e._super()},text:function(e){var t=this;return t._rendered&&e&&this.innerHtml(t.encode(e)),t._super(e)},renderHtml:function(){var e=this,t=e.settings.forId;return'\"+e.encode(e._text)+\" \"}})}),r(Ye,[J],function(e){return e.extend({Defaults:{role:\"toolbar\",layout:\"flow\"},init:function(e){var t=this;t._super(e),t.addClass(\"toolbar\")},postRender:function(){var e=this;return e.items().addClass(\"toolbar-item\"),e._super()}})}),r(Ge,[Ye],function(e){return e.extend({Defaults:{role:\"menubar\",containerCls:\"menubar\",ariaRoot:!0,defaults:{type:\"menubutton\"}}})}),r(Xe,[ke,G,Ge],function(e,t,n){function r(e,t){for(;e;){if(t===e)return!0;e=e.parentNode}return!1}var i=e.extend({init:function(e){var t=this;t._renderOpen=!0,t._super(e),t.addClass(\"menubtn\"),e.fixedWidth&&t.addClass(\"fixed-width\"),t.aria(\"haspopup\",!0),t.hasPopup=!0},showMenu:function(){var e=this,n=e.settings,r;return e.menu&&e.menu.visible()?e.hideMenu():(e.menu||(r=n.menu||[],r.length?r={type:\"menu\",items:r}:r.type=r.type||\"menu\",e.menu=t.create(r).parent(e).renderTo(),e.fire(\"createmenu\"),e.menu.reflow(),e.menu.on(\"cancel\",function(t){t.control.parent()===e.menu&&(t.stopPropagation(),e.focus(),e.hideMenu())}),e.menu.on(\"select\",function(){e.focus()}),e.menu.on(\"show hide\",function(t){t.control==e.menu&&e.activeMenu(\"show\"==t.type),e.aria(\"expanded\",\"show\"==t.type)}).fire(\"show\")),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),void e.menu.moveRel(e.getEl(),e.isRtl()?[\"br-tr\",\"tr-br\"]:[\"bl-tl\",\"tl-bl\"]))},hideMenu:function(){var e=this;e.menu&&(e.menu.items().each(function(e){e.hideMenu&&e.hideMenu()}),e.menu.hide())},activeMenu:function(e){this.toggleClass(\"active\",e)},renderHtml:function(){var e=this,t=e._id,r=e.classPrefix,i=e.settings.icon,o;return o=e.settings.image,o?(i=\"none\",\"string\"!=typeof o&&(o=window.getSelection?o[0]:o[1]),o=\" style=\\\"background-image: url('\"+o+\"')\\\"\"):o=\"\",i=e.settings.icon?r+\"ico \"+r+\"i-\"+i:\"\",e.aria(\"role\",e.parent()instanceof n?\"menuitem\":\"button\"),''+(i?' \":\"\")+\"\"+(e._text?(i?\"\\xa0\":\"\")+e.encode(e._text):\"\")+'
'},postRender:function(){var e=this;return e.on(\"click\",function(t){t.control===e&&r(t.target,e.getEl())&&(e.showMenu(),t.aria&&e.menu.items()[0].focus())}),e.on(\"mouseenter\",function(t){var n=t.control,r=e.parent(),o;n&&r&&n instanceof i&&n.parent()==r&&(r.items().filter(\"MenuButton\").each(function(e){e.hideMenu&&e!=n&&(e.menu&&e.menu.visible()&&(o=!0),e.hideMenu())}),o&&(n.focus(),n.showMenu()))}),e._super()},text:function(e){var t=this,n,r;if(t._rendered)for(r=t.getEl(\"open\").getElementsByTagName(\"span\"),n=0;n0&&(o=r[0].text,n._value=r[0].value),e.menu=r),e.text=e.text||o||r[0].text,n._super(e),n.addClass(\"listbox\"),n.on(\"select\",function(t){var r=t.control;a&&(t.lastControl=a),e.multiple?r.active(!r.active()):n.value(t.control.settings.value),a=r})},value:function(e){function t(e,n){e.items().each(function(e){i=e.value()===n,i&&(o=o||e.text()),e.active(i),e.menu&&t(e.menu,n)})}function n(t){for(var r=0;r'+(\"-\"!==a?' \\xa0\":\"\")+(\"-\"!==a?''+a+\" \":\"\")+(c?'\":\"\")+(i.menu?'
':\"\")+\"\"},postRender:function(){var e=this,t=e.settings,n=t.textStyle;if(\"function\"==typeof n&&(n=n.call(this)),n){var r=e.getEl(\"text\");r&&r.setAttribute(\"style\",n)}return e.on(\"mouseenter click\",function(n){n.control===e&&(t.menu||\"click\"!==n.type?(e.showMenu(),n.aria&&e.menu.focus(!0)):(e.fire(\"select\"),e.parent().hideAll()))}),e._super(),e},active:function(e){return\"undefined\"!=typeof e&&this.aria(\"checked\",e),this._super(e)},remove:function(){this._super(),this.menu&&this.menu.remove()}})}),r(Ze,[re,Qe,d],function(e,t,n){var r=e.extend({Defaults:{defaultType:\"menuitem\",border:1,layout:\"stack\",role:\"application\",bodyRole:\"menu\",ariaRoot:!0},init:function(e){var t=this;if(e.autohide=!0,e.constrainToViewport=!0,e.itemDefaults)for(var r=e.items,i=r.length;i--;)r[i]=n.extend({},e.itemDefaults,r[i]);t._super(e),t.addClass(\"menu\")},repaint:function(){return this.toggleClass(\"menu-align\",!0),this._super(),this.getEl().style.height=\"\",this.getEl(\"body\").style.height=\"\",this},cancel:function(){var e=this;e.hideAll(),e.fire(\"select\")},hideAll:function(){var e=this;return this.find(\"menuitem\").exec(\"hideMenu\"),e._super()},preRender:function(){var e=this;return e.items().each(function(t){var n=t.settings;return n.icon||n.selectable?(e._hasIcons=!0,!1):void 0}),e._super()}});return r}),r(et,[Te],function(e){return e.extend({Defaults:{classes:\"radio\",role:\"radio\"}})}),r(tt,[Ne,Q],function(e,t){return e.extend({renderHtml:function(){var e=this,t=e.classPrefix;return e.addClass(\"resizehandle\"),\"both\"==e.settings.direction&&e.addClass(\"resizehandle-both\"),e.canFocus=!1,'
'},postRender:function(){var e=this;e._super(),e.resizeDragHelper=new t(this._id,{start:function(){e.fire(\"ResizeStart\")},drag:function(t){\"both\"!=e.settings.direction&&(t.deltaX=0),e.fire(\"Resize\",t)},stop:function(){e.fire(\"ResizeEnd\")}})},remove:function(){return this.resizeDragHelper&&this.resizeDragHelper.destroy(),this._super()}})}),r(nt,[Ne],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass(\"spacer\"),e.canFocus=!1,'
'}})}),r(rt,[Xe,K],function(e,t){return e.extend({Defaults:{classes:\"widget btn splitbtn\",role:\"button\"},repaint:function(){var e=this,n=e.getEl(),r=e.layoutRect(),i,o;return e._super(),i=n.firstChild,o=n.lastChild,t.css(i,{width:r.w-t.getSize(o).width,height:r.h-2}),t.css(o,{height:r.h-2}),e},activeMenu:function(e){var n=this;t.toggleClass(n.getEl().lastChild,n.classPrefix+\"active\",e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r,i=e.settings.icon;return r=e.settings.image,r?(i=\"none\",\"string\"!=typeof r&&(r=window.getSelection?r[0]:r[1]),r=\" style=\\\"background-image: url('\"+r+\"')\\\"\"):r=\"\",i=e.settings.icon?n+\"ico \"+n+\"i-\"+i:\"\",''+(i?' \":\"\")+(e._text?(i?\" \":\"\")+e._text:\"\")+' '+(e._menuBtnText?(i?\"\\xa0\":\"\")+e._menuBtnText:\"\")+'
'},postRender:function(){var e=this,t=e.settings.onclick;return e.on(\"click\",function(e){var n=e.target;if(e.control==this)for(;n;){if(e.aria&&\"down\"!=e.aria.key||\"BUTTON\"==n.nodeName&&-1==n.className.indexOf(\"open\"))return e.stopImmediatePropagation(),void t.call(this,e);n=n.parentNode}}),delete e.settings.onclick,e._super()}})}),r(it,[Ue],function(e){return e.extend({Defaults:{containerClass:\"stack-layout\",controlClass:\"stack-layout-item\",endClass:\"break\"}})}),r(ot,[ee,K],function(e,t){return e.extend({Defaults:{layout:\"absolute\",defaults:{type:\"panel\"}},activateTab:function(e){var n;this.activeTabId&&(n=this.getEl(this.activeTabId),t.removeClass(n,this.classPrefix+\"active\"),n.setAttribute(\"aria-selected\",\"false\")),this.activeTabId=\"t\"+e,n=this.getEl(\"t\"+e),n.setAttribute(\"aria-selected\",\"true\"),t.addClass(n,this.classPrefix+\"active\"),this.items()[e].show().fire(\"showtab\"),this.reflow(),this.items().each(function(t,n){e!=n&&t.hide()})},renderHtml:function(){var e=this,t=e._layout,n=\"\",r=e.classPrefix;return e.preRender(),t.preRender(e),e.items().each(function(t,i){var o=e._id+\"-t\"+i;t.aria(\"role\",\"tabpanel\"),t.aria(\"labelledby\",o),n+=''+e.encode(t.settings.title)+\"
\"}),''+n+'
'+t.renderHtml(e)+\"
\"},postRender:function(){var e=this;e._super(),e.settings.activeTab=e.settings.activeTab||0,e.activateTab(e.settings.activeTab),this.on(\"click\",function(t){var n=t.target.parentNode;if(t.target.parentNode.id==e._id+\"-head\")for(var r=n.childNodes.length;r--;)n.childNodes[r]==t.target&&e.activateTab(r)})},initLayoutRect:function(){var e=this,n,r,i;r=t.getSize(e.getEl(\"head\")).width,r=0>r?0:r,i=0,e.items().each(function(e){r=Math.max(r,e.layoutRect().minW),i=Math.max(i,e.layoutRect().minH)}),e.items().each(function(e){e.settings.x=0,e.settings.y=0,e.settings.w=r,e.settings.h=i,e.layoutRect({x:0,y:0,w:r,h:i})});var o=t.getSize(e.getEl(\"head\")).height;return e.settings.minWidth=r,e.settings.minHeight=i+o,n=e._super(),n.deltaH+=o,n.innerH=n.h-n.deltaH,n}})}),r(at,[Ne,K],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t._value=e.value||\"\",t.addClass(\"textbox\"),e.multiline?t.addClass(\"multiline\"):t.on(\"keydown\",function(e){13==e.keyCode&&t.parents().reverse().each(function(t){return e.preventDefault(),t.hasEventListeners(\"submit\")&&t.toJSON?(t.fire(\"submit\",{data:t.toJSON()}),!1):void 0})})},disabled:function(e){var t=this;return t._rendered&&\"undefined\"!=typeof e&&(t.getEl().disabled=e),t._super(e)},value:function(e){var t=this;return\"undefined\"!=typeof e?(t._value=e,t._rendered&&(t.getEl().value=e),t):t._rendered?t.getEl().value:t._value},repaint:function(){var e=this,t,n,r,i=0,o=0,a;t=e.getEl().style,n=e._layoutRect,a=e._lastRepaintRect||{};var s=document;return!e.settings.multiline&&s.all&&(!s.documentMode||s.documentMode<=8)&&(t.lineHeight=n.h-o+\"px\"),r=e._borderBox,i=r.left+r.right+8,o=r.top+r.bottom+(e.settings.multiline?8:0),n.x!==a.x&&(t.left=n.x+\"px\",a.x=n.x),n.y!==a.y&&(t.top=n.y+\"px\",a.y=n.y),n.w!==a.w&&(t.width=n.w-i+\"px\",a.w=n.w),n.h!==a.h&&(t.height=n.h-o+\"px\",a.h=n.h),e._lastRepaintRect=a,e.fire(\"repaint\",{},!1),e},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.encode(e._value,!1),i=\"\";return\"spellcheck\"in n&&(i+=' spellcheck=\"'+n.spellcheck+'\"'),n.maxLength&&(i+=' maxlength=\"'+n.maxLength+'\"'),n.size&&(i+=' size=\"'+n.size+'\"'),n.subtype&&(i+=' type=\"'+n.subtype+'\"'),e.disabled()&&(i+=' disabled=\"disabled\"'),n.multiline?'\":' \"},postRender:function(){var e=this;return t.on(e.getEl(),\"change\",function(t){e.fire(\"change\",t)}),e._super()},remove:function(){t.off(this.getEl()),this._super()}})}),r(st,[K,Y],function(e,t){return function(n,r){var i=this,o,a=t.classPrefix;i.show=function(t){return i.hide(),o=!0,window.setTimeout(function(){o&&n.appendChild(e.createFragment('
'));\n\n},t||0),i},i.hide=function(){var e=n.lastChild;return e&&-1!=e.className.indexOf(\"throbber\")&&e.parentNode.removeChild(e),o=!1,i}}}),a([l,c,u,d,f,p,h,m,g,y,b,C,x,_,E,N,k,S,T,R,A,B,D,M,H,L,O,I,F,z,W,V,U,$,q,j,K,Y,G,X,J,Q,Z,ee,te,ne,re,ie,oe,ae,se,le,ce,ue,de,fe,pe,he,me,ge,ve,ye,be,Ce,xe,we,_e,Ee,Ne,ke,Se,Te,Re,Ae,Be,De,Me,He,Le,Pe,Oe,Ie,Fe,ze,We,Ve,Ue,$e,qe,je,Ke,Ye,Ge,Xe,Je,Qe,Ze,et,tt,nt,rt,it,ot,at,st])\n\n\n// theme\ntinymce.ThemeManager.add(\"modern\",function(a){function b(){function b(b){var d,e=[];if(b)return l(b.split(/[ ,]/),function(b){function c(){var c=a.selection;\"bullist\"==f&&c.selectorChanged(\"ul > li\",function(a,c){for(var d,e=c.parents.length;e--&&(d=c.parents[e].nodeName,\"OL\"!=d&&\"UL\"!=d););b.active(a&&\"UL\"==d)}),\"numlist\"==f&&c.selectorChanged(\"ol > li\",function(a,c){for(var d,e=c.parents.length;e--&&(d=c.parents[e].nodeName,\"OL\"!=d&&\"UL\"!=d););b.active(a&&\"OL\"==d)}),b.settings.stateSelector&&c.selectorChanged(b.settings.stateSelector,function(a){b.active(a)},!0),b.settings.disabledStateSelector&&c.selectorChanged(b.settings.disabledStateSelector,function(a){b.disabled(a)})}var f;\"|\"==b?d=null:k.has(b)?(b={type:b},j.toolbar_items_size&&(b.size=j.toolbar_items_size),e.push(b),d=null):(d||(d={type:\"buttongroup\",items:[]},e.push(d)),a.buttons[b]&&(f=b,b=a.buttons[f],\"function\"==typeof b&&(b=b()),b.type=b.type||\"button\",j.toolbar_items_size&&(b.size=j.toolbar_items_size),b=k.create(b),d.items.push(b),a.initialized?c():a.on(\"init\",c)))}),c.push({type:\"toolbar\",layout:\"flow\",items:e}),!0}var c=[];if(tinymce.isArray(j.toolbar)){if(0===j.toolbar.length)return;tinymce.each(j.toolbar,function(a,b){j[\"toolbar\"+(b+1)]=a}),delete j.toolbar}for(var d=1;10>d&&b(j[\"toolbar\"+d]);d++);return c.length||j.toolbar===!1||b(j.toolbar||o),c.length?{type:\"panel\",layout:\"stack\",classes:\"toolbar-grp\",ariaRoot:!0,ariaRemember:!0,items:c}:void 0}function c(){function b(b){var c;return\"|\"==b?{text:\"|\"}:c=a.menuItems[b]}function c(c){var d,e,f,g,h;if(h=tinymce.makeMap((j.removed_menuitems||\"\").split(/[ ,]/)),j.menu?(e=j.menu[c],g=!0):e=n[c],e){d={text:e.title},f=[],l((e.items||\"\").split(/[ ,]/),function(a){var c=b(a);c&&!h[a]&&f.push(b(a))}),g||l(a.menuItems,function(a){a.context==c&&(\"before\"==a.separator&&f.push({text:\"|\"}),a.prependToContext?f.unshift(a):f.push(a),\"after\"==a.separator&&f.push({text:\"|\"}))});for(var i=0;i]+>[^<]+<\\/a>$/.test(b)||-1==b.indexOf(\"href=\")))return!1;if(a){var c,d=a.childNodes;if(0===d.length)return!1;for(c=d.length-1;c>=0;c--)if(3!=d[c].nodeType)return!1}return!0}var i,j,k,l,m,n,o,p,q,r,s,t,u={},v=a.selection,w=a.dom;i=v.getNode(),j=w.getParent(i,\"a[href]\"),m=h(),u.text=k=j?j.innerText||j.textContent:v.getContent({format:\"text\"}),u.href=j?w.getAttrib(j,\"href\"):\"\",j?u.target=w.getAttrib(j,\"target\"):a.settings.default_link_target&&(u.target=a.settings.default_link_target),(t=w.getAttrib(j,\"rel\"))&&(u.rel=t),(t=w.getAttrib(j,\"class\"))&&(u[\"class\"]=t),(t=w.getAttrib(j,\"title\"))&&(u.title=t),m&&(n={name:\"text\",type:\"textbox\",size:40,label:\"Text to display\",onchange:function(){u.text=this.value()}}),b&&(o={type:\"listbox\",label:\"Link list\",values:c(b,function(b){b.value=a.convertURL(b.value||b.url,\"href\")},[{text:\"None\",value:\"\"}]),onselect:d,value:a.convertURL(u.href,\"href\"),onPostRender:function(){o=this}}),a.settings.target_list!==!1&&(a.settings.target_list||(a.settings.target_list=[{text:\"None\",value:\"\"},{text:\"New window\",value:\"_blank\"}]),q={name:\"target\",type:\"listbox\",label:\"Target\",values:c(a.settings.target_list)}),a.settings.rel_list&&(p={name:\"rel\",type:\"listbox\",label:\"Rel\",values:c(a.settings.rel_list)}),a.settings.link_class_list&&(r={name:\"class\",type:\"listbox\",label:\"Class\",values:c(a.settings.link_class_list,function(b){b.value&&(b.textStyle=function(){return a.formatter.getCssText({inline:\"a\",classes:[b.value]})})})}),a.settings.link_title!==!1&&(s={name:\"title\",type:\"textbox\",label:\"Title\",value:u.title}),l=a.windowManager.open({title:\"Insert link\",data:u,body:[{name:\"href\",type:\"filepicker\",filetype:\"file\",size:40,autofocus:!0,label:\"Url\",onchange:g,onkeyup:f},n,s,e(u.href),o,p,q,r],onSubmit:function(b){function c(b,c){var d=a.selection.getRng();window.setTimeout(function(){a.windowManager.confirm(b,function(b){a.selection.setRng(d),c(b)})},0)}function d(){var b={href:e,target:u.target?u.target:null,rel:u.rel?u.rel:null,\"class\":u[\"class\"]?u[\"class\"]:null,title:u.title?u.title:null};j?(a.focus(),m&&u.text!=k&&(\"innerText\"in j?j.innerText=u.text:j.textContent=u.text),w.setAttribs(j,b),v.select(j),a.undoManager.add()):m?a.insertContent(w.createHTML(\"a\",b,w.encode(u.text))):a.execCommand(\"mceInsertLink\",!1,b)}var e;return u=tinymce.extend(u,b.data),(e=u.href)?e.indexOf(\"@\")>0&&-1==e.indexOf(\"//\")&&-1==e.indexOf(\"mailto:\")?void c(\"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?\",function(a){a&&(e=\"mailto:\"+e),d()}):a.settings.link_assume_external_targets&&!/^\\w+:/i.test(e)||!a.settings.link_assume_external_targets&&/^\\s*www\\./i.test(e)?void c(\"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?\",function(a){a&&(e=\"http://\"+e),d()}):void d():void a.execCommand(\"unlink\")}})}a.addButton(\"link\",{icon:\"link\",tooltip:\"Insert/edit link\",shortcut:\"Meta+K\",onclick:b(d),stateSelector:\"a[href]\"}),a.addButton(\"unlink\",{icon:\"unlink\",tooltip:\"Remove link\",cmd:\"unlink\",stateSelector:\"a[href]\"}),a.addShortcut(\"Meta+K\",\"\",b(d)),a.addCommand(\"mceLink\",b(d)),this.showDialog=d,a.addMenuItem(\"link\",{icon:\"link\",text:\"Insert/edit link\",shortcut:\"Meta+K\",onclick:b(d),stateSelector:\"a[href]\",context:\"insert\",prependToContext:!0})});\n\n// textcolor\ntinymce.PluginManager.add(\"textcolor\",function(a){function b(b){var c;return a.dom.getParents(a.selection.getStart(),function(a){var d;(d=a.style[\"forecolor\"==b?\"color\":\"background-color\"])&&(c=d)}),c}function c(){var b,c,d=[];for(c=a.settings.textcolor_map||[\"000000\",\"Black\",\"993300\",\"Burnt orange\",\"333300\",\"Dark olive\",\"003300\",\"Dark green\",\"003366\",\"Dark azure\",\"000080\",\"Navy Blue\",\"333399\",\"Indigo\",\"333333\",\"Very dark gray\",\"800000\",\"Maroon\",\"FF6600\",\"Orange\",\"808000\",\"Olive\",\"008000\",\"Green\",\"008080\",\"Teal\",\"0000FF\",\"Blue\",\"666699\",\"Grayish blue\",\"808080\",\"Gray\",\"FF0000\",\"Red\",\"FF9900\",\"Amber\",\"99CC00\",\"Yellow green\",\"339966\",\"Sea green\",\"33CCCC\",\"Turquoise\",\"3366FF\",\"Royal blue\",\"800080\",\"Purple\",\"999999\",\"Medium gray\",\"FF00FF\",\"Magenta\",\"FFCC00\",\"Gold\",\"FFFF00\",\"Yellow\",\"00FF00\",\"Lime\",\"00FFFF\",\"Aqua\",\"00CCFF\",\"Sky blue\",\"993366\",\"Red violet\",\"FFFFFF\",\"White\",\"FF99CC\",\"Pink\",\"FFCC99\",\"Peach\",\"FFFF99\",\"Light yellow\",\"CCFFCC\",\"Pale green\",\"CCFFFF\",\"Pale cyan\",\"99CCFF\",\"Light sky blue\",\"CC99FF\",\"Plum\"],b=0;b'+(c?\"×\":\"\")+\"
\"}var d,e,f,g,h,k,l,m=this,n=m._id,o=0;for(d=c(),d.push({text:tinymce.translate(\"No color\"),color:\"transparent\"}),f='',g=d.length-1,k=0;j>k;k++){for(f+=\"\",h=0;i>h;h++)l=k*i+h,l>g?f+=\" \":(e=d[l],f+=b(e.color,e.text));f+=\" \"}if(a.settings.color_picker_callback){for(f+=''+tinymce.translate(\"Custom...\")+\"
\",f+=\"\",h=0;i>h;h++)f+=b(\"\",\"Custom color\");f+=\" \"}return f+=\"
\"}function e(b,c){a.undoManager.transact(function(){a.focus(),a.formatter.apply(b,{value:c}),a.nodeChanged()})}function f(b){a.undoManager.transact(function(){a.focus(),a.formatter.remove(b,{value:null},null,!0),a.nodeChanged()})}function g(c){function d(a){k.hidePanel(),k.color(a),e(k.settings.format,a)}function g(){k.hidePanel(),k.resetColor(),f(k.settings.format)}function h(a,b){a.style.background=b,a.setAttribute(\"data-mce-color\",b)}var j,k=this.parent();tinymce.DOM.getParent(c.target,\".mce-custom-color-btn\")&&(k.hidePanel(),a.settings.color_picker_callback.call(a,function(a){var b,c,e,f=k.panel.getEl().getElementsByTagName(\"table\")[0];for(b=tinymce.map(f.rows[f.rows.length-1].childNodes,function(a){return a.firstChild}),e=0;ee;e++)h(b[e],b[e+1].getAttribute(\"data-mce-color\"));h(c,a),d(a)},b(k.settings.format))),j=c.target.getAttribute(\"data-mce-color\"),j?(this.lastId&&document.getElementById(this.lastId).setAttribute(\"aria-selected\",!1),c.target.setAttribute(\"aria-selected\",!0),this.lastId=c.target.id,\"transparent\"==j?g():d(j)):null!==j&&k.hidePanel()}function h(){var a=this;a._color?e(a.settings.format,a._color):f(a.settings.format)}var i,j;j=a.settings.textcolor_rows||5,i=a.settings.textcolor_cols||8,a.addButton(\"forecolor\",{type:\"colorbutton\",tooltip:\"Text color\",format:\"forecolor\",panel:{role:\"application\",ariaRemember:!0,html:d,onclick:g},onclick:h}),a.addButton(\"backcolor\",{type:\"colorbutton\",tooltip:\"Background color\",format:\"hilitecolor\",panel:{role:\"application\",ariaRemember:!0,html:d,onclick:g},onclick:h})});\n\n// fullpage\ntinymce.PluginManager.add(\"fullpage\",function(a){function b(){var b=c();a.windowManager.open({title:\"Document properties\",data:b,defaults:{type:\"textbox\",size:40},body:[{name:\"title\",label:\"Title\"},{name:\"keywords\",label:\"Keywords\"},{name:\"description\",label:\"Description\"},{name:\"robots\",label:\"Robots\"},{name:\"author\",label:\"Author\"},{name:\"docencoding\",label:\"Encoding\"}],onSubmit:function(a){d(tinymce.extend(b,a.data))}})}function c(){function b(a,b){var c=a.attr(b);return c||\"\"}var c,d,f=e(),g={};return g.fontface=a.getParam(\"fullpage_default_fontface\",\"\"),g.fontsize=a.getParam(\"fullpage_default_fontsize\",\"\"),c=f.firstChild,7==c.type&&(g.xml_pi=!0,d=/encoding=\"([^\"]+)\"/.exec(c.value),d&&(g.docencoding=d[1])),c=f.getAll(\"#doctype\")[0],c&&(g.doctype=\"\"),c=f.getAll(\"title\")[0],c&&c.firstChild&&(g.title=c.firstChild.value),k(f.getAll(\"meta\"),function(a){var b,c=a.attr(\"name\"),d=a.attr(\"http-equiv\");c?g[c.toLowerCase()]=a.attr(\"content\"):\"Content-Type\"==d&&(b=/charset\\s*=\\s*(.*)\\s*/gi.exec(a.attr(\"content\")),b&&(g.docencoding=b[1]))}),c=f.getAll(\"html\")[0],c&&(g.langcode=b(c,\"lang\")||b(c,\"xml:lang\")),g.stylesheets=[],tinymce.each(f.getAll(\"link\"),function(a){\"stylesheet\"==a.attr(\"rel\")&&g.stylesheets.push(a.attr(\"href\"))}),c=f.getAll(\"body\")[0],c&&(g.langdir=b(c,\"dir\"),g.style=b(c,\"style\"),g.visited_color=b(c,\"vlink\"),g.link_color=b(c,\"link\"),g.active_color=b(c,\"alink\")),g}function d(b){function c(a,b,c){a.attr(b,c?c:void 0)}function d(a){g.firstChild?g.insert(a,g.firstChild):g.append(a)}var f,g,h,j,m,n=a.dom;f=e(),g=f.getAll(\"head\")[0],g||(j=f.getAll(\"html\")[0],g=new l(\"head\",1),j.firstChild?j.insert(g,j.firstChild,!0):j.append(g)),j=f.firstChild,b.xml_pi?(m='version=\"1.0\"',b.docencoding&&(m+=' encoding=\"'+b.docencoding+'\"'),7!=j.type&&(j=new l(\"xml\",7),f.insert(j,f.firstChild,!0)),j.value=m):j&&7==j.type&&j.remove(),j=f.getAll(\"#doctype\")[0],b.doctype?(j||(j=new l(\"#doctype\",10),b.xml_pi?f.insert(j,f.firstChild):d(j)),j.value=b.doctype.substring(9,b.doctype.length-1)):j&&j.remove(),j=null,k(f.getAll(\"meta\"),function(a){\"Content-Type\"==a.attr(\"http-equiv\")&&(j=a)}),b.docencoding?(j||(j=new l(\"meta\",1),j.attr(\"http-equiv\",\"Content-Type\"),j.shortEnded=!0,d(j)),j.attr(\"content\",\"text/html; charset=\"+b.docencoding)):j&&j.remove(),j=f.getAll(\"title\")[0],b.title?(j?j.empty():(j=new l(\"title\",1),d(j)),j.append(new l(\"#text\",3)).value=b.title):j&&j.remove(),k(\"keywords,description,author,copyright,robots\".split(\",\"),function(a){var c,e,g=f.getAll(\"meta\"),h=b[a];for(c=0;c\"))}function e(){return new tinymce.html.DomParser({validate:!1,root_name:\"#document\"}).parse(i)}function f(b){function c(a){return a.replace(/<\\/?[A-Z]+/g,function(a){return a.toLowerCase()})}var d,f,h,l,m=b.content,n=\"\",o=a.dom;if(!b.selection&&!(\"raw\"==b.format&&i||b.source_view&&a.getParam(\"fullpage_hide_in_source_view\"))){0!==m.length||b.source_view||(m=tinymce.trim(i)+\"\\n\"+tinymce.trim(m)+\"\\n\"+tinymce.trim(j)),m=m.replace(/<(\\/?)BODY/gi,\"<$1body\"),d=m.indexOf(\"\",d),i=c(m.substring(0,d+1)),f=m.indexOf(\"\\n\"),h=e(),k(h.getAll(\"style\"),function(a){a.firstChild&&(n+=a.firstChild.value)}),l=h.getAll(\"body\")[0],l&&o.setAttribs(a.getBody(),{style:l.attr(\"style\")||\"\",dir:l.attr(\"dir\")||\"\",vLink:l.attr(\"vlink\")||\"\",link:l.attr(\"link\")||\"\",aLink:l.attr(\"alink\")||\"\"}),o.remove(\"fullpage_styles\");var p=a.getDoc().getElementsByTagName(\"head\")[0];n&&(o.add(p,\"style\",{id:\"fullpage_styles\"},n),l=o.get(\"fullpage_styles\"),l.styleSheet&&(l.styleSheet.cssText=n));var q={};tinymce.each(p.getElementsByTagName(\"link\"),function(a){\"stylesheet\"==a.rel&&a.getAttribute(\"data-mce-fullpage\")&&(q[a.href]=a)}),tinymce.each(h.getAll(\"link\"),function(a){var b=a.attr(\"href\");q[b]||\"stylesheet\"!=a.attr(\"rel\")||o.add(p,\"link\",{rel:\"stylesheet\",text:\"text/css\",href:b,\"data-mce-fullpage\":\"1\"}),delete q[b]}),tinymce.each(q,function(a){a.parentNode.removeChild(a)})}}function g(){var b,c=\"\",d=\"\";return a.getParam(\"fullpage_default_xml_pi\")&&(c+='\\n'),c+=a.getParam(\"fullpage_default_doctype\",\"\"),c+=\"\\n\\n\\n\",(b=a.getParam(\"fullpage_default_title\"))&&(c+=\"\"+b+\" \\n\"),(b=a.getParam(\"fullpage_default_encoding\"))&&(c+=' \\n'),(b=a.getParam(\"fullpage_default_font_family\"))&&(d+=\"font-family: \"+b+\";\"),(b=a.getParam(\"fullpage_default_font_size\"))&&(d+=\"font-size: \"+b+\";\"),(b=a.getParam(\"fullpage_default_text_color\"))&&(d+=\"color: \"+b+\";\"),c+=\"\\n\\n\"}function h(b){b.selection||b.source_view&&a.getParam(\"fullpage_hide_in_source_view\")||(b.content=tinymce.trim(i)+\"\\n\"+tinymce.trim(b.content)+\"\\n\"+tinymce.trim(j))}var i,j,k=tinymce.each,l=tinymce.html.Node;a.addCommand(\"mceFullPageProperties\",b),a.addButton(\"fullpage\",{title:\"Document properties\",cmd:\"mceFullPageProperties\"}),a.addMenuItem(\"fullpage\",{text:\"Document properties\",cmd:\"mceFullPageProperties\",context:\"file\"}),a.on(\"BeforeSetContent\",f),a.on(\"GetContent\",h)});\n\n}(this);\n\ndefine(\"tinymce\", [\"jquery\"], (function (global) {\n return function () {\n var ret, fn;\n fn = function () {\n this.tinyMCE.DOM.events.domLoaded = true;\n return this.tinyMCE;\n };\n ret = fn.apply(global, arguments);\n return ret || global.tinyMCE;\n };\n}(this)));\n\n","define('modules/common/toggle-switch-directive',['angular'], function (angular) {\n\n return angular.module('bl.toggle-switch', [])\n .directive('toggleSwitch', function () {\n return {\n restrict: 'E',\n scope: { value:'=', text1: '=', text2: '=', text3:'=', text4:'=', disableToggle: '=' },\n templateUrl: '/js/modules/common/toggle-switch.tpl.html'\n };\n });\n});\n","define('modules/contact/modals/request-payment-information-ctrl',['./../module', 'angular', 'underscore', 'moment'], function(module, angular, _, moment) {\n 'use strict';\n\n module.controller('RequestPaymentInformationCtrl', function($scope, $filter, $q, $modalInstance, $timeout, $state, $window, $stateParams, $translate, $interval, Account, AppLicense, EmailTemplate, SMSTemplate, TransactionPaymentsModalService, SMS, Enums, Email, User, MergeFields, ContactUpdateRequest, CompanySetting, alerts, modalService, companyProfile, emailTemplates, smsTemplates, twilioSettings, contact, posSettings, paymentPlans) {\n\n $scope.twilioSettings = twilioSettings;\n $scope.request = { sendEmail: false, sendSms: false, entityId: null, text3: $translate.instant('common.contact'), text4: $translate.instant('payment-plans.payment-plan'), updatePaymentPlan: false, allowedToSendEmail: true };\n $scope.email = {};\n $scope.sms = {};\n\n initPaymentPlans($scope);\n\n $scope.sent = {\n email: false,\n sms: false\n };\n\n $scope.hasAllStripeSettings = TransactionPaymentsModalService.hasAllStripeSettings(posSettings);\n $scope.hasAllChargeItProSettings = TransactionPaymentsModalService.hasAllChargeItProSettings(posSettings);\n $scope.hasAllFullSteamSettings = TransactionPaymentsModalService.hasAllFullSteamSettings(posSettings);\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n var licenseStatus = AppLicense.get()[\"status\"];\n $scope.request.allowedToSendEmail = licenseStatus === 'A';\n\n $scope.onlinePaymentAccess = licenseHasPortalAccess || $scope.hasAllFullSteamSettings || $scope.hasAllChargeItProSettings || $scope.hasAllStripeSettings;\n\n var paymentInfoRequestMergeFields = _.where(MergeFields, { paymentInfoRequest: true });\n\n buildTemplates(emailTemplates, smsTemplates);\n\n $scope.installPaymentInfoTemplates = function() {\n Account.installPaymentInfoTemplates(function() {\n var emailTemplatePromise = EmailTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n var smsTemplatePromise = SMSTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n\n $q.all([emailTemplatePromise, smsTemplatePromise]).then(function(resources) {\n if(resources && resources.length) {\n buildTemplates(resources[0], resources[1]);\n alerts.push('alerts.settings.account.successfully-installed-payment-info-templates');\n }\n });\n });\n };\n\n $scope.state = {\n isContactChosen: false\n };\n\n $scope.existingContact = {\n contactName: null\n };\n\n var chosenContact;\n\n $scope.chooseContact = function(contact) {\n if(contact) {\n $scope.state.isContactChosen = true;\n $scope.contact = {};\n\n $scope.existingContact.contactName = contact.firstName + \" \" + (contact.lastName || '');\n\n angular.extend($scope.contact, contact);\n\n chosenContact = angular.copy($scope.contact);\n }\n };\n\n $scope.unselectContact = function() {\n chosenContact = undefined;\n delete $scope.contact;\n\n $scope.existingContact.contactName = null;\n\n $scope.state.isContactChosen = false;\n $scope.request.sendEmail = false;\n $scope.request.sendSms = false;\n };\n\n $scope.chooseContact(contact);\n\n $scope.senders = [\n {\n email: companyProfile.emailAddress,\n name: companyProfile.name,\n id: companyProfile.id\n },\n {\n email: User.emailAddress,\n name: User.firstName + ' ' + User.lastName,\n id: User.id\n }\n ];\n\n $scope.email.sender = $scope.senders[0];\n\n $scope.selectEmailTemplate = function(template) {\n $scope.selectedEmailTemplate = template ? template : null;\n $scope.email.message = template && template.body ? template.body : '';\n $scope.email.subject = template && template.subject ? template.subject : '';\n $scope.email.contentType = template && template.contentType ? template.contentType : 'text/html';\n };\n\n $scope.selectSmsTemplate = function(template) {\n $scope.selectedSmsTemplate = template ? template : null;\n $scope.sms.message = template && template.body ? template.body : '';\n };\n\n $scope.selectPaymentPlan = function(plan) {\n $scope.selectedPaymentPlan = plan ? plan : null;\n $scope.request.entityId = $scope.selectedPaymentPlan;\n };\n\n $scope.requestForPaymentInfo = function() {\n if(licenseStatus !== 'A') {\n $scope.request.sendEmail = false;\n }\n\n if(!$scope.request.updatePaymentPlan) {\n $scope.request.entityId = null;\n }\n\n //if payment plan and no payment plan selected request entity\n if($scope.request.updatePaymentPlan && !$scope.request.entityId) {\n // Error message, plan required...\n modalService.showMessage('alerts.request-payment.plan-required', 'modal-service.error');\n return false;\n }\n\n if(!$scope.contact) {\n // Error message, contact required...\n modalService.showMessage('alerts.request-payment.contact-required', 'modal-service.error');\n return false;\n }\n\n if(!$scope.request.sendEmail && !$scope.request.sendSms) {\n // Error message, Delivery Method Required\n modalService.showMessage('alerts.request-payment.delivery-method-required', 'modal-service.error');\n return false;\n }\n\n if($scope.request.sendEmail && !$scope.selectedEmailTemplate) {\n // Error message to please select a email template...\n modalService.showMessage('alerts.request-payment.email-template-required', 'modal-service.error');\n return false;\n }\n\n if($scope.request.sendSms && !$scope.selectedSmsTemplate) {\n // Error message to please select a sms template...\n modalService.showMessage('alerts.request-payment.sms-template-required', 'modal-service.error');\n return false;\n }\n\n var request = {\n requestEntityId: $scope.request.entityId,\n contactId: $scope.contact.id\n };\n\n ContactUpdateRequest.validateBeforeCreatingPaymentRequest(request, function (existingRequest) {\n if(existingRequest && existingRequest.id) {\n modalService.showConfirmation('contact.messages.request-again?', function () {\n createRequestAndSend(request);\n });\n } else {\n createRequestAndSend(request);\n }\n }, function (data, status) {\n ContactUpdateRequest.defaultErrorHandler(data, status);\n });\n };\n\n function sendMessages() {\n var email;\n if ($scope.request.sendEmail) {\n email = {\n recipientId: $scope.contact.id,\n recipientType: Email.CONTACT_TYPE,\n subject: $scope.email.subject,\n message: $scope.email.message,\n cc: $scope.email.cc,\n contentType: $scope.email.contentType,\n senderEmail: $scope.email.sender.email,\n senderEmployeeId: User.id,\n senderName: $scope.email.sender.name,\n recipientEmail: $scope.contact.emailAddress,\n recipientName: $scope.contact.firstName + ' ' + $scope.contact.lastName,\n status: Enums.statuses.sent.value,\n createdByUser: User.username,\n salesAgreementId: $scope.selectedAgreement ? $scope.selectedAgreement.id : null\n };\n }\n\n var sms;\n var smss = [];\n if ($scope.request.sendSms) {\n sms = {\n message: $scope.sms.message,\n toPhoneNumber: $scope.contact.mobilePhoneNumber,\n employeeId: User.id,\n fromPhoneNumber: twilioSettings.phoneNumber,\n contactId: $scope.contact.id,\n deliverOn: new Date(),\n status: Enums.statuses.sent.value,\n createdByUser: User.username,\n salesAgreementId: $scope.selectedAgreement ? $scope.selectedAgreement.id : null\n };\n\n smss.push(sms);\n }\n\n if($scope.request.sendEmail && $scope.request.sendSms) {\n $scope.sending = true;\n\n // send email and sms, close modal\n $q.all([\n Email.send(email),\n SMS.sendMultiple(smss)\n ]).then(function (responses) {\n $scope.sending = false;\n\n if (responses[0]) {\n $scope.sent.email = true;\n $scope.request.sendEmail = false;\n alerts.pushSuccess('alerts.contact.success-your-email-is-being-sent');\n }\n\n if (responses[1].errors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: {message: responses[1].errors[0].message}\n });\n } else if (responses[1].optedErrors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: {message: responses[1].optedErrors[0].message}\n });\n } else {\n $scope.sent.sms = true;\n $scope.request.sendSms = false;\n alerts.pushSuccess({\n key: 'alerts.contact.successfully-sent-sms',\n params: {number: responses[1].successes.length}\n });\n }\n\n if ($scope.sent.email && $scope.sent.sms) {\n $scope.$close();\n }\n\n }).catch(function (error) {\n console.log(error);\n $scope.sending = false;\n });\n } else if($scope.request.sendEmail) {\n $scope.sending = true;\n\n // send email, close modal\n Email.send(email, function (emailResult) {\n $scope.sending = false;\n alerts.pushSuccess('alerts.contact.success-your-email-is-being-sent');\n $scope.$close();\n\n }, function (err) {\n Email.defaultErrorHandler(err);\n $scope.sending = false;\n });\n } else if($scope.request.sendSms) {\n $scope.sending = true;\n\n // send, close, pass result\n var sendSMS = SMS.sendMultiple(smss);\n\n sendSMS.then(function (twilioResult) {\n $scope.sending = false;\n\n if (twilioResult.errors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: {message: twilioResult.errors[0].message}\n });\n } else if (twilioResult.optedErrors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: {message: twilioResult.optedErrors[0].message}\n });\n } else {\n alerts.pushSuccess({\n key: 'alerts.contact.successfully-sent-sms',\n params: {number: twilioResult.successes.length}\n });\n\n $scope.$close();\n }\n }, function (err) {\n SMS.defaultErrorHandler(err);\n $scope.sending = false;\n });\n }\n }\n\n function createRequestAndSend(request) {\n ContactUpdateRequest.requestPaymentInformationForContact(request, function () {\n sendMessages();\n }, function () {\n $scope.sending = false;\n });\n }\n\n function buildTemplates(emailTemps, smsTemps) {\n $scope.emailTemplates = [];\n $scope.smsTemplates = [];\n\n if(emailTemps && emailTemps.length) {\n _.each(paymentInfoRequestMergeFields, function(mergeField) {\n _.each(emailTemps, function(template) {\n if(template && template.body) {\n if(includesValue(template.body, mergeField.key)) {\n if($scope.emailTemplates.indexOf(template) === -1) {\n $scope.emailTemplates.push(template);\n }\n }\n }\n });\n });\n }\n\n if(smsTemps && smsTemps.length) {\n _.each(paymentInfoRequestMergeFields, function(mergeField) {\n _.each(smsTemps, function(template) {\n if(template && template.body) {\n if(includesValue(template.body, mergeField.key)) {\n if($scope.smsTemplates.indexOf(template) === -1) {\n $scope.smsTemplates.push(template);\n }\n }\n }\n });\n });\n }\n }\n\n function includesValue(container, value) {\n var returnValue = false;\n var pos = container.indexOf(value);\n if(pos >= 0) {\n returnValue = true;\n }\n return returnValue;\n }\n\n function initPaymentPlans($scope) {\n paymentPlans = _(paymentPlans).filter(function(plan) {\n return plan.status !== Enums.statuses.complete.value;\n });\n\n $scope.paymentPlans = _(paymentPlans).map(function (plan) {\n var trx = $filter('transactionTypeCode')(plan.trxTypeId, plan.status);\n plan.trxDescription = $translate.instant(trx) + ' #' + plan.trxNumber;\n return plan;\n });\n\n if(paymentPlans.length === 1) {\n $scope.request.text4 += ' ' + $scope.paymentPlans[0].trxDescription;\n }\n }\n\n $scope.$watch('request.updatePaymentPlan', function (value) {\n if(value && $scope.paymentPlans && $scope.paymentPlans.length === 1) {\n $scope.request.entityId = $scope.paymentPlans[0].id;\n $scope.selectedPaymentPlan = $scope.request.entityId;\n } else {\n $scope.request.entityId = null;\n }\n });\n\n $scope.gotoContact = function() {\n $scope.$close();\n $state.go('contact.personal_info', { id: $scope.contact.id });\n };\n\n });\n});\n\n","define('modules/dashboard/attention-items-ctrl',['./module', 'moment'], function (module, moment) {\n 'use strict';\n\n module.controller('AttentionItemsCtrl', function ($scope, $q, $translate, $rootScope, eventsBus, WaitList, Appointment, Task, Transaction, ReceivingVoucher, PurchaseOrder, CompanySetting, UtilsHelper, Enums, security, PaymentPlan) {\n $scope.attentionItems = [];\n\n /**\n * Init\n */\n\n var poSettings = null;\n initAttentionItems();\n\n var initResources = function (resources, indexes) {\n var incompleteAppts = resources[0];\n var pastDueTasks = resources[1];\n var listPendingSales = resources[2];\n var listPendingSOPastDueEventDate = resources[3];\n var listPendingROPastDue = resources[4];\n var listPendingWaitList = resources[5];\n var listPendingPaymentPlans = resources[6];\n var listErrorPaymentPlans = resources[7];\n var listLatePurchaseOrders = indexes.po ? resources[indexes.po] : {};\n\n if(incompleteAppts.total) {\n $scope.attentionItems.push({\n text: 'appointment.incomplete-past',\n count: incompleteAppts.total,\n url: '#/appointments/incomplete-past'\n });\n }\n\n if(pastDueTasks.total) {\n $scope.attentionItems.push({\n text: 'task.past-due',\n count: pastDueTasks.total,\n url: '#/tasks/past-due'\n });\n }\n\n if(listPendingSales.total) {\n $scope.attentionItems.push({\n text: 'transaction.pending-sales',\n count: listPendingSales.total,\n url: '#/transactions?typeId=1&status=P'\n });\n }\n\n if(listPendingSOPastDueEventDate.total) {\n $scope.attentionItems.push({\n text: 'transaction.pending-so-event-date-past-due',\n count: listPendingSOPastDueEventDate.total,\n url: '#/transactions?typeId=2&status=P&eventDateBefore=' + moment().format('YYYY-MM-DD')\n });\n }\n\n if(listPendingROPastDue.total) {\n $scope.attentionItems.push({\n text: 'transaction.pending-receiving-vouchers',\n count: listPendingROPastDue.total,\n url: '#/receiving-vouchers?status=P'\n });\n }\n\n if(listLatePurchaseOrders && listLatePurchaseOrders.total) {\n $scope.attentionItems.push({\n text: 'purchase-order.late-purchase-orders',\n count: listLatePurchaseOrders.total,\n url: '#/purchase-orders/past-ship-date?shipDateBefore=' + moment().subtract(UtilsHelper.getDaysFromWeeks(poSettings, CompanySetting.PO_PENDING_PO_PAST_SHIPPING_WINDOW), 'days').format('YYYY-MM-DD')\n });\n }\n\n if(listPendingWaitList && listPendingWaitList.total) {\n $scope.attentionItems.push({\n text: 'wait-list.pending-for-today',\n count: listPendingWaitList.total,\n url: '#/wait-list?startDateTime=' + moment().startOf('day').format() + '&endDateTime=' + moment().endOf('day').format()\n });\n }\n\n if(listPendingPaymentPlans && listPendingPaymentPlans.total) {\n $scope.attentionItems.push({\n text: 'payment-plans.payment-plan-installment-due',\n count: listPendingPaymentPlans.total,\n url: '#/payment-plans?status=A&autoPayFlag=N&installmentDateBefore=' + moment().startOf('day').format('YYYY-MM-DD')\n });\n }\n\n if(listErrorPaymentPlans && listErrorPaymentPlans.total) {\n $scope.attentionItems.push({\n text: 'payment-plans.error-payment-plans',\n count: listErrorPaymentPlans.total,\n url: '#/payment-plans?status=' + Enums.statuses.pending.value + '&installmentStatus=' + Enums.statuses.error.value\n });\n }\n };\n\n function initAttentionItems() {\n CompanySetting.getPurchasingSettings(init, init);\n }\n\n function init(settings) {\n poSettings = settings;\n var indexes = { po: null };\n var incompleteApptsPromise = Appointment.incompletePastAppointments();\n var pastDueTasksPromise = Task.pastDueTasks();\n var listPendingSalesPromise = Transaction.listPendingSales();\n var listPendingSOPastDueEventDatePromise = Transaction.listPendingSOPastDueEventDate();\n var listPendingROPastDuePromise = ReceivingVoucher.listPendingROPastDue();\n var listLatePurchaseOrdersPromise = PurchaseOrder.pastShipDatePOs(settings);\n var listPendingWaitListTodayPromise = WaitList.query({ filterObject:{ status:Enums.statuses.pending.value, startDateTime: moment().startOf('day').format(), endDateTime: moment().endOf('day').format() }});\n var listPendingPaymentPlansPromise = PaymentPlan.query({ filterObject:{ autoPayFlag: \"N\", includePreviousAndNextInstallments: true, status: Enums.statuses.active.value, installmentDateBefore: moment().startOf('day').format('YYYY-MM-DD') }});\n var listErrorPaymentPlansPromise = PaymentPlan.query({ filterObject:{ status: Enums.statuses.pending.value, installmentStatus: Enums.statuses.error.value }});\n\n var promises = [incompleteApptsPromise, pastDueTasksPromise, listPendingSalesPromise, listPendingSOPastDueEventDatePromise, listPendingROPastDuePromise, listPendingWaitListTodayPromise, listPendingPaymentPlansPromise, listErrorPaymentPlansPromise];\n\n if(UtilsHelper.getDaysFromWeeks(poSettings, CompanySetting.PO_PENDING_PO_PAST_SHIPPING_WINDOW) !== 1) {\n indexes.po = promises.length;\n promises.push(listLatePurchaseOrdersPromise);\n }\n\n $q.all(promises).then(function(results) {\n initResources(results, indexes);\n });\n }\n\n });\n});\n\n","define('modules/resources/appointment-block',['angular', './backend-resource'], function (angular) {\n 'use strict';\n\n return angular.module('bl.resources.appointment-block', ['bl.resources.backend-resource'])\n .factory('AppointmentBlock', function (backendResource, DateTimeTzService, $http) {\n\n var resource = backendResource('appointmentBlocks', true);\n\n /**\n *\n * @param appointmentBlockId - the id of the block to find the appointment types for\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.getAppointmentTypesForBlock = function (appointmentBlockId, successcb, errorcb) {\n var httpPromise = $http.get(resource.url + '/getAppointmentTypesForBlock/' + appointmentBlockId, null);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n /**\n *\n * @param appointmentBlockId - the id of the block to find the appointment types for\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.getTypesForBlock = function (appointmentBlockId, successcb, errorcb) {\n var httpPromise = $http.get(resource.url + '/getAppointmentTypesForBlock/' + appointmentBlockId, null);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n /**\n *\n * @param request\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.addTypesForBlock = function (request, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/addAppointmentTypesForBlock', request);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n /**\n *\n * @param dayOfWeekId - the id of the day that we want to delete the blocks for\n * @param fittingRoomId\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.removeBlocksForDayOfWeek = function (dayOfWeekId, fittingRoomId, successcb, errorcb) {\n var httpPromise = $http.delete(resource.url + '/deleteForDayOfWeek/' + dayOfWeekId + '/' + fittingRoomId);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n /**\n * @param fittingRoomId\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.deleteAllBlocksForFittingRoom = function (fittingRoomId, successcb, errorcb) {\n var httpPromise = $http.delete(resource.url + '/deleteAllBlocksForFittingRoom/' + fittingRoomId);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n /**\n * @param fittingRoomId\n * @param blockIds\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.bulkDelete = function (fittingRoomId, blockIds, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/bulkDelete/' + fittingRoomId, blockIds);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n /**\n * @param blockIds\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.bulkDeleteByIds = function (blockIds, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/bulkDeleteByIds/', blockIds);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n /**\n * @param blockIds\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.getAppointmentBlockTypes = function (blockIds, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/getAppointmentBlockTypes', blockIds);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n /**\n * @param request\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.deleteAppointmentBlockType = function (request, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/deleteAppointmentBlockType', request);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n /**\n *\n * @param blockId\n * @param typeId\n * @param successcb\n * @param errorcb\n * @returns {*}\n */\n resource.getAppointmentBlockType = function (blockId, typeId, successcb, errorcb) {\n var httpPromise = $http.get(resource.url + '/getAppointmentBlockType/' + blockId + '/' + typeId);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n /**\n *\n * @param filterObj\n * @param successcb\n * @param errorcb\n * @returns {*} ListResultModel\n */\n resource.listResultModel = function(filterObj, successcb, errorcb) {\n var params = {};\n\n if(filterObj && filterObj.filterObject) {\n params = angular.isObject(filterObj.filterObject) ? JSON.stringify(filterObj.filterObject) : {};\n\n // if(params && params.startTime) {\n // params.startTime = DateTimeTzService.formatTime(params.startTime);\n // }\n\n // if(params && params.endTime) {\n // params.endTime = DateTimeTzService.formatTime(params.endTime);\n // }\n // 11 -> 11:00am 11:00\n }\n\n var queryParameters = resource.buildQueryParameters(filterObj);\n var httpPromise = $http.post(resource.url + '/listResultModel?' + queryParameters.join('&'), params);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n return resource;\n });\n});\n\n","define('modules/settings/fitting-room/events/setup-event-blocks-ctrl',[\n 'angular',\n 'underscore',\n 'moment',\n '../../../common/enums-service',\n '../../../modal/modal-service',\n '../../../common/filters',\n '../../../resources/fitting-room',\n '../../../resources/appointment-block-event',\n '../../../calendar/fullcalendar-helper-service'\n], function (angular, _, moment) {\n 'use strict';\n\n return angular.module('bl.settings.fitting-room.setup-event-blocks', [\n 'bl.enums',\n 'bl.filters',\n 'bl.resources.appointment-block-event',\n 'bl.resources.fitting-room',\n 'bl.modal'\n ]).controller('FittingRoomEventBlockSetupCtrl', function ($scope, $controller, $modal, $translate, $locale, $q, User, alerts, FittingRoomService, FittingRoom, FittingRoomAppointmentType, AppointmentBlockEvent, fittingRoomEvent, fittingRooms, appt_types, Enums, modalService, FullcalendarHelper) {\n\n angular.extend(this, $controller('SearchBlockCtrl', {\n $scope: $scope,\n headers:[],\n modelView: 'blocks',\n Resource: AppointmentBlockEvent,\n fittingRoom: null,\n fittingRoomEvent: fittingRoomEvent,\n appt_types: appt_types,\n isModal: true,\n isEvent: true\n }));\n\n /**\n * Initialize Default Variables\n */\n\n $scope.isOpen = true;\n $scope.fittingRoomEvent = fittingRoomEvent;\n\n function fittingRoomEventDayOfWeek(event) {\n var dayOfWeeks = [];\n if(event.dayOfWeek) {\n dayOfWeeks.push(event.dayOfWeek);\n } else {\n var days = moment(event.endDate).diff(event.startDate, 'days') + 1;\n if(days >= 7) {\n dayOfWeeks = [1, 2, 3, 4, 5, 6, 7];\n } else {\n for(var i = 0; i < days; i++) {\n var date = moment(event.startDate).add(i, 'd').toDate();\n dayOfWeeks.push(moment(date).weekday() + 1);\n }\n }\n }\n\n return dayOfWeeks;\n }\n\n var rooms = mapFittingRooms(fittingRoomEvent, fittingRooms);\n\n var bookMethodTypes = {\n first: '' + $translate.instant('enums.booking-methods.first-available') + ' ',\n blocks: '' + $translate.instant('enums.booking-methods.blocks') + ' '\n };\n\n rooms = _(rooms).map(function(room) {\n room.type = room.bookingMethodId === 1 ? bookMethodTypes.blocks : bookMethodTypes.first;\n return room;\n });\n\n $scope.fittingRooms = rooms;\n $scope.fitting_rooms = angular.copy(rooms);\n\n $scope.appt_types = angular.copy(appt_types);\n $scope.block = initNewBlock($scope.fittingRoomEvent, $scope.appt_types);\n $scope.eventDays = fittingRoomEventDayOfWeek(fittingRoomEvent);\n $scope.days = FullcalendarHelper.initializeDaysChecked();\n $scope.days = mapDays($scope.days, $scope.eventDays);\n\n $scope.initialValues = { dayOfWeek: '', includeAppointmentTypeDetails: true, appointmentTypes: [], fittingRooms: [], fittingRoomEventId: fittingRoomEvent.id };\n $scope.formValues = angular.copy($scope.initialValues);\n\n /**\n * Add Fitting Room Block Methods\n */\n\n $scope.onAddBlock = _.debounce(function() {\n var block = _.omit($scope.block, 'appointmentTypeId');\n var days = _($scope.days).where({ isChecked: true, isDisabled: false });\n\n var savedPromises = [];\n _(days).each(function(d) {\n _(block.fittingRooms).each(function(room) {\n var clonedBlock = _(block).clone();\n clonedBlock.dayOfWeek = d.day;\n clonedBlock.fittingRoomId = room.id;\n savedPromises.push(clonedBlock.$save());\n });\n });\n\n $q.all(savedPromises).then(initBlock);\n }, 800, true);\n\n var initBlock = function() {\n $scope.runSearch();\n $scope.block.startTime = $scope.block.endTime;\n $scope.onStartTimeChange();\n };\n\n function initNewBlock(fittingRoomEvent, types) {\n return new AppointmentBlockEvent({\n appointmentTypeId: types && types.length ? types[0].id: null,\n fittingRoomEventId: fittingRoomEvent && fittingRoomEvent.id\n });\n }\n\n function mapFittingRooms(event, rooms) {\n var array = [];\n _(event.items).each(function(item) {\n var room = _(rooms).findWhere({ id: item.fittingRoomId });\n if(room && room.id) {\n array.push(room);\n }\n });\n return array;\n }\n\n function mapDays(days, eventDays) {\n _(days).each(function(value, key) {\n days[key].isDisabled = !_.contains(eventDays, days[key].day);\n });\n\n return days;\n }\n\n $scope.onStartTimeChange = function() {\n FittingRoomService.blocks.setEndTime($scope.block, $scope.appt_types);\n };\n\n $scope.onAppointmentTypeChange = function(data) {\n console.log('setup ctrl', data);\n };\n\n $scope.isValid = function(block) {\n return FittingRoomService.blocks.isValid(block, $scope.days);\n };\n\n $scope.isChecked = function(days) {\n return _(days).where({ isChecked: true }).length > 0;\n };\n\n $scope.clearAll = function() {\n $scope.days = FullcalendarHelper.initializeDaysChecked();\n $scope.days = mapDays($scope.days, $scope.eventDays);\n };\n\n $scope.getDayOfWeek = function(day) {\n return $translate.instant('settings.day-of-week.' + (day - 1));\n };\n\n /**\n * Blocks Search Methods\n */\n\n $scope.runSearch = function() {\n search();\n };\n\n function search() {\n AppointmentBlockEvent.listResultModel($scope.buildReportsObject(), function(resultModel) {\n $scope.updateResults(resultModel);\n });\n }\n\n /**\n * Watchers\n */\n\n var ignoreFirst = true;\n $scope.$watch('block.appointmentTypes', function() {\n if(ignoreFirst) {\n ignoreFirst = false;\n return;\n }\n\n $scope.onStartTimeChange();\n });\n\n });\n});\n\n","define('modules/settings/fitting-room/blocks/add-block-modal-ctrl',['angular', 'underscore', 'moment', '../module'], function (angular, _, moment, module) {\n 'use strict';\n module.controller('AddBlockModalCtrl', function ($scope, $locale, $q, modalService, FittingRoomService, FullcalendarHelper, AppointmentBlock, AppointmentBlockEvent, fittingRoom, fittingRoomEvent, appt_types, modelView) {\n\n $scope.appt_types = angular.copy(appt_types);\n $scope.fittingRoom = fittingRoom;\n $scope.fittingRoomEvent = fittingRoomEvent;\n $scope.block = initNewBlock($scope.fittingRoom, $scope.fittingRoomEvent, $scope.appt_types);\n $scope.days = FullcalendarHelper.initializeDaysChecked();\n\n /**\n * Handlers\n */\n\n $scope.onAddBlock = _.debounce(function () {\n var block = _.omit($scope.block, 'appointmentTypeId');\n\n if(FittingRoomService.blocks.isStartEndTimeValid(block, false)) {\n modalService.showMessageStaticModal('settings.employee.message.enter-validate-start-and-end-times', 'settings.employee.message.ooops');\n return;\n }\n\n var days = _($scope.days).where({ isChecked: true });\n\n var savedPromises = [];\n _(days).each(function(d) {\n var clonedBlock = _(block).clone();\n clonedBlock.dayOfWeek = d.day;\n savedPromises.push(clonedBlock.$save());\n });\n\n $q.all(savedPromises).then(closeModal);\n }, 800, true);\n\n $scope.onStartTimeChange = function() {\n FittingRoomService.blocks.setEndTime($scope.block, $scope.appt_types);\n };\n\n $scope.isValid = function(block) {\n return FittingRoomService.blocks.isValid(block, $scope.days);\n };\n\n $scope.isChecked = function(days) {\n return _(days).where({ isChecked: true }).length > 0;\n };\n\n $scope.clearAll = function() {\n $scope.days = FullcalendarHelper.initializeDaysChecked();\n };\n\n /**\n * Helpers\n */\n\n function closeModal(results) {\n $scope.$close({ blocks: results });\n }\n\n function initNewBlock(fittingRoom, fittingRoomEvent, types) {\n if(fittingRoomEvent && fittingRoomEvent.id) {\n return new AppointmentBlockEvent({\n appointmentTypeId: types && types.length ? types[0].id: null,\n fittingRoomId: fittingRoom && fittingRoom.id,\n fittingRoomEventId: fittingRoomEvent.id\n });\n } else {\n return new AppointmentBlock({\n appointmentTypeId: types && types.length ? types[0].id: null,\n fittingRoomId: fittingRoom && fittingRoom.id\n });\n }\n }\n\n /**\n * Watchers\n */\n\n var ignoreFirst = true;\n $scope.$watch('block.appointmentTypes', function() {\n if(ignoreFirst) {\n ignoreFirst = false;\n return;\n }\n\n $scope.onStartTimeChange();\n });\n });\n});\n\n","define('modules/resources/reports',['angular', 'moment', './backend-resource'], function (angular, moment) {\n 'use strict';\n\n angular.module('bl.resources.reports', ['bl.resources.backend-resource', 'bl.security.service'])\n\n .factory('Reports', function (backendResource, $http, $window, security, PrintHelper) {\n\n var resource = backendResource('reports');\n\n function handleExportOrPrintPDF(print, url) {\n if(print) {\n PrintHelper.printPDF(url);\n } else {\n $window.open(url);\n }\n }\n\n function encodeReportsCriteria(obj) {\n var encodedString = '';\n for(var key in obj) {\n if(encodedString.length !== 0) {\n encodedString += '&';\n }\n\n encodedString += key + '=' + encodeURIComponent(obj[key]);\n }\n return encodedString;\n }\n\n function addPaginationElements(criteria, reportsObj) {\n // allows 0 value\n if (typeof reportsObj.page !== 'undefined') {\n criteria.page = reportsObj.page;\n }\n if (reportsObj.size) {\n criteria.size = reportsObj.size;\n }\n if (reportsObj.sortField) {\n criteria.sortField = reportsObj.sortField;\n }\n if (reportsObj.sortDirection) {\n criteria.sortDirection = reportsObj.sortDirection;\n }\n }\n\n function buildReportsCriteria(reportsObj) {\n var criteria = {};\n if (reportsObj) {\n addPaginationElements(criteria, reportsObj);\n if (reportsObj.criteria.retailerId) {\n criteria.retailerId = reportsObj.criteria.retailerId;\n }\n if (reportsObj.criteria.departmentId) {\n criteria.departmentId = reportsObj.criteria.departmentId;\n }\n if (reportsObj.criteria.taxCodeId) {\n criteria.taxCodeId = reportsObj.criteria.taxCodeId;\n }\n if (reportsObj.criteria.trxTypeId) {\n criteria.trxTypeId = reportsObj.criteria.trxTypeId;\n }\n if (reportsObj.criteria.vendorId) {\n criteria.vendorId = reportsObj.criteria.vendorId;\n }\n if (reportsObj.criteria.appointmentTypeId) {\n criteria.appointmentTypeId = reportsObj.criteria.appointmentTypeId;\n }\n if (reportsObj.criteria.appointmentTypeIds) {\n criteria.appointmentTypeIds = reportsObj.criteria.appointmentTypeIds;\n }\n if (reportsObj.criteria.vendorIds) {\n criteria.vendorIds = reportsObj.criteria.vendorIds;\n }\n if (reportsObj.criteria.departmentIds) {\n criteria.departmentIds = reportsObj.criteria.departmentIds;\n }\n if (reportsObj.criteria.employeeId !== undefined) {\n criteria.employeeId = reportsObj.criteria.employeeId;\n }\n if (reportsObj.criteria.taskTypeId) {\n criteria.taskTypeId = reportsObj.criteria.taskTypeId;\n }\n if (reportsObj.criteria.taskStatus) {\n criteria.taskStatus = reportsObj.criteria.taskStatus;\n }\n if (reportsObj.criteria.year) {\n criteria.year = reportsObj.criteria.year;\n }\n if (reportsObj.criteria.month !== undefined) {\n criteria.month = reportsObj.criteria.month;\n }\n if (reportsObj.criteria.startDate) {\n criteria.startDate = moment(reportsObj.criteria.startDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.endDate) {\n criteria.endDate = moment(reportsObj.criteria.endDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.status) {\n criteria.status = reportsObj.criteria.status;\n }\n if (reportsObj.criteria.name) {\n criteria.name = reportsObj.criteria.name;\n }\n if (reportsObj.criteria.eventTypeId) {\n criteria.eventTypeId = reportsObj.criteria.eventTypeId;\n }\n if (reportsObj.criteria.eventType) {\n criteria.eventType = reportsObj.criteria.eventType;\n }\n if (reportsObj.criteria.poStartDate) {\n criteria.poStartDate = moment(reportsObj.criteria.poStartDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.poEndDate) {\n criteria.poEndDate = moment(reportsObj.criteria.poEndDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.shipStartDate) {\n criteria.shipStartDate = moment(reportsObj.criteria.shipStartDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.shipEndDate) {\n criteria.shipEndDate = moment(reportsObj.criteria.shipEndDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.rvStartDate) {\n criteria.rvStartDate = moment(reportsObj.criteria.rvStartDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.rvEndDate) {\n criteria.rvEndDate = moment(reportsObj.criteria.rvEndDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.trxStartDate) {\n criteria.trxStartDate = moment(reportsObj.criteria.trxStartDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.trxEndDate) {\n criteria.trxEndDate = moment(reportsObj.criteria.trxEndDate).format('YYYY-MM-DD');\n }\n if(reportsObj.criteria.trxBalanceDueZeroDateStart) {\n criteria.trxBalanceDueZeroDateStart = moment(reportsObj.criteria.trxBalanceDueZeroDateStart).format('YYYY-MM-DD');\n }\n if(reportsObj.criteria.trxBalanceDueZeroDateEnd) {\n criteria.trxBalanceDueZeroDateEnd = moment(reportsObj.criteria.trxBalanceDueZeroDateEnd).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.itemStatus) {\n criteria.itemStatus = reportsObj.criteria.itemStatus;\n }\n if (reportsObj.criteria.trxStatus) {\n criteria.trxStatus = reportsObj.criteria.trxStatus;\n }\n if (reportsObj.criteria.completedStartDate) {\n criteria.completedStartDate = moment(reportsObj.criteria.completedStartDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.completedEndDate) {\n criteria.completedEndDate = moment(reportsObj.criteria.completedEndDate).format('YYYY-MM-DD');\n }\n if (reportsObj.criteria.methodId) {\n criteria.methodId = reportsObj.criteria.methodId;\n }\n if (reportsObj.criteria.methodIds) {\n criteria.methodIds = reportsObj.criteria.methodIds;\n }\n if (reportsObj.criteria.categoryId) {\n criteria.categoryId = reportsObj.criteria.categoryId;\n }\n if (reportsObj.criteria.methodCategoryId != -1) {\n criteria.methodCategoryId = reportsObj.criteria.methodCategoryId;\n }\n if (reportsObj.criteria.excludePickup) {\n criteria.excludePickup = reportsObj.criteria.excludePickup;\n }\n if (reportsObj.criteria.sampleFlag) {\n criteria.sampleFlag = reportsObj.criteria.sampleFlag;\n }\n if (reportsObj.criteria.orderType) {\n criteria.orderType = reportsObj.criteria.orderType;\n }\n if (reportsObj.criteria.physicalInventoryId) {\n criteria.physicalInventoryId = reportsObj.criteria.physicalInventoryId;\n }\n if (reportsObj.criteria.trxItemStatus) {\n criteria.trxItemStatus = reportsObj.criteria.trxItemStatus;\n }\n if (reportsObj.criteria.showVoidedPayments) {\n criteria.showVoidedPayments = reportsObj.criteria.showVoidedPayments;\n }\n if (reportsObj.criteria.registerId) {\n criteria.registerId = reportsObj.criteria.registerId;\n }\n if (reportsObj.criteria.templateId) {\n criteria.templateId = reportsObj.criteria.templateId;\n }\n if (reportsObj.criteria.attributeIds) {\n criteria.attributeIds = reportsObj.criteria.attributeIds;\n }\n if (reportsObj.criteria.hasItemBeenScanned) {\n criteria.hasItemBeenScanned = reportsObj.criteria.hasItemBeenScanned;\n }\n if (reportsObj.criteria.finishOption >= 0 && reportsObj.criteria.finishOption != null && reportsObj.criteria.finishOption !== undefined) {\n criteria.finishOption = reportsObj.criteria.finishOption;\n }\n if (reportsObj.criteria.finishOptionDepartmentId) {\n criteria.finishOptionDepartmentId = reportsObj.criteria.finishOptionDepartmentId;\n }\n if (reportsObj.criteria.evaluationType) {\n criteria.evaluationType = reportsObj.criteria.evaluationType;\n }\n if (reportsObj.criteria.hasSalePrice) {\n criteria.hasSalePrice = reportsObj.criteria.hasSalePrice;\n }\n if (reportsObj.criteria.receiveDate) {\n criteria.receiveDate = moment(reportsObj.criteria.receiveDate).format('YYYY-MM-DD');\n }\n if(reportsObj.criteria.syncRecordId) {\n criteria.syncRecordId = reportsObj.criteria.syncRecordId;\n }\n if (reportsObj.criteria.departmentBreakdown) {\n criteria.departmentBreakdown = reportsObj.criteria.departmentBreakdown;\n }\n if (reportsObj.criteria.dateType) {\n criteria.dateType = reportsObj.criteria.dateType;\n }\n if (reportsObj.criteria.triedOnFlag) {\n criteria.triedOnFlag = reportsObj.criteria.triedOnFlag;\n }\n if(reportsObj.criteria.showTipPaymentsOnly) {\n criteria.showTipPaymentsOnly = reportsObj.criteria.showTipPaymentsOnly;\n }\n if (reportsObj.criteria.fundedFlag) {\n criteria.fundedFlag = reportsObj.criteria.fundedFlag;\n }\n if (reportsObj.criteria.apptStatus) {\n criteria.apptStatus = reportsObj.criteria.apptStatus;\n }\n if(reportsObj.criteria.dashboard) {\n criteria.dashboard = reportsObj.criteria.dashboard;\n }\n if(reportsObj.criteria.paymentReceivedMethod) {\n criteria.paymentReceivedMethod = reportsObj.criteria.paymentReceivedMethod;\n }\n if(reportsObj.criteria.endOfDay) {\n criteria.endOfDay = reportsObj.criteria.endOfDay;\n }\n if(reportsObj.criteria.commissionLevelType) {\n criteria.commissionLevelType = reportsObj.criteria.commissionLevelType;\n }\n if(reportsObj.criteria.percentPaid >= 0 && reportsObj.criteria.percentPaid <= 100) {\n criteria.percentPaid = reportsObj.criteria.percentPaid;\n }\n if(reportsObj.criteria.paymentStatus) {\n criteria.paymentStatus = reportsObj.criteria.paymentStatus;\n }\n if (reportsObj.criteria.reason) {\n criteria.reason = reportsObj.criteria.reason;\n }\n if (reportsObj.criteria.type) {\n criteria.type = reportsObj.criteria.type;\n }\n if (reportsObj.criteria.requirePaging) {\n criteria.requirePaging = reportsObj.criteria.requirePaging;\n }\n }\n return criteria;\n }\n\n resource.fundingReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/fundingReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.newFundingReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/newFundingReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.newFundingReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/newFundingReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.fundingReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/fundingReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.appointmentStatusCountByType = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/endOfDayReports/appointmentStatusCountByType?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, false);\n };\n\n resource.endOfDayReportsOverView = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/endOfDayReports/endOfDayReportsOverView?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, false);\n };\n\n resource.autoReconcileOverView = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/endOfDayReports/autoReconcileOverView?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.tipItemJournal = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/tipItemJournal?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.tipItemJournalExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/tipItemJournal.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.platformFeeJournal = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/platformFeeJournal?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.platformFeeJournalExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/platformFeeJournal.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.preAuthPaymentJournal = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/preAuthPaymentJournal?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.preAuthPaymentJournalExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/preAuthPaymentJournal.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.tipReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/tipReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.tipReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/tipReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.blPayPaymentSearch = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/blPayReports/paymentSearch?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.blPayPaymentSearchExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/blPayReports/paymentSearch.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.nf525JetReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/jetReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525DailyReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/dailyReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525MonthlyReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/monthlyReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525FiscalYearReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/fiscalYearReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525TicketReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/ticketReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525TransactionReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/transactionReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525DuplicateReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/duplicateReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525InvoiceReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/invoiceReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.nf525InvoiceSnapShotReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/nf525Reports/invoiceSnapShotReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.retrieveInvoiceDetails = function (documentId, success, error) {\n var httpPromise = $http.get(resource.url + \"/nf525Reports/retrieveInvoiceDetails/\" + documentId, null);\n return resource.thenFactoryMethod(httpPromise, success, error, false);\n };\n\n resource.qboSyncHistoryLog = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/auditLogs/qboSyncHistoryLog?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.qboSyncHistoryLogExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/auditLogs/qboSyncHistoryLog.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.smsHistoryLog = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/auditLogs/smsHistoryLog?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.smsHistoryLogExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/auditLogs/smsHistoryLog.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.emailHistoryLog = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/auditLogs/emailHistoryLog?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.emailHistoryLogExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/auditLogs/emailHistoryLog.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.registerHistoryLog = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/auditLogs/registerHistoryLog?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.registerHistoryLogExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/auditLogs/registerHistoryLog.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.cashDrawerHistoryLog = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/auditLogs/cashDrawerHistoryLog?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.cashDrawerHistoryLogExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/auditLogs/cashDrawerHistoryLog.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n \n resource.timesheet = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/timeSheet?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.timesheetExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/timeSheet.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.timePunchesByAssociate = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/timePunchesByAssociate?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.timePunchesByAssociateExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/timePunchesByAssociate.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.timeOffByAssociate = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/timeOffByAssociate\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.timeOffByAssociateExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/timeOffByAssociate.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.closingRatioAppointmentBased = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/closingRatioAppointmentBased?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.closingRatioAppointmentBasedExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/closingRatioAppointmentBased.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.closingRatioContactBased = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/closingRatioContactBased?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.closingRatioContactBasedExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/closingRatioContactBased.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.averageBudgetByMonth = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/contactReports/avgBudgetByMonth?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.averageBudgetByMonthExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/contactReports/avgBudgetByMonth.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.howHeardSummary = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/contactReports/howHeardSummary?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.howHeardSummaryExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/contactReports/howHeardSummary.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.howHeardEffectiveness = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/contactReports/howHeardEffectiveness?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.howHeardEffectivenessExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/contactReports/howHeardEffectiveness.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.salesPerHour = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/salesPerHour?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.salesPerHourExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/salesPerHour.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.salesPerformanceOverview = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/salesPerformanceOverview?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, false);\n };\n\n resource.salesPerformance = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/salesPerformance?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.salesPerformanceExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/salesPerformance.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.commissionReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/commissionReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.commissionReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/commissionReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.commissionItemJournal = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/associateReports/commissionItemJournal?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.commissionItemJournalExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/associateReports/commissionItemJournal.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.eventDateSummary = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/contactReports/eventDateSummary?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.eventDateSummaryExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/contactReports/eventDateSummary.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.eventLocationSummary = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/contactReports/eventLocationSummary?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.eventLocationSummaryExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/contactReports/eventLocationSummary.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.appointmentCountByDay = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/activityReports/appointmentCountByDay?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.appointmentCountByDayExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/activityReports/appointmentCountByDay.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.appointmentCountByMonth = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/activityReports/appointmentCountByMonth?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.appointmentCountByMonthExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/activityReports/appointmentCountByMonth.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.appointmentCountBeforePurchase = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/activityReports/appointmentCountBeforePurchase?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.appointmentCountBeforePurchaseExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/activityReports/appointmentCountBeforePurchase.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.appointmentStatusCountByMonth = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/activityReports/appointmentStatusCountByMonth?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.appointmentStatusCountByMonthExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/activityReports/appointmentStatusCountByMonth.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.taskCountByMonth = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/activityReports/taskCountByMonth?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.taskCountByMonthExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/activityReports/taskCountByMonth.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.smartView = function (success, error) {\n var httpPromise = $http.get(resource.url + '/smartView/');\n return resource.thenFactoryMethod(httpPromise, success, error);\n };\n\n resource.smartViewExecute = function (success, error) {\n var httpPromise = $http.get(resource.url + '/smartView/execute');\n return resource.thenFactoryMethod(httpPromise, success, error);\n };\n\n /** Inventory Reports **/\n resource.itemsOnHand = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/itemsOnHand\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.itemsOnHandExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/itemsOnHand.' + type + '?token=' + token + \"&userName=\" + security.currentUser.username + \"&\" + crit + printParam);\n };\n\n resource.itemsOnOrder = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/itemsOnOrder?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.itemsOnOrderExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/itemsOnOrder.' + type + '?token=' + token + \"&userName=\" + security.currentUser.username + \"&\" + crit + printParam);\n };\n\n resource.itemsAwaitingPickup = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/itemsAwaitingPickup?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.itemsAwaitingPickupExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/itemsAwaitingPickup.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.itemsNotSold = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/itemsNotSold?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.itemsNotSoldExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/itemsNotSold.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.favoriteItemSummary = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/favoriteItemsSummary?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.favoriteItemSummaryExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/favoriteItemsSummary.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.alterationsPlanningReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/alterationsPlanningReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.alterationsPlanningReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/alterationsPlanningReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.operationsPlanningReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/operationsPlanningReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.operationsPlanningReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/operationsPlanningReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.physicalInventoryAdjustmentReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/physicalInventoryAdjustmentReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.physicalInventoryAdjustmentReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/physicalInventoryAdjustmentReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.preparationReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/merchandiseReports/preparationReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.preparationReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/merchandiseReports/preparationReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.salesReport = function (reportId, reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/salesReports/\" + reportId + \"?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.salesReportExport = function (reportId, reportsObj, type, print, userName) {\n var userParam = userName ? '&userName='+userName : '';\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/salesReports/'+reportId+'.' + type + '?token=' + token + \"&\" + crit + printParam + userParam);\n };\n\n /** Purchasing Reports **/\n resource.receivingReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/purchasingReports/receivingReport?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n\n resource.receivingReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/purchasingReports/receivingReport.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.receivingVoucherJournal = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/purchasingReports/receivingVoucherJournal?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.receivingVoucherJournalExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/purchasingReports/receivingVoucherJournal.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.purchaseOrderJournal = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/purchasingReports/purchaseOrderJournal?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.purchaseOrderJournalExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/purchasingReports/purchaseOrderJournal.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n resource.vatReport = function (reportsObj, success, error) {\n var httpPromise = $http.post(resource.url + \"/salesReports/vat?\", buildReportsCriteria(reportsObj));\n return resource.thenFactoryMethod(httpPromise, success, error, true);\n };\n resource.vatReportExport = function (reportsObj, type, print) {\n var printParam = print ? '&printMode=true' : '';\n\n var crit = encodeReportsCriteria(buildReportsCriteria(reportsObj));\n var token = encodeURIComponent(security.readCookie().token);\n handleExportOrPrintPDF(print, resource.url + '/salesReports/vat.' + type + '?token=' + token + \"&\" + crit + printParam);\n };\n\n\n return resource;\n });\n});\n\n","define('modules/notifications/index',[\n './notifications-service',\n './notifications-directive',\n './error-notification-ctrl'\n], function () {});\n\n","define('modules/contact-modal/wedding-registry-ctrl',['./../contact/module', 'angular', 'underscore', 'moment'], function (module, angular, _) {\n 'use strict';\n\n module.controller('ContactWeddingRegistryCtrl', function ($scope, $state, $modal, Enums, contact, event, posSettings, User, alerts, EventMember, EventItem, Sms, modalService, EmailService, TransactionPaymentsModalService) {\n $scope.enumStatuses = Enums.statuses;\n $scope.contact = contact;\n $scope.event = event;\n\n /**\n * Methods\n */\n\n $scope.getMemberItems = function (member) {\n if ($scope.eventItems) {\n var knowIds = [];\n var sortedItems = _.sortBy($scope.eventItems, function (item) {\n return item.createdDate;\n });\n\n return sortedItems.filter(function (item) {\n return item.eventMemberId == member.id\n // multiple eventItems can have same id, take onlyfirst\n && (knowIds.indexOf(item.id) == -1 && knowIds.push(item.id));\n });\n }\n };\n\n $scope.getSelectedItems = function () {\n var selectedItems = _.where($scope.eventItems, { selected: true });\n\n return selectedItems.map(function (item) {\n return _.omit(angular.copy(item), 'selected');\n });\n };\n\n $scope.getTrxType = function (trxTypeId) {\n return _.findWhere(Enums.trxTypes, { value: trxTypeId });\n };\n\n var typeState = {\n 1: 'transaction.sale',\n 2: 'transaction.specialOrder',\n 4: 'transaction.layaway'\n };\n\n $scope.createTrx = function (trxTypeId) {\n\n //validate the selected items\n var selectedItems = $scope.getSelectedItems();\n if (selectedItems.length === 0) {\n modalService.showMessage('contact.messages.select-at-least-one-item');\n return false;\n }\n\n //make sure that none of the event items have a transaction associated with them yet\n if (selectedItems.some(function (eventItem) {\n return eventItem.transactionId;\n })) {\n modalService.showMessage('contact.messages.selected-item-has-already-transaction-associated-unselect-it-and-try-again');\n return false;\n }\n\n //find the contactId of the event member so we can create the trx for them.\n var eventMemberId = null;\n var multipleEventMembers = false;\n _.each(selectedItems, function (eventItem) {\n\n if (eventMemberId && eventMemberId != eventItem.eventMemberId) {\n multipleEventMembers = true;\n } else {\n eventMemberId = eventItem.eventMemberId;\n }\n\n });\n\n if (!multipleEventMembers) {\n $scope.createTrxForMember(eventMemberId, trxTypeId);\n } else {\n //if there are multiple event members for 1 transaction, show a popup where the user can select the event contact\n $modal.open({\n templateUrl: 'js/modules/contact/modals/wedding-registry-multiple-member-select-modal.tpl.html',\n controller: 'WeddingRegistryMultipleMemberSelectModalCtrl',\n resolve: {\n eventMembers: function () {\n //add the bride to the event members\n var membersPlusBride = _.clone($scope.eventMembers);\n\n //add the bride\n membersPlusBride.push({\n id: $scope.contact.id,\n firstName: $scope.contact.firstName,\n lastName: $scope.contact.lastName,\n typeId: 'Event Contact'\n });\n\n return membersPlusBride;\n },\n trxTypeId: function () {\n return trxTypeId;\n },\n callback: function () {\n return $scope.createTrxForMember;\n }\n }\n });\n }\n };\n\n $scope.createTrxForMember = function (eventMemberId, trxTypeId) {\n var selectedItems = $scope.getSelectedItems();\n\n //there should only be 1, but let's make sure\n var eventMemberContactId = 0;\n if (eventMemberId === $scope.contact.id) {\n //if the event contact is the one the user wants to assign as the main contact on the trx\n eventMemberContactId = $scope.contact.id;\n } else {\n var eventMember = _.findWhere($scope.eventMembers, { id: eventMemberId });\n eventMemberContactId = eventMember.contactId;\n }\n\n EventItem.createTrx(trxTypeId, eventMemberContactId, $scope.contact.id, User.id, selectedItems, function (trx) {\n $state.go(typeState[trxTypeId], { id: trx.id, referrer: 'contact.registry', promptLeadAndItemAddOns: true });\n });\n };\n\n $scope.showSendModal = EmailService.showSendModal;\n\n $scope.createPOs = function () {\n var selectedItems = $scope.getSelectedItems();\n\n if (selectedItems.length === 0) {\n modalService.showMessage('contact.messages.select-at-least-one-item');\n return false;\n }\n\n //make sure that none of the event items have a transaction associated with them yet\n if (selectedItems.some(function (eventItem) {\n return !eventItem.transactionId;\n })) {\n modalService.showMessage('contact.messages.selected-item-does-not-have-transaction-associated-please-create-transaction-for-it');\n return false;\n }\n\n\n // make sure that none of the event items doesn't have a item status of 'W' <-- 'Awaiting Picking'\n if (selectedItems.some(function (eventItem) {\n return eventItem.trxLineItemStatus === 'W';\n })) {\n modalService.showMessage('contact.messages.selected-item-is-set-for-awaiting-pickup-please-change-status-for-it');\n return false;\n }\n\n EventItem.createPOs(User.id, 'true', _.pluck(selectedItems, 'trxLineItemId'), function (trxItems) {\n alerts.pushSuccess('alerts.contact.successfully-created-purchase-orders');\n _.each(trxItems, function (item) {\n var eventItem = _.findWhere($scope.eventItems, { id: item.eventItemId });\n if(!eventItem.purchaseOrderId) {\n eventItem.poLineItemConfNumber = item.poLineItemConfNumber;\n eventItem.poLineItemQuantity = item.poLineItemQuantity;\n eventItem.poLineItemSequenceNumber = item.poLineItemSequenceNumber;\n eventItem.purchaseOrderId = item.purchaseOrderId;\n eventItem.purchaseOrderItemId = item.purchaseOrderItemId;\n eventItem.purchaseOrderNumber = item.purchaseOrderNumber;\n eventItem.purchaseOrderShipDate = item.purchaseOrderShipDate;\n }\n });\n });\n };\n\n $scope.createQuote = function() {\n //validate the selected items\n var selectedItems = $scope.getSelectedItems();\n if (selectedItems.length === 0) {\n modalService.showMessage('contact.messages.select-at-least-one-item');\n return false;\n }\n\n //make sure that none of the event items have a transaction associated with them yet\n if (selectedItems.some(function (eventItem) {\n return eventItem.transactionId;\n })) {\n modalService.showMessage('contact.messages.selected-item-has-already-transaction-associated-unselect-it-and-try-again');\n return false;\n }\n\n //find the contactId of the event member so we can create the trx for them.\n var eventMemberId = null;\n var multipleEventMembers = false;\n _.each(selectedItems, function (eventItem) {\n\n if (eventMemberId && eventMemberId !== eventItem.eventMemberId) {\n multipleEventMembers = true;\n } else {\n eventMemberId = eventItem.eventMemberId;\n }\n\n });\n\n if(!multipleEventMembers) {\n $scope.createQuoteForMember(eventMemberId);\n\n } else {\n $modal.open({\n templateUrl: 'js/modules/contact/modals/wedding-registry-multiple-member-select-modal.tpl.html',\n controller: 'WeddingRegistryMultipleMemberSelectModalCtrl',\n resolve: {\n eventMembers: function () {\n //add the bride to the event members\n var membersPlusBride = _.clone($scope.eventMembers);\n\n //add the bride\n membersPlusBride.push({\n id: $scope.contact.id,\n firstName: $scope.contact.firstName,\n lastName: $scope.contact.lastName,\n typeId: 'Event Contact'\n });\n\n return membersPlusBride;\n },\n trxTypeId: function () {\n return null;\n },\n callback: function () {\n return $scope.createQuoteForMember;\n }\n }\n });\n }\n };\n\n $scope.createQuoteForMember = function(eventMemberId, typeId) {\n var selectedItems = $scope.getSelectedItems();\n\n //there should only be 1, but let's make sure\n var eventMemberContactId = 0;\n if (eventMemberId === $scope.contact.id) {\n //if the event contact is the one the user wants to assign as the main contact on the trx\n eventMemberContactId = $scope.contact.id;\n } else {\n var eventMember = _.findWhere($scope.eventMembers, { id: eventMemberId });\n eventMemberContactId = eventMember.contactId;\n }\n\n EventItem.createQuote(eventMemberContactId, $scope.contact.id, User.id, selectedItems, function (quote) {\n $state.go('quote.edit', { id: quote.id, referrer: 'contact.registry', promptLeadAndItemAddOns: true });\n });\n };\n\n $scope.registerItems = function () {\n $modal.open({\n templateUrl: 'js/modules/contact/modals/wedding-registry-modal.tpl.html',\n controller: 'WeddingRegistryModalCtrl',\n resolve: {\n eventMembers: function () {\n return $scope.eventMembers;\n },\n eventItems: function () {\n return $scope.eventItems;\n },\n posSettings: function () {\n return posSettings;\n }\n }\n });\n };\n\n $scope.openSplitPayments = function () {\n var selectedItems = _($scope.getSelectedItems()).filter(function (item) {\n return item.balanceDue > 0;\n });\n\n if (!selectedItems.length) {\n return modalService.showMessage('contact.messages.select-transactions-with-positive-balance-due');\n }\n\n var transactions = _(selectedItems).map(function (eventItem) {\n var transaction = _(eventItem).pick('trxNumber', 'balanceDue');\n transaction.contactName = eventItem.firstName + ' ' + eventItem.lastName;\n transaction.eventDate = eventItem.trxEventDate;\n transaction.typeId = eventItem.trxTypeId;\n transaction.id = eventItem.transactionId;\n return transaction;\n });\n\n //de-dupe the transactions\n transactions = _(transactions).uniq(function(transaction){\n return transaction.id;\n });\n\n TransactionPaymentsModalService\n .openSplitPayments(transactions)\n .result\n .then(function (results) {\n init();\n if(results) {\n openSplitPaymentsSuccessModal(results);\n }\n });\n };\n\n function openSplitPaymentsSuccessModal(transactions) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/split-payments-confirmation-modal.tpl.html',\n controller: 'SplitPaymentsConfirmationModalCtrl',\n resolve: {\n transactions: function () {\n return transactions;\n },\n posSettings: function (CompanySetting) {\n return CompanySetting.getPOSSettings();\n }\n }\n });\n }\n\n $scope.sendTextMessage = function () {\n\n var selectedMembers = _.where($scope.eventMembers, { selected: true });\n\n if (!selectedMembers.length) {\n modalService.showMessage('contact.messages.select-at-least-one-contact', 'common.message.wooops');\n return false;\n }\n\n if (selectedMembers.length > 250) {\n modalService.showMessage('contact.messages.select-no-more-than-250', 'common.message.cannot-send-sms-messages');\n return false;\n }\n\n var selectedContacts = [];\n _.forEach(selectedMembers, function(selectedMember) {\n var contact = {\n id: selectedMember.contactId,\n mobilePhoneNumber: selectedMember.mobilePhoneNumber\n };\n\n selectedContacts.push(contact);\n\n });\n\n var successcb = function (result) {\n var modalScope = {\n errors: result.errors,\n successes: result.successes,\n optedErrors: result.optedErrors\n };\n\n $modal.open({\n templateUrl: 'js/modules/contact/modals/bulk-twilio-send-result-modal.tpl.html',\n controller: 'BulkTwilioSendResultModalCtrl',\n resolve: {\n modalScope: function () {\n return modalScope;\n }\n },\n backdrop:'static'\n });\n\n };\n\n Sms.showSendModal(selectedContacts, null, null, null, successcb);\n };\n\n $scope.selectAllMembers = function (selected) {\n $scope.eventMembers.forEach(function (member) {\n member.selected = selected;\n $scope.selectMemberItems(member);\n });\n };\n\n $scope.selectMemberItems = function (member) {\n $scope.getMemberItems(member).forEach(function (item) {\n item.selected = member.selected;\n });\n };\n\n $scope.removeMemberItem = function (eventItem) {\n $scope.eventItems = _($scope.eventItems).without(eventItem);\n eventItem.$remove();\n alerts.push('alerts.contact.successfully-removed-wedding-registry-item');\n };\n\n $scope.updateEventItem = function (item) {\n _.omit(item, 'selected').$update(function (updatedItem) {\n item.version = updatedItem.version;\n });\n };\n\n /**\n * Watchers\n */\n\n $scope.$watchCollection('eventItems', function (items) {\n $scope.itemsColorOptions = {};\n $scope.itemsColor2Options = {};\n $scope.itemsSizeOptions = {};\n\n if (items) {\n items.forEach(function (item) {\n // color, color2\n $scope.itemsColorOptions[item.id] = item.colorString ? item.colorString.replace(/ /g, '').split(',') : [];\n $scope.itemsColor2Options[item.id] = item.color2String ? item.color2String.replace(/ /g, '').split(',') : [];\n\n // size\n $scope.itemsSizeOptions[item.id] = item.sizeGroup && item.sizeGroup.sizes.map(function (size) {\n return size.size;\n });\n });\n }\n });\n\n /**\n * Init\n */\n\n function init() {\n EventItem.list({ eventId: $scope.event.id }, function (items) {\n $scope.eventItems = items;\n });\n\n EventMember.query({ filterObject: { eventId: $scope.event.id } }, function (members) {\n $scope.eventMembers = members;\n\n // Expand all members\n $scope.eventMembers.map(function (member) {\n member.expanded = true;\n member.selected = false;\n });\n });\n }\n\n init();\n });\n});\n\n","/* \n * Angular JS Multi Select\n * Creates a dropdown-like button with checkboxes. \n *\n * Project started on: Tue, 14 Jan 2014 - 5:18:02 PM\n * Current version: 2.0.2\n * \n * Released under the MIT License\n * --------------------------------------------------------------------------------\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Ignatius Steven (https://github.com/isteven)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy \n * of this software and associated documentation files (the \"Software\"), to deal \n * in the Software without restriction, including without limitation the rights \n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \n * copies of the Software, and to permit persons to whom the Software is \n * furnished to do so, subject to the following conditions: \n *\n * The above copyright notice and this permission notice shall be included in all \n * copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \n * SOFTWARE.\n * --------------------------------------------------------------------------------\n */\n\nangular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$timeout', function ( $sce, $timeout ) {\n return {\n restrict: \n 'AE',\n\n replace: \n true,\n\n scope: \n { \n // models\n inputModel : '=',\n outputModel : '=',\n\n // settings based on attribute\n buttonLabel : '@',\n defaultLabel : '@',\n directiveId : '@',\n helperElements : '@', \n isDisabled : '=',\n itemLabel : '@',\n maxLabels : '@',\n orientation : '@',\n selectionMode : '@',\n isGroupSelectionDisabled: '@',\n\n // settings based on input model property \n tickProperty : '@',\n disableProperty : '@',\n groupProperty : '@',\n maxHeight : '@',\n\n // callbacks\n onClose : '&', \n onItemClick : '&',\n onOpen : '&' \n },\n\n template: \n '' + \n '' +\n ' ' + \n '' +\n ' ',\n\n link: function ( $scope, element, attrs ) { \n\n $scope.backUp = [];\n $scope.varButtonLabel = ''; \n $scope.scrolled = false;\n $scope.spacingProperty = '';\n $scope.indexProperty = ''; \n $scope.checkBoxLayer = '';\n $scope.orientationH = false;\n $scope.orientationV = true;\n $scope.filteredModel = [];\n $scope.inputLabel = { labelFilter: '' };\n $scope.selectedItems = []; \n $scope.formElements = [];\n $scope.tabIndex = 0;\n $scope.clickedItem = null;\n prevTabIndex = 0;\n helperItems = [];\n helperItemsLength = 0;\n\n // If user specify a height, call this function\n $scope.setHeight = function() {\n if ( typeof $scope.maxHeight !== 'undefined' ) {\n return 'max-height: ' + $scope.maxHeight + '; overflow-y:scroll';\n }\n }\n\n // A little hack so that AngularJS ng-repeat can loop using start and end index like a normal loop\n // http://stackoverflow.com/questions/16824853/way-to-ng-repeat-defined-number-of-times-instead-of-repeating-over-array\n $scope.numberToArray = function( num ) {\n return new Array( num ); \n }\n\n $scope.updateFilter = function()\n {\n // we check by looping from end of array\n $scope.filteredModel = [];\n var i = 0;\n\n if ( typeof $scope.inputModel === 'undefined' ) {\n return []; \n }\n\n for( i = $scope.inputModel.length - 1; i >= 0; i-- ) {\n\n // if it's group end\n if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ $scope.groupProperty ] === false ) {\n $scope.filteredModel.push( $scope.inputModel[ i ] );\n }\n \n // if it's data \n var gotData = false;\n if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] === 'undefined' ) { \n\n for (var key in $scope.inputModel[ i ] ) {\n // if filter string is in one of object property \n if ( typeof $scope.inputModel[ i ][ key ] !== 'boolean' && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0 ) {\n gotData = true;\n break;\n }\n } \n if ( gotData === true ) { \n // push\n $scope.filteredModel.push( $scope.inputModel[ i ] );\n }\n }\n\n // if it's group start\n if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ $scope.groupProperty ] === true ) {\n\n if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] === false ) {\n $scope.filteredModel.pop();\n }\n else {\n $scope.filteredModel.push( $scope.inputModel[ i ] );\n }\n }\n } \n\n $scope.filteredModel.reverse(); \n $timeout( function() {\n $scope.getFormElements(); \n },0);\n };\n\n // List all the input elements.\n // This function will be called everytime the filter is updated. Not good for performance, but oh well..\n $scope.getFormElements = function() { \n $scope.formElements = [];\n for ( var i = 0; i < element[ 0 ].getElementsByTagName( 'FORM' )[ 0 ].elements.length ; i++ ) { \n $scope.formElements.push( element[ 0 ].getElementsByTagName( 'FORM' )[ 0 ].elements[ i ] );\n }\n } \n\n // check if an item has $scope.groupProperty (be it true or false)\n $scope.isGroupMarker = function( item , type ) {\n if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === type ) return true; \n return false;\n }\n\n $scope.removeGroupEndMarker = function( item ) {\n if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) return false; \n return true;\n }\n \n\n // Show or hide a helper element \n $scope.displayHelper = function( elementString ) {\n\n if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {\n\n switch( elementString.toUpperCase() ) {\n case 'ALL': \n return false; \n break;\n case 'NONE': \n return false;\n break;\n case 'RESET':\n if ( typeof attrs.helperElements === 'undefined' ) {\n return true; \n }\n else if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( 'RESET' ) >= 0 ) {\n return true;\n } \n break;\n case 'FILTER':\n if ( typeof attrs.helperElements === 'undefined' ) {\n return true; \n } \n if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( 'FILTER' ) >= 0 ) {\n return true;\n }\n break; \n default: \n break;\n } \n\n return false;\n }\n\n else {\n if ( typeof attrs.helperElements === 'undefined' ) {\n return true; \n }\n if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( elementString.toUpperCase() ) >= 0 ) {\n return true;\n }\n return false;\n } \n } \n\n // call this function when an item is clicked\n $scope.syncItems = function( item, e, ng_repeat_index ) { \n\n e.preventDefault();\n e.stopPropagation();\n\n // if it's globaly disabled, then don't do anything\n if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) { \n return false;\n }\n\n // don't change disabled items\n if ( typeof attrs.isDisabled !== 'undefined' && $scope.isDisabled === true ) { \n return false;\n } \n\n // we don't care about end of group markers\n if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) {\n return false;\n } \n\n index = $scope.filteredModel.indexOf( item ); \n\n // process items if the start of group marker is clicked ( only for multiple selection! )\n // if, in a group, there are items which are not selected, then they all will be selected\n // if, in a group, all items are selected, then they all will be de-selected \n if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === true ) { \n\n if ( (attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE') || $scope.isGroupSelectionDisabled ) {\n return false;\n }\n \n var i,j,k;\n var startIndex = 0;\n var endIndex = $scope.filteredModel.length - 1;\n var tempArr = [];\n var nestLevel = 0; \n\n for( i = index ; i < $scope.filteredModel.length ; i++) { \n\n if ( nestLevel === 0 && i > index ) \n {\n break;\n }\n \n // if group start\n if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === true ) {\n \n // To cater multi level grouping\n if ( tempArr.length === 0 ) {\n startIndex = i + 1; \n } \n nestLevel = nestLevel + 1;\n } \n\n // if group end\n else if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === false ) {\n\n nestLevel = nestLevel - 1; \n\n // cek if all are ticked or not \n if ( tempArr.length > 0 && nestLevel === 0 ) { \n\n var allTicked = true; \n\n endIndex = i;\n\n for ( j = 0; j < tempArr.length ; j++ ) { \n if ( typeof tempArr[ j ][ $scope.tickProperty ] !== 'undefined' && tempArr[ j ][ $scope.tickProperty ] === false ) {\n allTicked = false;\n break;\n }\n } \n\n if ( allTicked === true ) {\n for ( j = startIndex; j <= endIndex ; j++ ) {\n if ( typeof $scope.filteredModel[ j ][ $scope.groupProperty ] === 'undefined' ) {\n if ( typeof attrs.disableProperty === 'undefined' ) {\n $scope.filteredModel[ j ][ $scope.tickProperty ] = false;\n // we refresh input model as well\n inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];\n $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;\n }\n else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {\n $scope.filteredModel[ j ][ $scope.tickProperty ] = false;\n // we refresh input model as well\n inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];\n $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;\n }\n }\n } \n }\n\n else {\n for ( j = startIndex; j <= endIndex ; j++ ) {\n if ( typeof $scope.filteredModel[ j ][ $scope.groupProperty ] === 'undefined' ) {\n if ( typeof attrs.disableProperty === 'undefined' ) {\n $scope.filteredModel[ j ][ $scope.tickProperty ] = true; \n // we refresh input model as well\n inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];\n $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;\n\n } \n else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {\n $scope.filteredModel[ j ][ $scope.tickProperty ] = true;\n // we refresh input model as well\n inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];\n $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;\n }\n }\n } \n } \n }\n }\n \n // if data\n else { \n tempArr.push( $scope.filteredModel[ i ] ); \n }\n } \n }\n\n // single item click\n else {\n\n // If it's single selection mode\n if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {\n \n // first, set everything to false\n for( i=0 ; i < $scope.filteredModel.length ; i++) { \n $scope.filteredModel[ i ][ $scope.tickProperty ] = false; \n } \n for( i=0 ; i < $scope.inputModel.length ; i++) { \n $scope.inputModel[ i ][ $scope.tickProperty ] = false; \n } \n \n // then set the clicked item to true\n $scope.filteredModel[ index ][ $scope.tickProperty ] = true;\n\n $scope.toggleCheckboxes( e ); \n } \n\n // Multiple\n else {\n $scope.filteredModel[ index ][ $scope.tickProperty ] = !$scope.filteredModel[ index ][ $scope.tickProperty ];\n }\n\n // we refresh input model as well\n inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ]; \n $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ]; \n } \n\n $scope.clickedItem = angular.copy( item ); \n\n // We update the index here\n prevTabIndex = $scope.tabIndex;\n $scope.tabIndex = ng_repeat_index + helperItemsLength;\n \n // Set focus on the hidden checkbox \n e.target.focus();\n\n // set & remove CSS style\n $scope.removeFocusStyle( prevTabIndex );\n $scope.setFocusStyle( $scope.tabIndex );\n } \n\n // update $scope.selectedItems\n // this variable is used in $scope.outputModel and to refresh the button label\n $scope.refreshSelectedItems = function() { \n $scope.selectedItems = [];\n angular.forEach( $scope.inputModel, function( value, key ) {\n if ( typeof value !== 'undefined' ) { \n if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {\n if ( value[ $scope.tickProperty ] === true ) {\n $scope.selectedItems.push( value ); \n }\n }\n }\n }); \n }\n\n // refresh output model as well\n $scope.refreshOutputModel = function() { \n if ( typeof attrs.outputModel !== 'undefined' ) { \n $scope.outputModel = angular.copy( $scope.selectedItems ); \n angular.forEach( $scope.outputModel, function( value, key ) {\n // remove the index number and spacing number from output model\n delete value[ $scope.indexProperty ];\n delete value[ $scope.spacingProperty ]; \n });\n } \n }\n\n // refresh button label\n $scope.refreshButton = function() {\n\n $scope.varButtonLabel = ''; \n ctr = 0; \n\n // refresh button label...\n if ( $scope.selectedItems.length === 0 ) {\n // https://github.com/isteven/angular-multi-select/pull/19 \n $scope.varButtonLabel = ( typeof $scope.defaultLabel !== 'undefined' ) ? $scope.defaultLabel : 'None selected';\n }\n else { \n var tempMaxLabels = $scope.selectedItems.length;\n if ( typeof $scope.maxLabels !== 'undefined' && $scope.maxLabels !== '' ) {\n tempMaxLabels = $scope.maxLabels;\n }\n\n // if max amount of labels displayed..\n if ( $scope.selectedItems.length > tempMaxLabels ) {\n $scope.more = true;\n }\n else {\n $scope.more = false;\n } \n \n angular.forEach( $scope.selectedItems, function( value, key ) {\n if ( typeof value !== 'undefined' ) { \n if ( ctr < tempMaxLabels ) { \n $scope.varButtonLabel += ( $scope.varButtonLabel.length > 0 ? ', ' : '
') + $scope.writeLabel( value, 'buttonLabel' );\n }\n ctr++;\n }\n }); \n\n if ( $scope.more === true ) {\n // https://github.com/isteven/angular-multi-select/pull/16\n if (tempMaxLabels > 0) {\n $scope.varButtonLabel += ', ... ';\n }\n $scope.varButtonLabel += '(Total: ' + $scope.selectedItems.length + ')'; \n }\n }\n $scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '
' ); \n }\n\n // Check if a checkbox is disabled or enabled. It will check the granular control (disableProperty) and global control (isDisabled)\n // Take note that the granular control has higher priority.\n $scope.itemIsDisabled = function( item ) {\n \n if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) { \n return true;\n }\n else { \n if ( $scope.isDisabled === true ) { \n return true;\n }\n else {\n return false;\n }\n }\n \n }\n\n // A simple function to parse the item label settings\n $scope.writeLabel = function( item, type ) {\n var label = '';\n var temp = $scope[ type ].split( ' ' ); \n angular.forEach( temp, function( value2, key2 ) {\n if ( typeof value2 !== 'undefined' ) { \n angular.forEach( item, function( value1, key1 ) { \n if ( key1 == value2 ) {\n label += ' ' + value1; \n }\n }); \n }\n });\n if ( type.toUpperCase() === 'BUTTONLABEL' ) {\n return label;\n }\n return $sce.trustAsHtml( label );\n }\n\n // UI operations to show/hide checkboxes based on click event..\n $scope.toggleCheckboxes = function( e ) { \n\n // We grab the checkboxLayer\n $scope.checkBoxLayer = element.children()[1];\n\n // We grab the button\n clickedEl = element.children()[0];\n\n // Just to make sure.. had a bug where key events were recorded twice\n angular.element( document ).unbind( 'click', $scope.externalClickListener );\n angular.element( document ).unbind( 'keydown', $scope.keyboardListener ); \n\n // clear filter\n $scope.inputLabel.labelFilter = ''; \n $scope.updateFilter(); \n\n // close if ESC key is pressed.\n if ( e.keyCode === 27 ) {\n angular.element( $scope.checkBoxLayer ).removeClass( 'show' ); \n angular.element( clickedEl ).removeClass( 'buttonClicked' ); \n angular.element( document ).unbind( 'click', $scope.externalClickListener );\n angular.element( document ).unbind( 'keydown', $scope.keyboardListener ); \n\n // clear the focused element;\n $scope.removeFocusStyle( $scope.tabIndex );\n\n // close callback\n $scope.onClose( { data: element } );\n return true;\n } \n\n // The idea below was taken from another multi-select directive - https://github.com/amitava82/angular-multiselect \n // His version is awesome if you need a more simple multi-select approach.\n\n // close\n if ( angular.element( $scope.checkBoxLayer ).hasClass( 'show' )) { \n angular.element( $scope.checkBoxLayer ).removeClass( 'show' ); \n angular.element( clickedEl ).removeClass( 'buttonClicked' ); \n angular.element( document ).unbind( 'click', $scope.externalClickListener );\n angular.element( document ).unbind( 'keydown', $scope.keyboardListener ); \n\n // clear the focused element;\n $scope.removeFocusStyle( $scope.tabIndex );\n\n // close callback\n $scope.onClose( { data: element } );\n } \n // open\n else \n { \n helperItems = [];\n helperItemsLength = 0;\n\n angular.element( $scope.checkBoxLayer ).addClass( 'show' ); \n angular.element( clickedEl ).addClass( 'buttonClicked' ); \n angular.element( document ).bind( 'click', $scope.externalClickListener );\n angular.element( document ).bind( 'keydown', $scope.keyboardListener ); \n\n // to get the initial tab index, depending on how many helper elements we have. \n // priority is to always focus it on the input filter \n $scope.getFormElements();\n $scope.tabIndex = 0;\n\n var helperContainer = angular.element( element[ 0 ].querySelector( '.helperContainer' ) )[0]; \n \n if ( typeof helperContainer !== 'undefined' ) {\n for ( i = 0; i < helperContainer.getElementsByTagName( 'BUTTON' ).length ; i++ ) {\n helperItems[ i ] = helperContainer.getElementsByTagName( 'BUTTON' )[ i ];\n }\n helperItemsLength = helperItems.length + helperContainer.getElementsByTagName( 'INPUT' ).length;\n }\n \n // focus on the filter element on open. \n if ( element[ 0 ].querySelector( '.inputFilter' ) ) { \n element[ 0 ].querySelector( '.inputFilter' ).focus(); \n $scope.tabIndex = $scope.tabIndex + helperItemsLength - 2;\n }\n // if there's no filter then just focus on the first checkbox item\n else { \n $scope.formElements[ $scope.tabIndex ].focus(); \n } \n\n // open callback\n $scope.onOpen( { data: element } );\n } \n }\n \n // handle clicks outside the button / multi select layer\n $scope.externalClickListener = function( e ) { \n targetsArr = element.find( e.target.tagName );\n for (var i = 0; i < targetsArr.length; i++) { \n if ( e.target == targetsArr[i] ) {\n return;\n }\n }\n\n angular.element( $scope.checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' ); \n angular.element( $scope.checkBoxLayer ).removeClass( 'show' );\n angular.element( document ).unbind( 'click', $scope.externalClickListener ); \n angular.element( document ).unbind( 'keydown', $scope.keyboardListener ); \n \n // close callback \n $timeout( function() {\n $scope.onClose( { data: element } );\n }, 0 );\n }\n \n // traverse up to find the button tag\n // http://stackoverflow.com/questions/7332179/how-to-recursively-search-all-parentnodes\n $scope.findUpTag = function ( el, tag, className ) {\n while ( el.parentNode ) { \n el = el.parentNode; \n if ( typeof el.tagName !== 'undefined' ) {\n if ( el.tagName.toUpperCase() === tag.toUpperCase() && el.className.indexOf( className ) > -1 ) {\n return el;\n }\n }\n }\n return null;\n }\n\n // select All / select None / reset buttons\n $scope.select = function( type, e ) {\n\n helperIndex = helperItems.indexOf( e.target );\n $scope.tabIndex = helperIndex;\n\n switch( type.toUpperCase() ) {\n case 'ALL':\n angular.forEach( $scope.filteredModel, function( value, key ) { \n if ( typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) { \n if ( typeof value[ $scope.groupProperty ] === 'undefined' ) { \n value[ $scope.tickProperty ] = true;\n }\n }\n }); \n break;\n case 'NONE':\n angular.forEach( $scope.filteredModel, function( value, key ) {\n if ( typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) { \n if ( typeof value[ $scope.groupProperty ] === 'undefined' ) { \n value[ $scope.tickProperty ] = false;\n }\n }\n }); \n break;\n case 'RESET': \n angular.forEach( $scope.filteredModel, function( value, key ) { \n if ( typeof value[ $scope.groupProperty ] === 'undefined' && typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) { \n temp = value[ $scope.indexProperty ]; \n value[ $scope.tickProperty ] = $scope.backUp[ temp ][ $scope.tickProperty ];\n }\n }); \n break;\n case 'CLEAR':\n $scope.tabIndex = $scope.tabIndex + 1;\n break;\n case 'FILTER': \n $scope.tabIndex = helperItems.length - 1;\n break;\n default: \n } \n } \n\n // just to create a random variable name \n genRandomString = function( length ) { \n var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n var temp = '';\n for( var i=0; i < length; i++ ) {\n temp += possible.charAt( Math.floor( Math.random() * possible.length ));\n }\n return temp;\n }\n\n // count leading spaces\n $scope.prepareGrouping = function() {\n var spacing = 0; \n angular.forEach( $scope.filteredModel, function( value, key ) {\n value[ $scope.spacingProperty ] = spacing; \n if ( value[ $scope.groupProperty ] === true ) {\n spacing+=2;\n } \n else if ( value[ $scope.groupProperty ] === false ) {\n spacing-=2;\n } \n });\n }\n\n // prepare original index\n $scope.prepareIndex = function() {\n ctr = 0;\n angular.forEach( $scope.filteredModel, function( value, key ) {\n value[ $scope.indexProperty ] = ctr;\n ctr++;\n });\n }\n\n // navigate using up and down arrow\n $scope.keyboardListener = function( e ) { \n \n var key = e.keyCode ? e.keyCode : e.which; \n var isNavigationKey = false; \n\n // ESC key (close)\n if ( key === 27 ) {\n $scope.toggleCheckboxes( e );\n } \n \n // next element ( tab, down & right key ) \n else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) { \n isNavigationKey = true;\n prevTabIndex = $scope.tabIndex; \n $scope.tabIndex++; \n if ( $scope.tabIndex > $scope.formElements.length - 1 ) {\n $scope.tabIndex = 0;\n prevTabIndex = $scope.formElements.length - 1; \n } \n while ( $scope.formElements[ $scope.tabIndex ].disabled === true ) { \n $scope.tabIndex++;\n if ( $scope.tabIndex > $scope.formElements.length - 1 ) {\n $scope.tabIndex = 0; \n } \n }\n }\n \n // prev element ( shift+tab, up & left key )\n else if ( key === 38 || key === 37 || ( e.shiftKey && key == 9 ) ) { \n isNavigationKey = true;\n prevTabIndex = $scope.tabIndex; \n $scope.tabIndex--; \n if ( $scope.tabIndex < 0 ) {\n $scope.tabIndex = $scope.formElements.length - 1;\n prevTabIndex = 0;\n } \n while ( $scope.formElements[ $scope.tabIndex ].disabled === true ) {\n $scope.tabIndex--;\n if ( $scope.tabIndex < 0 ) {\n $scope.tabIndex = $scope.formElements.length - 1;\n } \n } \n } \n\n if ( isNavigationKey === true ) { \n\n e.preventDefault();\n e.stopPropagation(); \n\n // set focus on the checkbox\n $scope.formElements[ $scope.tabIndex ].focus(); \n \n // css styling\n var actEl = document.activeElement; \n\n if ( actEl.type.toUpperCase() === 'CHECKBOX' ) { \n $scope.setFocusStyle( $scope.tabIndex );\n $scope.removeFocusStyle( prevTabIndex );\n } \n else {\n $scope.removeFocusStyle( prevTabIndex );\n $scope.removeFocusStyle( helperItemsLength );\n $scope.removeFocusStyle( $scope.formElements.length - 1 );\n } \n }\n\n isNavigationKey = false;\n }\n\n // set (add) CSS style on selected row\n $scope.setFocusStyle = function( tabIndex ) { \n angular.element( $scope.formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' ); \n }\n\n // remove CSS style on selected row\n $scope.removeFocusStyle = function( tabIndex ) {\n angular.element( $scope.formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' );\n }\n\n ///////////////////////////////////////////////////////\n //\n // Logic starts here, initiated by watch 1 & watch 2.\n //\n ///////////////////////////////////////////////////////\n\n var tempStr = genRandomString( 5 );\n $scope.indexProperty = 'idx_' + tempStr;\n $scope.spacingProperty = 'spc_' + tempStr; \n\n // set orientation css \n if ( typeof attrs.orientation !== 'undefined' ) {\n if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) { \n $scope.orientationH = true;\n $scope.orientationV = false;\n }\n else {\n $scope.orientationH = false;\n $scope.orientationV = true;\n }\n } \n \n // watch1, for changes in input model property\n // updates multi-select when user select/deselect a single checkbox programatically\n // https://github.com/isteven/angular-multi-select/issues/8\n $scope.$watch( 'inputModel' , function( newVal ) { \n if ( newVal ) {\n $scope.refreshSelectedItems(); \n $scope.refreshOutputModel();\n $scope.refreshButton(); \n if ( $scope.clickedItem !== null ) { \n $timeout( function() {\n $scope.onItemClick( { data: $scope.clickedItem } );\n $scope.clickedItem = null; \n }, 0 ); \n } \n }\n }, true);\n\n // watch2 for changes in input model as a whole\n // this on updates the multi-select when a user load a whole new input-model. We also update the $scope.backUp variable\n $scope.$watch( 'inputModel' , function( newVal ) { \n if ( newVal ) {\n $scope.backUp = angular.copy( $scope.inputModel ); \n $scope.updateFilter();\n $scope.prepareGrouping();\n $scope.prepareIndex(); \n $scope.refreshSelectedItems(); \n $scope.refreshOutputModel(); \n $scope.refreshButton(); \n }\n }); \n\n // watch for changes in directive state (disabled or enabled)\n $scope.$watch( 'isDisabled' , function( newVal ) { \n $scope.isDisabled = newVal; \n }); \n\n // this is for touch enabled devices. We don't want to hide checkboxes on scroll. \n angular.element( document ).bind( 'touchstart', function( e ) { \n $scope.$apply( function() {\n $scope.scrolled = false;\n }); \n });\n \n // also for touch enabled devices\n angular.element( document ).bind( 'touchmove', function( e ) { \n $scope.$apply( function() {\n $scope.scrolled = true; \n });\n });\n \n // for IE8, perhaps. Not sure if this is really executed.\n if ( !Array.prototype.indexOf ) {\n Array.prototype.indexOf = function(what, i) { \n i = i || 0;\n var L = this.length;\n while (i < L) {\n if(this[i] === what) return i;\n ++i;\n }\n return -1;\n };\n }\n } \n }\n}]);\n\n\ndefine(\"multi-select\", [\"angular\"], function(){});\n\n","define('modules/item/favorited-by/favorited-by-ctrl',['./module', 'underscore'], function (module, _) {\n 'use strict';\n\n return module.controller('ItemFavoritedByCtrl', function ($controller, $scope, $modal, security, favoriteHistory, Enums, Sms, EmailService, modalService, ItemInterestEmailSettings) {\n\n $scope.selectedResults = [];\n $scope.headers = [\n { title: 'common.contact-name' },\n { title: 'common.color' },\n { title: 'common.accent-color' },\n { title: 'common.size' },\n { title: 'common.modal.mobile-phone-number' },\n { title: 'common.modal.email' },\n { title: 'contact.favorites.tried-on' },\n { title: 'common.added-by' }\n ];\n\n $scope.colSpanCount = 8;\n\n if(security.hasPermission('MESSAGES')) {\n $scope.colSpanCount++;\n }\n\n $scope.showSendTextMessageModal = function (favorite) {\n var contactObj = {\n id: favorite.contactId,\n itemInterestId: favorite.id,\n mobilePhoneNumber: favorite.mobilePhoneNumber,\n favoriteItemImageUrl: favorite.itemPictureImageUrl\n };\n\n Sms.showSendModal(contactObj, null, null, null, null, {\n showOptions: true,\n includeItemImage: !!(favorite.itemPictureImageUrl),\n imageUrl: favorite.itemPictureImageUrl\n });\n };\n\n $scope.showSendEmailModal = function(favorite) {\n var contact = favorite.contact;\n EmailService.showSendModal(contact, null, null, null, null, [favorite],{\n showOptions: true,\n showFavorites: true,\n favoritesEmailSettings: ItemInterestEmailSettings\n }, true);\n };\n\n $scope.syncSelectedResults = function(obj) {\n var checkedItWasSelected = _($scope.selectedResults).findWhere({ id: obj.id });\n if (checkedItWasSelected) {\n $scope.selectedResults = _($scope.selectedResults).without(checkedItWasSelected);\n } else {\n $scope.selectedResults.push(obj);\n }\n };\n\n $scope.selectAll = false;\n $scope.toggleSelectAll = function (removeAll) {\n $scope.selectAll = !$scope.selectAll;\n if($scope.selectAll) {\n $scope.selectedResults = _.union($scope.selectedResults, $scope.favoriteHistory);\n } else {\n $scope.selectedResults = removeAll ? [] : _($scope.selectedResults).difference($scope.favoriteHistory);\n }\n\n //toggle all favorites\n $scope.favoriteHistory = _($scope.favoriteHistory).map(function(favorite) {\n favorite.checked = $scope.selectAll;\n return favorite;\n });\n };\n\n $scope.favoriteHistory = mapFavoriteHistory(favoriteHistory);\n\n function mapFavoriteHistory(favorites) {\n return _(favorites).map(function (favorite) {\n return _.extend(favorite, {\n contactName: favorite.contact ? favorite.contact.firstName + ' ' + (favorite.contact.lastName || '') : '',\n emailAddress: favorite.contact && favorite.contact.emailAddress ? favorite.contact.emailAddress : '',\n mobilePhoneNumber: favorite.contact && favorite.contact.mobilePhoneNumber ? favorite.contact.mobilePhoneNumber : '',\n smsOptIn: favorite.contact && favorite.contact.smsOptIn ? favorite.contact.smsOptIn : false,\n vip: favorite.contact && favorite.contact.vip ? favorite.contact.vip : false\n });\n });\n }\n\n $scope.sendTextMessage = function () {\n if(!$scope.selectedResults.length) {\n modalService.showMessage('contact.messages.select-at-least-one-contact', 'common.message.wooops');\n return false;\n }\n\n if($scope.selectedResults.length > 250) {\n modalService.showMessage('contact.messages.select-no-more-than-250', 'common.message.cannot-send-sms-messages');\n return false;\n }\n\n var selectedResultsSMS = [];\n\n _.each($scope.selectedResults, function (selectedResults) {\n var contactObj = {\n id: selectedResults.contactId,\n mobilePhoneNumber: selectedResults.mobilePhoneNumber,\n itemInterestId: selectedResults.id,\n favoriteItemImageUrl: selectedResults.itemPictureImageUrl\n };\n\n selectedResultsSMS.push(contactObj);\n });\n\n var successcb = function (result) {\n var modalScope = {\n errors: result.errors,\n successes: result.successes,\n optedErrors: result.optedErrors\n };\n\n $modal.open({\n templateUrl: 'js/modules/contact/modals/bulk-twilio-send-result-modal.tpl.html',\n controller: 'BulkTwilioSendResultModalCtrl',\n resolve: {\n modalScope: function () {\n return modalScope;\n }\n },\n backdrop:'static'\n });\n\n };\n\n Sms.showSendModal(selectedResultsSMS, null, null, null, successcb, {\n showOptions: true\n });\n };\n });\n});\n\n","define('modules/item/search/search-ctrl',['./../module', 'angular', 'underscore'], function (module, angular, _) {\n 'use strict';\n\n module.controller('ItemSearchCtrl', function (\n $scope,\n $controller,\n $locale,\n $modal,\n $translate,\n $timeout,\n $state,\n $rootScope,\n AppLicense,\n Enums,\n eventsBus,\n Item,\n SizeGroup,\n ColorGroup,\n Department,\n Vendor,\n ItemAddOn,\n PurchaseOrder,\n TaxCode,\n User,\n departments,\n vendors,\n taxCodes,\n ItemModal,\n alerts,\n modalService,\n ImportService,\n ItemLabelsModal,\n Permissions,\n security,\n clientPortalSettings,\n inventorySettings,\n attributeGroups\n ) {\n var headers = [\n {\n title: 'item.#',\n sortField: 'itemNumber'\n },\n {\n title: 'item.barcode',\n sortField: 'qbEditSequence',\n isHidden: $rootScope.legacySystem !== 'BBL'\n },\n {\n title: 'common.name',\n sortField: 'name'\n },\n {\n title: 'item.vendor-name',\n sortField: 'vendorItemName'\n },\n {\n title: 'common.color',\n sortField: 'color'\n },\n {\n title: 'common.size',\n sortField: 'size'\n },\n {\n title: 'item.menu.in-stock'\n },\n {\n title: 'item.qoh',\n sortField: 'quantityOnHand'\n },\n {\n title: 'common.price',\n sortField: 'regularPrice'\n },\n {\n title: 'item.order-cost',\n sortField: 'orderCost',\n isHidden: !security.hasPermission('ITEM_COST')\n },\n {\n title: 'common.vendor',\n sortField: 'vendorName'\n },\n {\n title: 'common.department',\n sortField: 'departmentCode'\n }\n ];\n\n angular.extend(this, $controller('SearchCtrl', { $scope: $scope, headers: headers }));\n\n // pagination settings wrapper\n var perPageOptions = [{ size: 10, label: 10 }, { size: 25, label: 25 }, { size: 50, label: 50 }, { size: 100, label: 100 }, { size: 500, label: 500 }];\n $scope.pagination.perPageOptions = perPageOptions;\n\n eventsBus.subscribe('item:add', function () {\n $scope.search();\n });\n\n var license = AppLicense.get();\n //always override the retailerId regardless of what was used in previous visits.\n if($scope.initialFormValues) {\n $scope.initialFormValues.retailerId = license.retailerId;\n }\n\n $scope.attributeGroups = attributeGroups;\n\n if(license.linkedAccounts.length > 1 && license.plan.multiStore) {\n $scope.linkedAccounts = angular.copy(license.linkedAccounts);\n $scope.linkedAccounts.unshift({ linkedRetailerId:\"ALL_LOCATIONS\", nickName: $translate.instant('reports.common.all-locations') });\n }\n\n $scope.showMoreActions = security.hasPermission('MULTI_TRANSFER_ITEMS') && security.hasPermission('DELETE_ITEM') && license.linkedAccounts.length > 1 && license.plan.multiStore;\n\n // default search values\n $scope.initialValues = {\n status: Enums.statuses.active.value,\n retailerId: license.retailerId,\n isLinkedToMarketplace: Enums.marketplaceFilterType.all.value,\n isItemVisibleOnLookbook: Enums.showingOnLookbookFilterType.all.value,\n isItemSmartMatched: Enums.smartMatchedIgnoredFilterType.all.value,\n hasSalePrice: Enums.salePriceFilterType[0].value\n };\n\n $scope.formValues = angular.copy($scope.initialValues);\n $scope.security = security;\n $scope.advancedSearch = false;\n\n var defaultSortType = _(inventorySettings).findWhere({ type: 'INVTRY_DEFAULT_SORT_FIELD' });\n\n if (defaultSortType && headers[defaultSortType.value] && headers[defaultSortType.value].sortField) {\n $scope.pagination.sortField = headers[defaultSortType.value].sortField;\n if ($scope.activeHeader) {\n $scope.activeHeader.active = false;\n }\n headers[defaultSortType.value].active = true;\n $scope.activeHeader = headers[defaultSortType.value];\n }\n\n var defaultSortDirection = _(inventorySettings).findWhere({ type: 'INVTRY_DEFAULT_SORT_DIRECTION' });\n\n if(defaultSortDirection && defaultSortDirection.value) {\n $scope.pagination.sortDirection = defaultSortDirection.value === 'D' ? 'desc' : 'asc';\n }\n\n //add the \"All\" options to the lists\n if (vendors && vendors.result) {\n $scope.vendors = angular.copy(vendors.result);\n $scope.vendors.unshift({ name: $translate.instant('common.all') });\n }\n\n if (departments && departments.result) {\n $scope.departments = angular.copy(departments.result);\n $scope.departments.unshift({ name: $translate.instant('common.all') });\n }\n\n if (taxCodes) {\n $scope.taxCodes = _.filter(angular.copy(taxCodes), function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n\n $scope.taxCodes.unshift({ description: $translate.instant('common.all') });\n $scope.taxCodesWithoutAll = _.filter(angular.copy(taxCodes), function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n }\n\n //setup data for search form\n $scope.itemTypes = Enums.itemTypes;\n $scope.types = {\n inventoryItem: $translate.instant('item.types.inventory'),\n nonInventoryItem: $translate.instant('item.types.non-inventory'),\n service: $translate.instant('item.service'),\n rental: $translate.instant('item.rental')\n };\n\n $scope.marketplaceFilterType =[\n Enums.marketplaceFilterType.all,\n Enums.marketplaceFilterType.linked,\n Enums.marketplaceFilterType.unlinked\n ];\n\n $scope.showingOnLookbookFilterType = [\n Enums.showingOnLookbookFilterType.all,\n Enums.showingOnLookbookFilterType.showing,\n Enums.showingOnLookbookFilterType.not_showing\n ];\n\n $scope.smartMatchedIgnoredFilterType = [\n Enums.smartMatchedIgnoredFilterType.all,\n Enums.smartMatchedIgnoredFilterType.ignored,\n Enums.smartMatchedIgnoredFilterType.not_ignored\n ];\n\n $scope.statuses = [\n Enums.statuses.all,\n Enums.statuses.active,\n Enums.statuses.discontinued,\n Enums.statuses.inactive\n ];\n\n $scope.salePriceFilterType = Enums.salePriceFilterType;\n\n // determine visibility of client portal feature\n var portalFeature = _(clientPortalSettings).findWhere({ type: 'CP_FEATURE_LOOKBOOK' });\n var portalFeatureEnabled = portalFeature ? portalFeature.value === 'Y' : true;\n var userHasPermission = security.hasPermission(\"EDIT_SHOW_ON_LOOKBOOK\");\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n $scope.lookbookUpdatesAvailable = licenseHasPortalAccess && portalFeatureEnabled && userHasPermission;\n\n $scope.translations = {\n reorderPoint: $translate.instant('item.re-order-point')\n };\n\n $scope.results = $scope.selectedResults = [];\n $scope.search = function () {\n var params = $scope.buildFilterObject();\n\n // Format attributes\n if ($scope.formValues.attributes) {\n params.filterObject.attributes = $scope.formValues.attributes.map(function (attr) {\n return {\n attributeId: typeof attr === 'object' ? attr.attributeId : attr\n };\n });\n } else {\n $scope.formValues.attributes = [];\n }\n\n $scope.onAttributeSelected = function (id) {\n $scope.formValues.attributes.push(id);\n };\n\n $scope.onAttributeUnselected = function (id) {\n var index = $scope.formValues.attributes.indexOf(id);\n $scope.formValues.attributes.splice(index, 1);\n };\n\n $scope.onAttributeRemoveAll = function () {\n $scope.formValues.attributes = [];\n };\n\n if ($scope.formValues.itemNumber && isNaN($scope.formValues.itemNumber)){\n return;\n }\n Item.query(params, function (resultModel) {\n if($scope.formValues.retailerId === \"ALL_LOCATIONS\") {\n var nameMap = {};\n _.each(resultModel.result, function (result) {\n if(result.retailerId) {\n\n if(!nameMap.hasOwnProperty(result.retailerId)) {\n nameMap[result.retailerId] = getCompanyNickName(result.retailerId);\n }\n\n result.locationName = nameMap[result.retailerId];\n }\n });\n }\n\n _.each(resultModel.result, function (result) {\n if(result.reorderPoint && result.reorderPoint >= 0) {\n result.reorderPointDescription = $scope.translations.reorderPoint + ': ' + result.reorderPoint;\n }\n });\n\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.onVendorChange = function (vendorId) {\n\n //when the vendor changes, we need to reset the drop downs\n $scope.formValues.colorGroupId = null;\n $scope.formValues.sizeGroupId = null;\n\n var filterObject = { vendorId: vendorId ? vendorId : $scope.formValues.vendorId };\n if (filterObject.vendorId) {\n SizeGroup.query({ filterObject: filterObject, sortField: 'name' }, function (results) {\n $scope.sizeGroups = results;\n if (results && results.length) {\n $scope.size_groups = angular.copy(results);\n $scope.size_groups.unshift({ name: $translate.instant('common.all') });\n } else {\n $scope.size_groups = [];\n }\n });\n\n ColorGroup.query({ filterObject: filterObject, sortField: 'name' }, function (results) {\n $scope.colorGroups = results;\n if (results && results.length) {\n $scope.color_groups = angular.copy(results);\n $scope.color_groups.unshift({ name: $translate.instant('common.all') });\n } else {\n $scope.color_groups = [];\n }\n });\n\n ItemAddOn.listByVendorId(filterObject.vendorId, function(results) {\n $scope.vendorItemAddOns = results;\n });\n\n } else {\n $scope.colorGroups = [];\n $scope.color_groups = [];\n $scope.sizeGroups = [];\n $scope.size_groups = [];\n $scope.vendorItemAddOns = [];\n }\n };\n\n $scope.changeSearch = function () {\n $scope.advancedSearch = !$scope.advancedSearch;\n\n /**\n * Reset advanced search inputs if the user hides the advanced search inputs.\n */\n if(!$scope.advancedSearch) {\n $scope.formValues.isLinkedToMarketplace = Enums.marketplaceFilterType.all.value;\n $scope.formValues.isItemVisibleOnLookbook = Enums.showingOnLookbookFilterType.all.value;\n delete $scope.formValues.colorGroupId;\n delete $scope.formValues.sizeGroupId;\n delete $scope.formValues.attributes;\n delete $scope.formValues.description;\n delete $scope.formValues.taxCodeId;\n }\n };\n\n //load the vendor's color/size groups if the vendor is selected\n //initialFormValues is set in common/search-ctrl, which memorizes the previous search\n if ($scope.initialFormValues && $scope.initialFormValues.vendorId) {\n $scope.onVendorChange($scope.initialFormValues.vendorId);\n } else {\n $scope.colorGroups = [];\n $scope.color_groups = [];\n $scope.sizeGroups = [];\n $scope.size_groups = [];\n $scope.vendorItemAddOns = [];\n }\n\n $scope.showItemModal = ItemModal.openAdd;\n\n $scope.remove = function (item) {\n modalService.showConfirmation('item.message.are-you-sure-you-would-like-to-delete?', function () {\n item.$remove(function () {\n $scope.onResultRemoved(item);\n });\n });\n };\n\n $scope.onLocationChange = function() {\n\n $scope.results = [];\n\n //when the location changes, we need to reset the drop downs because they will need to be\n //re-initialized with the new retailerId for this location\n $scope.disableLinks = $scope.formValues.retailerId !== license.retailerId;\n\n //when the location changes, we need to reset the drop downs because they will need to be\n //re-initialized with the new retailerId for this location\n delete $scope.formValues.departmentId;\n delete $scope.formValues.vendorId;\n delete $scope.formValues.taxCodeId;\n\n if($scope.formValues.retailerId !== \"ALL_LOCATIONS\") {\n\n //show inputs that are location specific\n $scope.departmentCriteriaHidden = false;\n $scope.vendorCriteriaHidden = false;\n $scope.additionalOptionsHidden = false;\n\n var filter = { retailerId: $scope.formValues.retailerId, status: Enums.statuses.active.value };\n\n //now re-initialize the 3 lists for the new location selected\n Department.query({sortField: 'name', filterObject:filter}, function(departments) {\n if (departments && departments.result) {\n $scope.departments = angular.copy(departments.result);\n $scope.departments.unshift({ name: $translate.instant('common.all') });\n }\n });\n\n Vendor.query({sortField: 'name', filterObject:filter}, function(vendors) {\n if (vendors && vendors.result) {\n $scope.vendors = angular.copy(vendors.result);\n $scope.vendors.unshift({ name: $translate.instant('common.all') });\n }\n });\n\n TaxCode.query({filterObject:filter}, function(taxCodes) {\n if (taxCodes) {\n $scope.taxCodes = _.filter(angular.copy(taxCodes), function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n\n $scope.taxCodes.unshift({ description: $translate.instant('common.all') });\n $scope.taxCodesWithoutAll = _.filter(angular.copy(taxCodes), function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n }\n });\n\n\n } else {\n //hide inputs that are location specific\n $scope.departmentCriteriaHidden = true;\n $scope.vendorCriteriaHidden = true;\n $scope.additionalOptionsHidden = true;\n }\n\n };\n\n $scope.printLabels = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n ItemLabelsModal.open($scope.selectedResults);\n };\n\n $scope.changeStatus = function (status) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-status',\n params: {\n number: $scope.selectedResults.length,\n status: $translate.instant(status.description)\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-status?',\n params: {\n number: $scope.selectedResults.length,\n status: $translate.instant(status.description)\n }\n }, function () {\n Item.bulkUpdateStatus(status.value, _($scope.selectedResults).pluck('id'), successcb);\n });\n\n };\n\n $scope.changeTaxCode = function (taxCode) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-tax-code',\n params: {\n number: $scope.selectedResults.length,\n taxCode: taxCode.description\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-tax-code?',\n params: {\n number: $scope.selectedResults.length,\n taxCode: taxCode.description\n }\n }, function () {\n Item.bulkUpdateTaxCode(taxCode.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeItemType = function (itemType) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-item-type',\n params: {\n number: $scope.selectedResults.length,\n itemType: $translate.instant(itemType.description)\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-item-type?',\n params: {\n number: $scope.selectedResults.length,\n itemType: $translate.instant(itemType.description)\n }\n }, function () {\n Item.bulkUpdateItemType(itemType.value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changePromptToRegister = function (value) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-prompt-to-register-setting',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-prompt-to-register-setting?',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n }, function () {\n Item.bulkUpdatePromptToRegister(value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeAllowDiscount = function (value) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-do-not-allow-discounts-setting',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-do-not-allow-discounts-setting?',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n }, function () {\n Item.bulkUpdateDoNotAllowDiscounts(value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeIsSample = function (value) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-is-sample-setting',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-is-sample-setting?',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n }, function () {\n Item.bulkUpdateSample(value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeColorGroup = function (colorGroup) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-color-group',\n params: { number: $scope.selectedResults.length }\n });\n };\n\n var message = \"\";\n if (colorGroup.id !== 0) {\n message = {\n key: 'item.message.are-you-sure-you-would-like-to-change-the-color-group?',\n params: {\n number: $scope.selectedResults.length,\n colorGroup: colorGroup.name\n }\n };\n } else {\n message = {\n key: 'item.message.are-you-sure-you-would-like-to-remove-the-color-group?',\n params: { number: $scope.selectedResults.length }\n };\n }\n\n modalService.showConfirmation(message, function () {\n Item.bulkUpdateColorGroup(colorGroup.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeColor2Group = function (color2Group) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-accent-color-group',\n params: { number: $scope.selectedResults.length }\n });\n };\n\n var message = \"\";\n if (color2Group.id !== 0) {\n message = {\n key: 'item.message.are-you-sure-you-would-like-to-change-the-accent-color-group?',\n params: {\n number: $scope.selectedResults.length,\n colorGroup: color2Group.name\n }\n };\n } else {\n message = {\n key: 'item.message.are-you-sure-you-would-like-to-remove-the-accent-color-group?',\n params: { number: $scope.selectedResults.length }\n };\n }\n\n modalService.showConfirmation(message, function () {\n Item.bulkUpdateColor2Group(color2Group.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeSizeGroup = function (sizeGroup) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-size-group',\n params: { number: $scope.selectedResults.length }\n });\n };\n\n var message = \"\";\n if (sizeGroup.id !== 0) {\n message = {\n key: 'item.message.are-you-sure-you-would-like-to-change-the-size-group?',\n params: {\n number: $scope.selectedResults.length,\n sizeGroup: sizeGroup.name\n }\n };\n } else {\n message = {\n key: 'item.message.are-you-sure-you-would-like-to-remove-the-size-group?',\n params: { number: $scope.selectedResults.length }\n };\n }\n modalService.showConfirmation(message, function () {\n Item.bulkUpdateSizeGroup(sizeGroup.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.itemAddOn = function (addOn, addItem) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n if($scope.selectedResults.length > 500) {\n modalService.showMessage({ key: 'item.message.select-no-more-than-500', params: {\n action: $translate.instant(addItem ? 'common._adding' : 'common.removing')\n }}, 'common.message.wooops');\n return false;\n }\n\n var successcb = function (result) {\n $scope.search();\n alerts.pushSuccess({\n key: addItem ? 'alerts.item.successfully-added-item-add-ons' : 'alerts.item.successfully-removed-item-add-ons',\n params: {\n number: result.nbrRowsAffected,\n addOn: addOn.description\n }\n });\n };\n\n var message = {\n key: addItem ? 'item.message.are-you-sure-you-would-like-to-add-the-item-add-on?' : 'item.message.are-you-sure-you-would-like-to-remove-the-item-add-on?',\n params: {\n number: $scope.selectedResults.length,\n addOn: addOn.description\n }\n };\n\n\n modalService.showConfirmation(message, function () {\n Item.bulkAddOrRemoveItemAddOn(addOn.id, _($scope.selectedResults).pluck('id'), addItem, successcb);\n });\n };\n\n $scope.changeShowOnLookbook = function (value) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-lookbook-visibility',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-lookbook-visibility?',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n }, function () {\n Item.bulkUpdateShowOnLookbook(value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeSalePrice = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n ItemModal.openSalePriceModal($scope.selectedResults, function (result) {\n if(result.nbrRowsAffected !== $scope.selectedResults.length) {\n var nbrRowsFailed = $scope.selectedResults.length > 1 ? $scope.selectedResults.length - result.nbrRowsAffected : 1;\n\n alerts.pushError({\n key: 'alerts.item.failed-to-change-the-sale-price',\n params: {\n number: nbrRowsFailed,\n total: $scope.selectedResults.length\n }\n });\n } else {\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-sale-price',\n params: {\n number: $scope.selectedResults.length\n }\n });\n }\n\n $scope.search();\n });\n };\n\n $scope.removeSalePrice = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-sale-price',\n params: {\n number: $scope.selectedResults.length\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-remove-the-sale-price?',\n params: {\n number: $scope.selectedResults.length\n }\n }, function () {\n Item.bulkUpdateRemoveSalePrice(_($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.removeMarketplaceLinkage = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-marketplace-linkage',\n params: {\n number: $scope.selectedResults.length\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-remove-the-marketplace-linkage?',\n params: {\n number: $scope.selectedResults.length\n }\n }, function () {\n Item.bulkUnlinkMarketplaceIds(_($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.changeReorderPoint = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n ItemModal.openReorderPointModal($scope.selectedResults, function (result) {\n if(result.nbrRowsAffected !== $scope.selectedResults.length) {\n var nbrRowsFailed = $scope.selectedResults.length > 1 ? $scope.selectedResults.length - result.nbrRowsAffected : 1;\n\n alerts.pushError({\n key: 'alerts.item.failed-to-change-the-reorder-point',\n params: {\n number: nbrRowsFailed,\n total: $scope.selectedResults.length\n }\n });\n } else {\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-reorder-point',\n params: {\n number: $scope.selectedResults.length\n }\n });\n }\n\n $scope.search();\n });\n };\n\n $scope.removeReorderPoint = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-reorder-point',\n params: {\n number: $scope.selectedResults.length\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-remove-the-reorder-point?',\n params: {\n number: $scope.selectedResults.length\n }\n }, function () {\n Item.bulkUpdateRemoveReorderPoint( _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.smartMatchIgnore = function(value) {\n if(!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.item.successfully-changed-the-smart-match-ignore',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-change-the-smart-match-ignore?',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('common.yes') : $translate.instant('common.no'))\n }\n }, function () {\n Item.bulkUpdateSmartMatchIgnore(value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.createPOForItems = function (vendorId) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n if (!vendorId) {\n modalService.showMessage('alerts.vendor.please-select-a-vendor-before-creating-a-stock-po');\n return false;\n }\n\n var vendor = _($scope.vendors).findWhere({ id: vendorId });\n\n if(!vendor) {\n modalService.showMessage('alerts.vendor.please-select-a-vendor-before-creating-a-stock-po');\n return false;\n }\n\n var successcb = function (po) {\n $state.go('purchase-order.edit', { id: po.id }, { location: 'replace' });\n };\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-create-stock-po?',\n params: {\n number: $scope.selectedResults.length,\n vendor: (vendor.name)\n }\n }, function () {\n PurchaseOrder.createPOForItems(vendorId, User.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.exportToExcel = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('item.message.please-select-at-least-1-item', 'common.message.wooops');\n return false;\n }\n\n modalService.showConfirmation({\n key: 'item.message.are-you-sure-you-would-like-to-export-to-excel?',\n params: { number: $scope.selectedResults.length }\n }, function () {\n Item.exportToExcel(_($scope.selectedResults).pluck('id'), $scope.buildFilterObject());\n });\n };\n\n $scope.import = function () {\n var usesVAT = $locale.usesVat;\n ImportService.openImportModal({\n modelDescription: $translate.instant('item.items'),\n Resource: Item,\n allowUpdates: true,\n templateUrlCreate: '/assets/docs/import_templates/items' + (usesVAT ? '_vat' : '') + '.csv',\n templateUrlUpdate: '/assets/docs/import_templates/items_update.csv'\n });\n };\n\n $scope.onItemTypeChanged = function () {\n _(_($scope.types).keys()).each(function (field) {\n if (field === $scope.itemType) {\n $scope.formValues[field] = true;\n } else {\n delete $scope.formValues[field];\n }\n });\n };\n\n $scope.getColSpan = function (defaultColSpanValue) {\n if(security.hasPermission('ITEM_COST')) {\n defaultColSpanValue++;\n }\n\n if($rootScope.legacySystem === 'BBL') {\n defaultColSpanValue++;\n }\n\n if(license.retailerId !== $scope.formValues.retailerId && $scope.formValues.retailerId !== 'ALL_LOCATIONS') {\n defaultColSpanValue--;\n }\n\n return defaultColSpanValue;\n };\n\n function getCompanyNickName(retailerId) {\n var location = _(license.linkedAccounts).findWhere({ linkedRetailerId: retailerId });\n return location && location.nickName;\n }\n\n $scope.isActiveRetailer = function (retailerId) {\n return retailerId === license.retailerId;\n };\n\n $scope.transfer = function (item) {\n if(!item) {\n return;\n }\n\n if(item.quantityOnHand <= 0 && (item.rental || item.inventoryItem)) {\n return modalService.showMessage(\"Item's quantity on hand must be greater than 0 to transfer.\");\n }\n\n //populate taxCode and department description by matching the ids against the drop downs\n var taxCode = _($scope.taxCodes).findWhere({ id:item.taxCodeId });\n\n if(taxCode) {\n item.taxCodeDescription = taxCode.description;\n }\n\n var department = _($scope.departments).findWhere({ id:item.departmentId });\n\n if(department) {\n item.departmentName = department.name;\n item.departmentCode = department.code;\n }\n\n var vendor = _($scope.vendors).findWhere({ id:item.vendorId });\n\n if(vendor) {\n item.vendorName = vendor.name;\n item.vendorCode = vendor.code;\n }\n\n $modal.open({\n templateUrl: 'js/modules/item/modals/copy-item-modal.tpl.html',\n controller: 'CopyItemModalController',\n resolve: {\n transferView: function() {\n return item.rental || item.inventoryItem ? 'new' : 'old';\n },\n itemToCopy: function () {\n return item;\n },\n itemPictures: function (ItemPicture) {\n return ItemPicture.query({ filterObject: { inventoryItemId: item.id } });\n },\n },\n windowClass: '__medium',\n backdrop: 'static',\n keyboard: false\n });\n };\n\n /**\n * Watchers\n */\n\n var initializing = true;\n\n $scope.$watch('formValues.vendorId', function(value) {\n if (initializing) {\n $timeout(function() { initializing = false; });\n } else {\n if(!value) {\n $scope.onVendorChange(value);\n }\n }\n });\n\n $scope.$watchGroup(['formValues.inventoryItem', 'formValues.nonInventoryItem', 'formValues.service', 'formValues.rental'], function (values) {\n switch (true) {\n case values[0]:\n $scope.itemType = 'inventoryItem';\n break;\n case values[1]:\n $scope.itemType = 'nonInventoryItem';\n break;\n case values[2]:\n $scope.itemType = 'service';\n break;\n case values[3]:\n $scope.itemType = 'rental';\n break;\n default:\n $scope.itemType = null;\n }\n });\n });\n});\n\n","define('modules/item/config',['./module'], function (module) {\n 'use strict';\n\n module.constant('LABEL_CONFIG', {\n DYMO: {\n SHIPPING: {\n INVENTORY_ITEM_BARCODE_AND_PRICE: \"\\n\" +\n \"
\\n\" +\n \" Landscape \\n\" +\n \" Shipping \\n\" +\n \" 30323 Shipping \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" PRICE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Center \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" PRICE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" BARCODE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" True \\n\" +\n \" 1234567 \\n\" +\n \" Code128Auto \\n\" +\n \" Medium \\n\" +\n \" Bottom \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" None \\n\" +\n \" 0 \\n\" +\n \" Center \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ORIGINAL_PRICE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Center \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ORIGINAL_PRICE\\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\",\n INVENTORY_ITEM_NO_BARCODE_NO_PRICE: \"\\n\" +\n \"
\\n\" +\n \" Landscape \\n\" +\n \" Shipping \\n\" +\n \" 30323 Shipping \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\",\n INVENTORY_ITEM_BARCODE_ONLY: \"\\n\" +\n \"
\\n\" +\n \" Landscape \\n\" +\n \" Shipping \\n\" +\n \" 30323 Shipping \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" BARCODE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" True \\n\" +\n \" 1234567 \\n\" +\n \" Code128Auto \\n\" +\n \" Medium \\n\" +\n \" Bottom \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" None \\n\" +\n \" 0 \\n\" +\n \" Center \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\",\n INVENTORY_ITEM_PRICE_ONLY: \"\\n\" +\n \"
\\n\" +\n \" Landscape \\n\" +\n \" Shipping \\n\" +\n \" 30323 Shipping \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ITEM_NAME \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS2 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS3 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_COLORS4 \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" DESCRIPTION \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" LG_COLOR \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" SIZE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Left \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" AVA_SIZES \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" PRICE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Center \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" PRICE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ORIGINAL_PRICE \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" Rotation0 \\n\" +\n \" False \\n\" +\n \" False \\n\" +\n \" Center \\n\" +\n \" Middle \\n\" +\n \" None \\n\" +\n \" True \\n\" +\n \" False \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" ORIGINAL_PRICE\\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\" +\n \" \\n\"\n }\n }\n });\n});\n\n","define('modules/marketplace/download/matches-popover-directive',[\n './module',\n 'angular',\n 'text!./matches-popover-directive.tpl.html'\n], function (module, angular, popoverTpl) {\n 'use strict';\n\n module.directive('matchesPopover', function ($compile, $timeout) {\n return {\n restrict: 'A',\n scope: {\n item: '=matchesPopover'\n },\n link: function (scope, element) {\n var popover;\n\n element.click(function () {\n var compilerTpl = $compile('
' + popoverTpl + '
');\n\n // Open popover\n popover = element.popover({\n content: compilerTpl(scope),\n placement: 'bottom',\n trigger: 'manual',\n html: true,\n container: 'body',\n template: '
'\n });\n\n $timeout(function () {\n scope.showPopover();\n }, 0);\n\n });\n\n scope.showPopover = function () {\n popover.popover('show');\n angular.element(document).on('mousedown', hidePopover);\n };\n\n // Close on clicking outside\n function hidePopover(event) {\n var target = angular.element(event.target);\n var isInsidePopover = target.closest('.popover').length && !target.is('a');\n var isTriggerLink = target.is(element);\n\n if (!isInsidePopover && !isTriggerLink) {\n popover.popover('hide');\n angular.element(document).off('mousedown', hidePopover);\n }\n }\n }\n };\n });\n});\n\n","define('modules/prom-registry-modal/register-to-prom-ctrl',['./module', 'angular', 'underscore'], function (module, angular, _) {\n 'use strict';\n\n module.controller('RegisterToPromCtrl', function ($scope, $state, $controller, $modal, security, Contact, modalService, Enums, Item, RegisteredItem, transactionItem, readonly, settings, alerts, contact, successcb) {\n /**\n * Init models\n */\n\n $scope.hasTransactionItem = !!transactionItem;\n $scope.registeredItems = null;\n\n $scope.readonly = readonly || false;\n\n //determine how the user want's their items to be registered (either Item Name only or Item Name and Color\n var registerByItemNameAndColor;\n var registerByVendorItemName;\n var promRegistrationType = _(settings).findWhere({ type: 'PROM_REGISTRATION_TYPE' }) || {};\n switch (promRegistrationType.value) {\n case 'S':\n registerByItemNameAndColor = false;\n break;\n case 'SC':\n registerByItemNameAndColor = true;\n break;\n case 'V':\n registerByVendorItemName = true;\n registerByItemNameAndColor = false;\n break;\n case 'VC':\n registerByVendorItemName = true;\n registerByItemNameAndColor = true;\n break;\n default:\n registerByItemNameAndColor = true;\n }\n\n if (!$scope.hasTransactionItem) {\n $scope.item = {};\n $scope.$watch('item', function (item) {\n if (!item || !item.id) {\n $scope.registeredEventLocationsIds = [];\n return;\n }\n var filterObject = registerByVendorItemName ? { filterObject: { vendorItemName : item.vendorItemName } } : { filterObject: { itemName : item.name } };\n\n if (registerByItemNameAndColor) {\n filterObject.filterObject.itemColor = item.color;\n }\n\n RegisteredItem.query(filterObject, function (registeredItems) {\n $scope.registeredEventLocationsIds = _(registeredItems.result).pluck('eventLocationId');\n }, null, '&validateItems=true');\n }, true);\n } else {\n var filterObject = registerByVendorItemName ? { filterObject: { vendorItemName : transactionItem.itemVendorItemName } } : { filterObject: { itemName : transactionItem.itemName } };\n\n if (registerByItemNameAndColor) {\n filterObject.filterObject.itemColor = transactionItem.itemColor;\n }\n\n RegisteredItem.query(filterObject, function (registeredItems) {\n $scope.registeredItems = registeredItems.result;\n $scope.registeredEventLocationsIds = _($scope.registeredItems).pluck('eventLocationId');\n }, null, '&validateItems=true');\n }\n\n $scope.filterObject = {\n status: Enums.statuses.active.value\n };\n $scope.eventTypes = _(Enums.eventTypes).values();\n $scope.filterObject.eventTypeId = Enums.eventTypes.prom.value;\n\n $scope.itemSelectorOptions = {\n settingsPopoverPlacement: 'bottom',\n onChooseItem: function (item) {\n $scope.item = item;\n },\n onAddNewItem: function (item) {\n $scope.item = item;\n }\n };\n\n /**\n * Methods\n */\n\n $scope.registerEventLocations = function () {\n // get all checked ELs\n var checkedEventLocations = _($scope.resources).where({ checked: true });\n\n if (!checkedEventLocations.length) {\n return;\n }\n\n // convert it to RIs\n var newRegisteredItems = _(checkedEventLocations).map(eventLocationToRegisterItem);\n\n var onDone = _.after(newRegisteredItems.length, function () {\n\n if(contact && security.hasPermission('CONTACTS')) {\n var locationsWithDates = _(checkedEventLocations).filter(function(location) {\n return location.date;\n });\n\n if (locationsWithDates && locationsWithDates.length) {\n if(locationsWithDates.length > 1 || locationsWithDates[0].date !== contact.eventDate) {\n modalService.showConfirmation('alerts.prom-registry.contact-event-date-different', function () {\n if(locationsWithDates.length > 1) {\n //open modal\n $modal.open({\n templateUrl: 'js/modules/prom-registry-modal/select-location-modal.tpl.html',\n controller: 'SelectLocationModalCtrl',\n resolve: {\n locations: function () {\n return locationsWithDates;\n }\n },\n windowClass: '__medium'\n }).result.then(function(result) {\n if(result) {\n updateContactsEeventDate(contact, result);\n }\n });\n } else {\n updateContactsEeventDate(contact, locationsWithDates[0]);\n }\n });\n }\n }\n }\n\n alerts.pushSuccess('alerts.prom-registry.successfully-registered-item');\n $scope.$close(true);\n });\n\n // POST each RI to the server\n _(newRegisteredItems).each(function (registeredItem) {\n var registeredItemResource = new RegisteredItem(registeredItem);\n registeredItemResource.$save(onDone);\n });\n };\n\n $scope.rejectNew = function (location) {\n return !location.isNew;\n };\n\n function updateContactsEeventDate(contact, location) {\n Contact.updateEventDate(contact.id ,location.date, function (updatedContact) {\n successcb && successcb(updatedContact);\n });\n }\n\n /**\n * Helpers\n */\n\n function eventLocationToRegisterItem(eventLocation) {\n var result;\n\n if ($scope.hasTransactionItem) {\n result = {\n itemId: transactionItem.inventoryItemId,\n itemColor: transactionItem.itemColor,\n itemSize: transactionItem.itemSize,\n transactionId: transactionItem.transactionId,\n eventLocationId: eventLocation.id\n };\n } else {\n result = {\n itemId: $scope.item.id,\n itemColor: $scope.item.color,\n itemSize: $scope.item.size,\n eventLocationId: eventLocation.id\n };\n }\n\n return result;\n }\n\n /**\n * Watchers\n */\n\n // reset checked event location on event type changing\n $scope.$watch('filterObject.eventTypeId', function () {\n _($scope.resources).each(function (eventLocation) {\n eventLocation.checked = null;\n });\n });\n\n // set form validity\n $scope.$watch('resources', function (eventLocations) {\n $scope.isFormInvalid = !_(eventLocations).where({ checked: true }).length;\n }, true);\n\n /**\n * Init\n */\n\n var dependencies = {\n $scope: _($scope).extend({\n prefilledModel: $scope.filterObject\n })\n };\n\n // get access to onAddClick()\n _(this).extend($controller('SettingsEventLocationCtrl', dependencies));\n\n $scope.openAddEventLocationModal = function () {\n $scope\n .onAddClick()\n .then(function (eventLocation) {\n var foundLocation = _($scope.resources).findWhere({ id: eventLocation.id });\n foundLocation.isNew = true;\n foundLocation.checked = true;\n\n // Scroll inner table scroll to top so user can see recently added locations\n angular.element('.event-locations-table').scrollTop(0);\n });\n };\n });\n});\n\n","define('modules/resources/purchase-order',['angular', 'moment', 'underscore', './backend-resource'], function (angular, moment, _) {\n 'use strict';\n\n return angular.module('bl.resources.purchase-order', ['bl.resources.backend-resource'])\n .factory('PurchaseOrder', function (backendResource, $http, $window, security, CompanySetting, UtilsHelper, PrintHelper) {\n\n var resource = backendResource('purchaseOrders');\n\n resource.associatedWithContact = function (filterObj, successcb, errorcb) {\n var queryParameters = resource.buildQueryParameters(filterObj);\n var httpPromise = $http.get(resource.url + '/associatedWithContact/' +\n (filterObj.filterObject.contactId || 0) + '?' +\n queryParameters.join('&'));\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.createForTrxItems = function (employeeId, doCopyNotes, transactionItems, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/createForTrxItems/' + employeeId + '/' + doCopyNotes, transactionItems);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.containingItem = function (filterObj, successcb, errorcb) {\n var queryParameters = resource.buildQueryParameters(filterObj);\n var httpPromise = $http.get(resource.url + '/containingItem/' +\n (filterObj.filterObject.startDate ? moment(filterObj.filterObject.startDate).format('YYYY-MM-DD') : 'null') + '/' +\n (filterObj.filterObject.endDate ? moment(filterObj.filterObject.endDate).format('YYYY-MM-DD') : 'null') + '?' +\n queryParameters.join('&') +\n '&vendorItemName=' + (filterObj.filterObject.vendorItemName || \"NONE\")\n );\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.unconfirmedPOs = function (filterObj, successcb, errorcb) {\n var queryParameters = resource.buildQueryParameters(filterObj);\n var httpPromise = $http.get(resource.url + '/unconfirmedPOs/' +\n (filterObj.filterObject.vendorId || 0) + '?' +\n 'eventDateStart=' + (filterObj.filterObject.eventDateAfter ? moment(filterObj.filterObject.eventDateAfter).format('YYYY-MM-DD') : '') + '&' +\n 'eventDateEnd=' + (filterObj.filterObject.eventDateBefore ? moment(filterObj.filterObject.eventDateBefore).format('YYYY-MM-DD') : '') + '&' +\n queryParameters.join('&'));\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.unsubmittedPOs = function (filterObj, successcb, errorcb) {\n var queryParameters = resource.buildQueryParameters(filterObj);\n var httpPromise = $http.get(resource.url + '/unsubmittedPOs/' +\n (filterObj.filterObject.vendorId || 0) + '?' +\n 'eventDateStart=' + (filterObj.filterObject.eventDateAfter ? moment(filterObj.filterObject.eventDateAfter).format('YYYY-MM-DD') : '') + '&' +\n 'eventDateEnd=' + (filterObj.filterObject.eventDateBefore ? moment(filterObj.filterObject.eventDateBefore).format('YYYY-MM-DD') : '') + '&' +\n queryParameters.join('&'));\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.pastShipDatePOs = function (settings, successcb, errorcb) {\n var filterObj = {\n shipDateBefore: moment().subtract(UtilsHelper.getDaysFromWeeks(settings, CompanySetting.PO_PENDING_PO_PAST_SHIPPING_WINDOW), 'days').format('YYYY-MM-DD'),\n status: 'P'\n };\n\n var httpPromise = $http.post(resource.url + '/list', filterObj);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.readByNumber = function (number, successcb, errorcb) {\n number = String(number).replace(/[^\\d]/g, '');\n\n var httpPromise = $http.get(resource.url + '/readByNumber/' + number);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.print = function (id) {\n var token = encodeURIComponent(security.readCookie().token);\n PrintHelper.printPDF(resource.url + '/' + id + '/po.pdf?token=' + token);\n };\n\n resource.addLineItem = function (purchaseOrderItem, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/' + purchaseOrderItem.purchaseOrderId + '/addLineItem', purchaseOrderItem);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.updateLineItem = function (purchaseOrderItem, syncChangesToSO, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/' + purchaseOrderItem.purchaseOrderId + '/updateLineItem/' + syncChangesToSO, purchaseOrderItem);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.deleteLineItem = function (purchaseOrderItem, employeeId, successcb, errorcb) {\n var httpPromise = $http.delete(resource.url + '/' + purchaseOrderItem.purchaseOrderId + '/deleteLineItem/' + purchaseOrderItem.id + '/' + employeeId);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.deleteLineItemAddOn = function (addOnItem, employeeId, successcb, errorcb) {\n var httpPromise = $http.delete(resource.url + '/' + addOnItem.purchaseOrderId + '/deleteLineItemAddOn/' + addOnItem.id + '/' + employeeId);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.deleteLineItems = function (purchaseOrderId, employeeId, ids, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/' + purchaseOrderId + '/deleteLineItems/' + employeeId, ids);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.void = function (purchaseOrderId, employeeId, successcb, errorcb) {\n var httpPromise = $http.put(resource.url + '/' + purchaseOrderId + '/voidPurchaseOrder/' + employeeId);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.bulkPrint = function (ids) {\n var param = \"\";\n for (var i = 0; i < ids.length; i++) {\n param += \"id=\" + ids[i] + \"&\";\n }\n\n var token = encodeURIComponent(security.readCookie().token);\n PrintHelper.printPDF(resource.url + '/printMultiple.pdf?token=' + token + \"&\" + param, true);\n };\n\n resource.previewForTrxs = function (ids, successcb, errorcb) {\n var payload = _(ids).map(function (id) {\n return { id: id };\n });\n\n var httpPromise = $http.post(resource.url + '/previewForTrxs', payload);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.listByIds = function (ids, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/listByIds', ids);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb);\n };\n\n resource.getTrxLineItemsFromPOItemIds = function (ids, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/getTrxLineItemsFromPOItemIds', ids);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, true);\n };\n\n resource.bulkUpdateEmployee = function (empId, idList, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/bulkUpdateEmployee/' + empId, idList);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n resource.createPOForItems = function (vendorId, employeeId, idList, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/createPOForItems/' + vendorId + '/' + employeeId, idList);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n resource.updateContactAssociatedWithPOItems = function (contactId, transactionId, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/updateContactAssociatedWithPOItems/' + contactId + '/' + transactionId, {});\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n resource.sendPurchaseOrder = function(request, successcb, errorcb) {\n var httpPromise = $http.post(resource.url + '/sendPurchaseOrder', request);\n return resource.thenFactoryMethod(httpPromise, successcb, errorcb, false);\n };\n\n return resource;\n });\n});\n\n","define('modules/purchase-order/purchase-order-ctrl',['./module', 'angular', 'underscore', 'moment'], function (module, angular, _, moment) {\n 'use strict';\n\n module.controller('PurchaseOrderController', function (\n $compile,\n $scope,\n $rootScope,\n $window,\n $state,\n $timeout,\n $translate,\n $locale,\n $filter,\n $modal,\n Permissions,\n eventsBus,\n Enums,\n modalService,\n EmailService,\n Email,\n purchaseOrder,\n ResourcesHelper,\n security,\n Item,\n ItemModal,\n ItemAddOn,\n ItemHelper,\n PurchaseOrder,\n PurchaseOrderItem,\n PurchaseOrderItemAddOn,\n ReceivingVoucher,\n Vendor,\n Contact,\n ContactModal,\n CompanySetting,\n alerts,\n employees,\n settings,\n User,\n itemSizesModal\n ) {\n /**\n * Init models\n */\n $scope.selectedResults = [];\n $scope.locale = $locale;\n $scope.sortType = 'sequenceNumber'; // set the default sort type\n $scope.sortReverse = false; // set the default sort order\n $scope.hooks = [];\n\n $scope.poItemHeaders = [\n {\n title: 'item.vendor-name',\n sortField:'itemVendorItemName',\n active: true\n },\n {\n title: 'common.color',\n css: { minWidth: 100 },\n sortField:'itemColor',\n active: false\n },\n {\n title: 'purchase-order.acc-color',\n css: { minWidth: 100 },\n sortField:'itemColor2',\n active: false\n },\n {\n title: 'common.size',\n css: { width: 75 },\n sortField:'itemSize',\n active: false\n },\n {\n title: 'item.qty',\n css: { width: 78 },\n sortField:'quantity',\n active: false\n },\n {\n title: 'contact.purchase.headers.qty-recv',\n sortField:'quantityReceived',\n active: false,\n css: { width: 75 }\n },\n {\n title: 'purchase-order.voucher-number',\n css: { width: 90 }\n },\n {\n title: 'item.order-cost',\n sortField:'cost',\n active: false,\n css: { width: 100 }\n },\n {\n title: $locale.vatLabelKey,\n sortField:'vat',\n active: false,\n css: { width: 100 },\n isHidden: !$scope.locale.usesVat\n },\n {\n title: 'item.ext-cost',\n sortField:'extCost',\n active: false,\n css: { width: 100 }\n }\n ];\n\n $scope.sortHeader = function (header) {\n if(header && header.active) {\n $scope.sortReverse = !$scope.sortReverse;\n }\n\n _.each($scope.poItemHeaders, function (poItemHeader) {\n poItemHeader.active = poItemHeader.sortField && poItemHeader.sortField === header.sortField ? true: false;\n });\n\n $scope.sortType = header.sortField;\n\n $scope.purchaseOrder.lineItems = $filter('orderBy')($scope.purchaseOrder.lineItems, $scope.sortType, $scope.sortReverse);\n\n $scope.updateSequence();\n };\n\n var ignoreRedirectWarning = false;\n $scope.employees = ResourcesHelper.adaptList('employees', employees);\n $scope.purchaseOrder = purchaseOrder;\n $scope.readonly = $scope.purchaseOrder.status === Enums.statuses.complete.value\n || $scope.purchaseOrder.status === Enums.statuses.voided.value\n || !security.hasPermission(\"EDIT_PURCHASE_ORDERS\");\n\n $scope.itemAddOnsPerm = {\n add: security.hasPermission(Permissions.ADD_PO_ITEM_ADD_ON),\n set: security.hasPermission(Permissions.SELECT_PO_ITEM_ADD_ON),\n editNotes: security.hasPermission(Permissions.EDIT_PO_ITEM_ADD_ON_NOTE),\n editDesc: security.hasPermission(Permissions.EDIT_PO_ITEM_ADD_ON_DESC),\n editCost: security.hasPermission(Permissions.EDIT_PO_ITEM_ADD_ON_COST),\n remove: security.hasPermission(Permissions.REMOVE_PO_ITEM_ADD_ON),\n addOnRequired: security.hasPermission(Permissions.PO_ITEM_ADD_ON_REQUIRED)\n };\n\n // Sort line items on init\n $scope.purchaseOrder.lineItems = _($scope.purchaseOrder.lineItems).sortBy('sequenceNumber');\n\n $scope.orderTypes = [\n Enums.purchaseOrderTypes.stock,\n Enums.purchaseOrderTypes.customer\n ];\n\n $scope.itemSelectorOptions = {\n searchField: 'vendorItemName',\n addNewItemSaveButtonLabel: $translate.instant('purchase-order.save-and-add'),\n additionalFilterObject: { 'vendorId': $scope.purchaseOrder.vendorId },\n prefillObject: { 'vendorId': $scope.purchaseOrder.vendorId },\n onAddNewItem: getAllItemValues,\n onChooseItem: getAllItemValues\n };\n\n function getAllItemValues(item) {\n ItemAddOn.listByItemId(item.id, function(itemAddOns) {\n item.itemAddOns = itemAddOns;\n onAddLineItem(item);\n });\n }\n\n function onAddLineItem(item) {\n var poItem = itemToPurchaseOrderItem(item);\n $scope.isNewPurchaseOrder = false;\n\n PurchaseOrder.addLineItem(poItem, function(purchaseOrder) {\n angular.extend($scope.purchaseOrder, purchaseOrder);\n if($scope.purchaseOrder.lineItems.length === 1) {\n $scope.purchaseOrder.vendorName = item.vendorName;\n $scope.purchaseOrder.vendorId = item.vendorId;\n $scope.savePurchaseOrder();\n }\n\n $scope.getAvailableAddOnsAndOpenModal(_(purchaseOrder.lineItems).last(), item, { showModalOnlyIfAddOnsAvailable: true, fetchItemAddOns: $scope.itemAddOnsPerm.set });\n });\n }\n\n function openAddOnModal(poItem, addOnsResult, options) {\n var allowedToClose = !$scope.itemAddOnsPerm.addOnRequired;\n\n if(options && options.userSelected) {\n allowedToClose = true;\n }\n\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/add-item-add-ons.tpl.html',\n controller: 'AddItemAddOnsCtrl',\n windowClass: '__large',\n backdrop: 'static',\n keyboard: allowedToClose,\n resolve: {\n entityType: function() {\n return 'purchaseOrder';\n },\n entityItem: function () {\n return poItem;\n },\n addOnsResult: function () {\n return addOnsResult;\n },\n item: function () {\n return addOnsResult.item;\n },\n allowedToClose: function () {\n return allowedToClose;\n }\n }\n })\n .result\n .then(function (addOnsModalResult) {\n var poItemModifications = addOnsModalResult && addOnsModalResult.poItemModifications;\n if(poItemModifications && poItemModifications.lineItemAddOns && poItemModifications.lineItemAddOns.length) {\n $scope.savePurchaseOrderItem(_.extend(poItem, poItemModifications));\n }\n });\n }\n\n $scope.addItemAddOn = function(poItem) {\n if($scope.readonly || !$scope.itemAddOnsPerm.set) {\n return;\n }\n\n getAvailableAddItemsOnsAndOpenModal(poItem, null, { fetchItemAddOns: true, showModalOnlyIfAddOnsAvailable: false, userSelected: true });\n };\n\n $scope.getAvailableAddOnsAndOpenModal = function(purchaseOrderItem, item, options, callback) {\n var addOnRequest = {\n leadTimes: false,\n itemAddOns: options.fetchItemAddOns,\n purchaseOrder: $scope.purchaseOrder,\n purchaseOrderItem: purchaseOrderItem,\n item: item\n };\n\n PurchaseOrderItem.getAvailableAddOns(addOnRequest, function (addOnsResult) {\n if(addOnsResult.itemAddOns === null) {\n addOnsResult.itemAddOns = [];\n }\n\n var lineItemAddOns = _(purchaseOrderItem.lineItemAddOns).filter(function(addOn) {\n return addOn.addOnId !== 0 && addOn.addOnId !== null;\n });\n\n if(options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnRequest.itemAddOns && addOnsResult.itemAddOns.length > 0 && lineItemAddOns.length !== addOnsResult.itemAddOns.length) {\n openAddOnModal(purchaseOrderItem, addOnsResult, { userSelected: purchaseOrderItem.lineItemAddOns && purchaseOrderItem.lineItemAddOns.length });\n }\n });\n };\n\n $scope.switchToEdit = function(event, addOn, type, index) {\n if(event) {\n event.preventDefault();\n }\n\n addOn[type + 'OldValue'] = addOn[type];\n addOn.show = {};\n addOn.show[type] = index;\n };\n\n $scope.switchToView = function (event, addOn, poItem, type, index) {\n if(!addOn.show) {\n return;\n }\n\n if(event) {\n event.preventDefault();\n }\n\n var oldValue = addOn[type + 'OldValue'];\n\n if(oldValue !== undefined && oldValue !== addOn[type]) {\n if($scope.validateAddOnItemValue(type, addOn, oldValue, poItem)) {\n if(type === 'cost') {\n if(!addOn.cost) {\n addOn.cost = 0;\n }\n\n $scope.onAddOnItemCostChange(poItem, addOn.cost, oldValue, index);\n } else if(type === 'description') {\n $scope.savePurchaseOrderItem(poItem, false, function () {\n alerts.pushSuccess('alerts.transaction.successfully-updated-add-on-item');\n });\n }\n } else {\n addOn[type] = oldValue;\n delete addOn[type + 'OldValue'];\n }\n }\n\n delete addOn.show;\n };\n\n $scope.onAddOnItemCostChange = function(poItem, newValue, oldValue) {\n newValue = +newValue;\n\n if(newValue > oldValue) {\n poItem.cost = poItem.cost + (newValue - oldValue);\n poItem.extCost = poItem.extCost + (newValue - oldValue);\n } else {\n poItem.cost = poItem.cost - (oldValue - newValue);\n poItem.extCost = poItem.extCost - (oldValue - newValue);\n }\n\n if ($scope.locale.usesVat && $scope.purchaseOrder.vendor) {\n var vatPercent = $scope.purchaseOrder.vendor.vatPercent ? parseFloat($scope.purchaseOrder.vendor.vatPercent) : 0;\n poItem.vat = poItem.cost * vatPercent / 100;\n }\n\n $scope.savePurchaseOrderItem(poItem, false, function () {\n alerts.pushSuccess('alerts.transaction.successfully-updated-add-on-item');\n });\n };\n\n $scope.sortableOptions = {\n tolerance: 'pointer',\n revert: 100,\n distance: 5,\n forcePlaceholderSize: true,\n handle: \".handle\",\n helper: function (e, ui) {\n ui.children().each(function () {\n $(this).width($(this).width());\n });\n\n return ui;\n },\n stop: function () {\n // Reindex line items\n _($scope.purchaseOrder.lineItems).each(function (line, index) {\n line.sequenceNumber = index;\n });\n\n $scope.savePurchaseOrder();\n }\n };\n\n /**\n * Methods\n */\n\n $scope.sendPurchaseOrder = function(hook) {\n modalService.showStaticConfirmation({ key: 'purchase-order.would-like-to-send-po?', params: { url: hook.value }}, function () {\n $scope.sendingPO = true;\n\n var request = { id: $scope.purchaseOrder.id, employeeId: User.id, endPointURL: hook.value };\n PurchaseOrder.sendPurchaseOrder(request, function(response) {\n $scope.sendingPO = false;\n\n if(response && response.status === 200) {\n hook.sent = true;\n alerts.push({ key: 'purchase-order.send-po-success', params: { url: hook.value }});\n if(!$scope.purchaseOrder.submittedDate) {\n modalService.showConfirmation('purchase-order.would-like-to-update-submitted-date?', function() {\n $scope.purchaseOrder.submittedDate = new Date();\n $scope.savePurchaseOrder();\n });\n }\n } else {\n alerts.pushError({ key: 'purchase-order.send-po-error', params: { url: hook.value }});\n hook.sent = false;\n }\n }, function (data, status) {\n PurchaseOrder.defaultErrorHandler(data, status);\n alerts.pushError({ key: 'purchase-order.send-po-error', params: { url: hook.value }});\n $scope.sendingPO = false;\n hook.sent = true;\n });\n });\n };\n\n //Update shipping date\n $scope.updateShippingDate = function(shipDateUpdated) {\n if(shipDateUpdated && (!$scope.purchaseOrder.lineItems || !$scope.purchaseOrder.lineItems.length)) {\n return;\n }\n\n if (!$scope.purchaseOrder.lineItems || !$scope.purchaseOrder.lineItems.length) {\n modalService.showMessage('purchase-order.po-items-required-before-updating-est-arrival-date', 'common.message.wooops');\n return false;\n }\n\n modalService.showConfirmation('purchase-order.would-like-to-update-estimated-ship-dates?', function () {\n var ids = _($scope.purchaseOrder.lineItems).pluck('id');\n\n $modal.open({\n templateUrl: 'js/modules/purchase-order/est-ship-date/edit-est-ship-date-modal.tpl.html',\n controller: \"EditEstShipDateModalCtrl\",\n resolve: {\n lineItems: function (PurchaseOrder) {\n return PurchaseOrder.getTrxLineItemsFromPOItemIds(ids);\n },\n poShippingDate: function () {\n return $scope.purchaseOrder.shipDate;\n },\n settings: function () {\n return settings;\n }\n },\n backdrop: 'static',\n windowClass: '__large'\n });\n });\n };\n\n $scope.updateSequence = function () {\n if($scope.queriesInProgress === 0) {\n _($scope.purchaseOrder.lineItems).each(function (line, index) {\n line.sequenceNumber = index;\n });\n\n $scope.savePurchaseOrder();\n }\n };\n\n $scope.recalculateVat = function (item) {\n if ($scope.locale.usesVat && $scope.purchaseOrder.vendor) {\n var vatPercent = $scope.purchaseOrder.vendor.vatPercent ? parseFloat($scope.purchaseOrder.vendor.vatPercent) : 0;\n item.vat = item.cost * vatPercent / 100;\n }\n };\n\n $scope.queriesInProgress = 0;\n\n $scope.savePurchaseOrderItem = function (poItem, syncChangesToSO, callback) {\n $scope.queriesInProgress += 1;\n var purchaseOrderItem = new PurchaseOrderItem(_(poItem).omit('checked'));\n purchaseOrderItem.modifiedDate = Date.now();\n purchaseOrderItem.modifiedByUser = User.username;\n\n PurchaseOrder.updateLineItem(purchaseOrderItem, syncChangesToSO || false, function (savedPurchaseOrder) {\n angular.extend($scope.purchaseOrder, _(savedPurchaseOrder).omit('lineItems'));\n _($scope.purchaseOrder.lineItems).each(function (item, index) {\n _(item).extend(savedPurchaseOrder.lineItems[index]);\n\n _(item.lineItemAddOns).each(function (addOn, i) {\n if(savedPurchaseOrder.lineItems[index].lineItemAddOns && savedPurchaseOrder.lineItems[index].lineItemAddOns.length && savedPurchaseOrder.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedPurchaseOrder.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n\n if(callback) {\n callback();\n }\n\n }).finally(function () {\n // without timeout saving state will be \"jumpy\"\n $timeout(function () {\n $scope.queriesInProgress -= 1;\n }, 1000);\n });\n };\n\n $scope.savePurchaseOrder = _.debounce(function (typeChanged) {\n $scope.queriesInProgress += 1;\n $scope.purchaseOrder.modifiedDate = Date.now();\n $scope.purchaseOrder.modifiedByUser = User.username;\n\n var employee = _.findWhere($scope.employees, { id: $scope.purchaseOrder.employeeId });\n $scope.purchaseOrder.employeeName = employee ? employee.fullName : $translate.instant('appointment.filter-panels.unassigned');\n\n var localPurchaseOrder = _($scope.purchaseOrder).clone();\n\n // Omit the checked property from each lineItem\n localPurchaseOrder.lineItems = _(localPurchaseOrder.lineItems).map(function (poItem) {\n return _(poItem).omit('checked');\n });\n\n localPurchaseOrder.$saveOrUpdate(function (savedPurchaseOrder) {\n $scope.isNewPurchaseOrder = false;\n\n if(typeChanged === 'shipDate') {\n var setting = _(settings).findWhere({ type: 'PO_PROMPT_EST_ARRIVAL_DATE_WHEN_SHIP_DATE_CHANGES' });\n\n if(setting && setting.value === 'Y') {\n $scope.updateShippingDate(true);\n }\n }\n\n angular.extend($scope.purchaseOrder, _(savedPurchaseOrder).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'cancelDate',\n 'submittedDate',\n 'shipDate',\n 'confirmationNumber',\n 'notes',\n 'discountAmount',\n 'fee'\n ));\n\n _($scope.purchaseOrder.lineItems).each(function (poItem, index) {\n poItem.version = savedPurchaseOrder.lineItems[index].version;\n\n _(poItem.lineItemAddOns).each(function (addOn, i) {\n if(savedPurchaseOrder.lineItems[index].lineItemAddOns && savedPurchaseOrder.lineItems[index].lineItemAddOns.length && savedPurchaseOrder.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedPurchaseOrder.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n }).finally(function () {\n $scope.queriesInProgress -= 1;\n });\n }, 1000);\n\n $scope.completeOrder = function () {\n modalService.showConfirmation('purchase-order.would-like-to-complete?', function () {\n $scope.purchaseOrder.status = Enums.statuses.complete.value;\n\n _($scope.purchaseOrder.lineItems).each(function(lineItem) {\n lineItem.status = Enums.statuses.complete.value;\n });\n\n $scope.readonly = true;\n $scope.savePurchaseOrder();\n alerts.pushSuccess('alerts.purchase-order.successfully-completed');\n });\n };\n\n $scope.removeAddOnItem = function (addOnItem, purchaseOrderItem) {\n if($scope.readonly) {\n return;\n }\n\n modalService.showConfirmation('purchase-order.would-like-to-delete-add-on-item?', function () {\n for(var i = 0; i < $scope.purchaseOrder.lineItems.length; i++) {\n for(var j = 0; j < $scope.purchaseOrder.lineItems[i].lineItemAddOns.length; j++) {\n var addOn = $scope.purchaseOrder.lineItems[i].lineItemAddOns[j];\n\n if(addOn && addOn.id === addOnItem.id) {\n $scope.purchaseOrder.lineItems[i].lineItemAddOns = _($scope.purchaseOrder.lineItems[i].lineItemAddOns).without(addOnItem);\n break;\n }\n }\n }\n\n PurchaseOrder.deleteLineItemAddOn(addOnItem, User.id, function (purchaseOrder) {\n $scope.purchaseOrder.modifiedDate = Date.now();\n $scope.purchaseOrder.modifiedByUser = User.username;\n\n var savedPurchaseOrderItem = _(purchaseOrder.lineItems).findWhere({ id: purchaseOrderItem.id });\n // Update special field of current line item\n purchaseOrderItem.cost = savedPurchaseOrderItem.cost;\n purchaseOrderItem.extCost = savedPurchaseOrderItem.extCost;\n purchaseOrderItem.vat = savedPurchaseOrderItem.vat;\n\n angular.extend($scope.purchaseOrder, _(purchaseOrder).omit('lineItems'));\n _($scope.purchaseOrder.lineItems).each(function (item, index) {\n item.version = purchaseOrder.lineItems[index].version;\n\n _(item.lineItemAddOns).each(function (addOn, i) {\n if(purchaseOrder.lineItems[index].lineItemAddOns && purchaseOrder.lineItems[index].lineItemAddOns.length && purchaseOrder.lineItems[index].lineItemAddOns[i]) {\n addOn.version = purchaseOrder.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n alerts.pushSuccess('alerts.purchase-order.successfully-deleted-item');\n });\n });\n };\n\n $scope.removeLineItem = function (purchaseOrderItem) {\n modalService.showConfirmation('purchase-order.would-like-to-delete-item?', function () {\n $scope.purchaseOrder.lineItems = _($scope.purchaseOrder.lineItems).without(purchaseOrderItem);\n\n PurchaseOrder.deleteLineItem(purchaseOrderItem, User.id, function (purchaseOrder) {\n $scope.purchaseOrder.modifiedDate = Date.now();\n $scope.purchaseOrder.modifiedByUser = User.username;\n angular.extend($scope.purchaseOrder, _(purchaseOrder).omit('lineItems'));\n _($scope.purchaseOrder.lineItems).each(function (item, index) {\n item.version = purchaseOrder.lineItems[index].version;\n\n _(item.lineItemAddOns).each(function (addOn, i) {\n if(purchaseOrder.lineItems[index].lineItemAddOns && purchaseOrder.lineItems[index].lineItemAddOns.length && purchaseOrder.lineItems[index].lineItemAddOns[i]) {\n addOn.version = purchaseOrder.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n alerts.pushSuccess('alerts.purchase-order.successfully-deleted-item');\n });\n });\n };\n\n $scope.removeLineItems = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('voucher.message.select-at-least-one-receiving', 'common.message.wooops');\n return false;\n }\n\n var selectedLength = $scope.selectedResults.length;\n\n modalService.showConfirmation({\n key:'purchase-order.would-like-to-delete-items?',\n params: { number: selectedLength }\n }, function () {\n PurchaseOrder.deleteLineItems($scope.purchaseOrder.id, User.id, _($scope.selectedResults).pluck('id'), function (purchaseOrder) {\n $scope.toggleSelectAll(true);\n $scope.purchaseOrder.modifiedDate = Date.now();\n $scope.purchaseOrder.modifiedByUser = User.username;\n angular.extend($scope.purchaseOrder, purchaseOrder);\n if($scope.purchaseOrder.lineItems && $scope.purchaseOrder.lineItems.length) {\n _($scope.purchaseOrder.lineItems).each(function (item, index) {\n item.version = purchaseOrder.lineItems[index].version;\n\n _(item.lineItemAddOns).each(function (addOn, i) {\n if(purchaseOrder.lineItems[index].lineItemAddOns && purchaseOrder.lineItems[index].lineItemAddOns.length && purchaseOrder.lineItems[index].lineItemAddOns[i]) {\n addOn.version = purchaseOrder.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n }\n\n alerts.pushSuccess({ key:'alerts.purchase-order.successfully-deleted-items', params: { number: selectedLength }});\n });\n });\n };\n\n $scope.print = function () {\n PurchaseOrder.print($scope.purchaseOrder.id);\n };\n\n $scope.voidOrder = function () {\n modalService.showConfirmation('purchase-order.would-like-to-void?',\n function () {\n PurchaseOrder.void($scope.purchaseOrder.id, User.id, function (purchaseOrder) {\n $scope.readonly = true;\n $scope.purchaseOrder.modifiedByUser = purchaseOrder.modifiedByUser;\n $scope.purchaseOrder.modifiedDate = purchaseOrder.modifiedDate;\n $scope.purchaseOrder.status = purchaseOrder.status;\n alerts.pushSuccess('alerts.purchase-order.successfully-voided');\n });\n });\n };\n\n $scope.deleteOrder = function () {\n modalService.showConfirmation('purchase-order.would-like-to-delete-along-with-vouchers?', function () {\n $scope.purchaseOrder.$remove().then(function () {\n ignoreRedirectWarning = true;\n alerts.pushSuccess('alerts.purchase-order.has-been-deleted-successfully');\n $state.go('purchase-orders');\n });\n });\n };\n\n $scope.showSendEmailModalVendor = function () {\n Vendor.getById($scope.purchaseOrder.vendorId, function (vendor) {\n var modal = EmailService.showSendModal(vendor, null, Email.VENDOR_TYPE, null, $scope.purchaseOrder);\n if(modal) {\n modal.result.then(function(email) {\n if(!email) {\n return;\n }\n\n if(!$scope.purchaseOrder.submittedDate) {\n modalService.showConfirmation('purchase-order.would-like-to-update-submitted-date?', function() {\n $scope.purchaseOrder.submittedDate = new Date();\n $scope.savePurchaseOrder();\n });\n }\n });\n }\n });\n };\n\n $scope.receiveItems = function () {\n var selectedItems = ($scope.purchaseOrder.lineItems.length === 1 && [$scope.purchaseOrder.lineItems[0]]) || _($scope.purchaseOrder.lineItems).where({ checked: true });\n\n if (!$scope.purchaseOrder.vendorId) {\n return modalService.showMessage('purchase-order.assign-vendor-before');\n }\n\n if (selectedItems.length) {\n // Omit the checked property from each lineItem\n selectedItems = _(selectedItems).map(function (poItem) {\n return _(poItem).omit('checked');\n });\n\n ReceivingVoucher.createForPOItems($scope.purchaseOrder.vendorId, User.id, selectedItems, function (receivingVoucher) {\n alerts.push({\n key: 'alerts.purchase-order.successfully-created-voucher',\n params: { number: receivingVoucher.receivingVoucherNumber }\n });\n $state.go('receiving-voucher.edit', { id: receivingVoucher.id });\n });\n } else {\n modalService.showMessage('purchase-order.select-one-line-item');\n }\n };\n\n $scope.selectAll = false;\n $scope.toggleSelectAll = function (removeAll) {\n if(removeAll) {\n $scope.selectAll = false;\n } else {\n $scope.selectAll = !$scope.selectAll;\n }\n\n var checkedPOItems = [];\n _($scope.purchaseOrder.lineItems).each(function (poItem) {\n if (poItem.quantity > poItem.quantityReceived) {\n poItem.checked = $scope.selectAll;\n checkedPOItems.push(poItem);\n }\n });\n\n if ($scope.selectAll) {\n $scope.selectedResults = _.union($scope.selectedResults, checkedPOItems);\n } else {\n $scope.selectedResults = removeAll ? [] : _($scope.selectedResults).difference(checkedPOItems);\n }\n };\n\n $scope.validateAddOnItemValue = function(type, addOn, oldValue) {\n if(oldValue === addOn[type]) {\n return false;\n }\n\n if(type === 'cost') {\n if(addOn[type] < 0) {\n modalService.showMessage('alerts.add-ons.invalid-cost');\n return false;\n } else if(!addOn[type]) {\n addOn[type] = 0;\n }\n } else if(type === 'description') {\n if(addOn[type] === '') {\n modalService.showMessage('alerts.add-ons.invalid-description');\n return false;\n }\n }\n\n return true;\n };\n\n $scope.collapseRow = function(event, poItem) {\n if(event) {\n event.preventDefault();\n }\n\n poItem.collapsed = !poItem.collapsed;\n };\n\n $scope.syncSelectedResults = function (obj) {\n if(obj.quantity <= obj.quantityReceived) {\n return;\n }\n\n var checkedItWasSelected = _($scope.selectedResults).findWhere({ id: obj.id });\n if (checkedItWasSelected) {\n $scope.selectedResults = _($scope.selectedResults).without(checkedItWasSelected);\n } else {\n $scope.selectedResults.push(obj);\n }\n };\n\n $scope.gotoContact = function (poItem) {\n $state.go('contact.personal_info', { id: poItem.contactId });\n };\n\n $scope.showSendEmailModalContact = function (poItem) {\n Contact.getById(poItem.contactId, function (contact) {\n EmailService.showSendModal(contact, null, Email.CONTACT_TYPE, null, $scope.purchaseOrder);\n });\n };\n\n $scope.openWeddingRegistryModal = ContactModal.openWeddingRegistry;\n\n $scope.showSizeModal = function(poItem, sizeGroup) {\n itemSizesModal.view(poItem, sizeGroup, { id: poItem.contactId }, false, function(size) {\n if(size) {\n $scope.applySizeChange(poItem, poItem.itemSize, size);\n poItem.itemSize = size;\n\n if(poItem.transactionId) {\n modalService.showStaticConfirmation('purchase-order.would-like-to-change-associated-so?', function () {\n $scope.savePurchaseOrderItem(poItem, true);\n }, function() {\n $scope.savePurchaseOrderItem(poItem, false);\n });\n } else {\n $scope.savePurchaseOrderItem(poItem, false);\n }\n }\n });\n };\n\n /**\n * Helpers\n */\n\n /**\n * Convert Item to PurchaseOrderItem\n * @param {resource|object} item\n * @returns {object} purchaseOrderItem\n */\n function itemToPurchaseOrderItem(item) {\n var cost = item.orderCost;\n var lineItemAddOns = ItemHelper.buildLineItemAddOnsPO(item.itemAddOns, item, null, { default: true });\n\n return {\n // converting\n inventoryItemId: item.id,\n cost: cost,\n vat: item.vat,\n itemNumber: item.itemNumber,\n itemVendorItemName: item.vendorItemName,\n itemColor: item.color,\n itemColor2: item.color2,\n itemSize: item.size,\n itemDepartmentId: item.departmentId,\n lineItemAddOns: lineItemAddOns,\n\n // defaults\n quantity: 1,\n extCost: cost,\n purchaseOrderId: $scope.purchaseOrder.id,\n sequenceNumber: $scope.purchaseOrder.lineItems.length,\n status: Enums.statuses.pending.value,\n createdByUser: User.username,\n createdDate: new Date().valueOf()\n };\n }\n\n function getAvailableAddItemsOnsAndOpenModal(purchaseOrderItem, item, options) {\n if (options.fetchItemAddOns) {\n var addOnRequest = {\n leadTimes: false,\n itemAddOns: true,\n purchaseOrder: $scope.purchaseOrder,\n purchaseOrderItem: purchaseOrderItem,\n item: item\n };\n\n PurchaseOrderItem.getAvailableAddOns(addOnRequest, function (addOnsResult) {\n //if we asked to load item add-ons and we have some, show the modal\n if(addOnsResult.itemAddOns === null) {\n addOnsResult.itemAddOns = [];\n }\n\n if(!options.showModalOnlyIfAddOnsAvailable || (options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnsResult.itemAddOns.length > 0)) {\n openAddOnModal(purchaseOrderItem, addOnsResult, options);\n }\n });\n }\n }\n\n function closePopover(event) {\n if (!angular.element(event.target).parents('.popover').length && $popover) {\n $popover.popover('destroy');\n }\n }\n\n function getPOWebHooks() {\n CompanySetting.readyByTypeArray(CompanySetting.PO_SEND_PO_WEB_HOOKS, function(hooks) {\n $scope.hooks = hooks;\n }, function (data, status) {\n console.error(data, status);\n });\n }\n\n getPOWebHooks();\n\n /**\n * Handlers\n */\n\n $scope.$on('$stateChangeStart', function (event, toState, toParams) {\n if (ignoreRedirectWarning || $state.readonly) {\n return;\n }\n\n if ($scope.isNewPurchaseOrder) {\n event.preventDefault();\n\n modalService.showConfirmation('purchase-order.would-like-to-discard?', function () {\n $scope.purchaseOrder.$remove(function () {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, { location: 'replace' });\n });\n });\n }\n });\n\n /**\n * Watchers\n */\n\n var ignoreFirstTime = true;\n\n //vendorId changes are not triggered by the save-trigger, so we watch...\n $scope.$watch('purchaseOrder.vendorId', function () {\n if (!ignoreFirstTime) {\n\n if (!$scope.purchaseOrder.vendorId) {\n $scope.purchaseOrder.vendorName = \"\";\n }\n\n $scope.savePurchaseOrder();\n }\n\n ignoreFirstTime = false;\n });\n\n $scope.$watch('purchaseOrder.lineItems', function (poItems) {\n $scope.itemColorOptions = {};\n $scope.itemColor2Options = {};\n $scope.itemSizeOptions = {};\n\n poItems.forEach(function (poItem) {\n // color, color2\n $scope.itemColorOptions[poItem.id] = poItem.colorString ? poItem.colorString.replace(/ /g, '').split(',') : [];\n $scope.itemColor2Options[poItem.id] = poItem.color2String ? poItem.color2String.replace(/ /g, '').split(',') : [];\n\n // size\n $scope.itemSizeOptions[poItem.id] = poItem.sizeGroup && poItem.sizeGroup.sizes.map(function (size) {\n return size.size;\n });\n });\n });\n\n /**\n * Init\n */\n\n // get a PO item attached to passed DOM element by tracking parent tr element\n function getPoItem(element) {\n var poIndex = parseInt(angular.element(element).parents('tr').attr('data-index'), 10);\n return $scope.purchaseOrder.lineItems[poIndex];\n }\n\n function getPoItemAddOn(element) {\n var parentIndex = parseInt(angular.element(element).parents('tr').attr('data-parent-index'), 10);\n var childIndex = parseInt(angular.element(element).parents('tr').attr('data-child-index'), 10);\n return $scope.purchaseOrder.lineItems[parentIndex].lineItemAddOns[childIndex];\n }\n\n var $popover;\n var initialNote;\n var initialAddOnNote;\n var poItemNoteScope;\n\n angular.element('.purchaseOrder_items').on('click', '.purchaseOrder_item-notes', function (event) {\n event.stopPropagation();\n\n if ($popover) {\n $popover.popover('destroy');\n }\n\n if (poItemNoteScope) {\n poItemNoteScope.$destroy();\n }\n\n var poItem = getPoItem(this);\n\n poItemNoteScope = $rootScope.$new();\n poItemNoteScope.poItem = poItem;\n poItemNoteScope.readonly = $scope.readonly;\n\n var template = '
';\n\n $popover = angular.element(this).popover({\n content: $compile(template)(poItemNoteScope),\n placement: 'right',\n trigger: 'manual',\n html: true\n }).popover('show');\n\n poItemNoteScope.$digest();\n\n initialNote = poItem.note;\n\n angular.element($window).on('click', closePopover);\n });\n\n // When popover is closed\n angular.element('.purchaseOrder_items').on('hidden.bs.popover', '.purchaseOrder_item-notes', function () {\n var poItem = getPoItem(this);\n\n if (initialNote !== poItem.note) {\n // save purchaseOrderItem\n $scope.savePurchaseOrderItem(poItem);\n }\n\n // disable close popover handler\n angular.element($window).off('click', closePopover);\n });\n\n angular.element('.purchaseOrder_items').on('click', '.purchaseOrder_item-add-on-notes', function (event) {\n event.stopPropagation();\n\n if ($popover) {\n $popover.popover('destroy');\n }\n\n if (poItemNoteScope) {\n poItemNoteScope.$destroy();\n }\n\n var addOn = getPoItemAddOn(this);\n\n poItemNoteScope = $rootScope.$new();\n poItemNoteScope.addOn = addOn;\n poItemNoteScope.readonly = $scope.readonly;\n poItemNoteScope.editNotes = $scope.itemAddOnsPerm.editNotes;\n\n var template = '
';\n\n $popover = angular.element(this).popover({\n content: $compile(template)(poItemNoteScope),\n placement: 'right',\n trigger: 'manual',\n html: true\n }).popover('show');\n\n poItemNoteScope.$digest();\n\n initialAddOnNote = addOn.notes;\n\n angular.element($window).on('click', closePopover);\n });\n\n // When popover is closed\n angular.element('.purchaseOrder_items').on('hidden.bs.popover', '.purchaseOrder_item-add-on-notes', function () {\n var addOnItem = getPoItemAddOn(this);\n\n if(addOnItem && addOnItem.purchaseOrderItemId) {\n var poItem = _($scope.purchaseOrder.lineItems).findWhere({ id: addOnItem.purchaseOrderItemId });\n\n if (initialAddOnNote !== addOnItem.note && poItem && poItem.id) {\n $scope.savePurchaseOrderItem(poItem);\n }\n }\n\n // disable close popover handler\n angular.element($window).off('click', closePopover);\n });\n\n // Save purchaseOrderItem on any input changing\n angular.element('.purchaseOrder_items').on('change', 'input:not(.not-save-trigger), select:not(.not-save-trigger)', _.debounce(function (event) {\n var poItem = getPoItem(this);\n\n if (!poItem) {\n return;\n }\n\n // Apply upsize charge when size is updated and there is an upsize cost\n if (event.target.getAttribute('ng-model') === 'poItem.itemSize') {\n var oldSize = event.removed ? event.removed.text : \"\";\n var newSize = event.added ? event.added.text : \"\";\n $scope.applySizeChange(poItem, oldSize, newSize);\n }\n\n //if color, color2 or size is changed and the item is associated with an SO, ask\n //if the user wants to change the SO too.\n if ((event.target.getAttribute('ng-model') === 'poItem.itemColor'\n || event.target.getAttribute('ng-model') === 'poItem.itemColor2'\n || event.target.getAttribute('ng-model') === 'poItem.itemSize')\n && poItem.transactionId) {\n\n modalService.showStaticConfirmation('purchase-order.would-like-to-change-associated-so?', function () {\n $scope.savePurchaseOrderItem(poItem, true);\n }, function() {\n $scope.savePurchaseOrderItem(poItem, false);\n });\n } else {\n $scope.savePurchaseOrderItem(poItem, false);\n }\n\n }, 1000));\n\n // Save purchaseOrder on any input changing\n angular.element(document).off('change', '.save-trigger').on('change', '.save-trigger', function () {\n $scope.savePurchaseOrder();\n });\n\n\n $scope.applySizeChange = function (poItem, oldSize, newSize) {\n //check to see if there was an upcharge on the old size, if so, the upcharge will be removed before adding another one.\n //this helps fix the issue of having an inventory item that has the upcharge already added\n //into the price.\n\n //if there is no size group then there cannot be any upcharges, get outta here.\n if (!poItem.sizeGroup) {\n return;\n }\n\n var sizeUpchargeAppliedToOldSize = $scope.hasSizeUpcharge(poItem.sizeGroup.sizes, oldSize);\n\n //only change pricing if the old size is not the same as the new size\n if (oldSize != newSize) {\n if (!sizeUpchargeAppliedToOldSize) {\n //look up the upcharge and apply\n $scope.applySizeUpcharge(poItem, poItem.sizeGroup.sizes, newSize);\n } else {\n //if the upcharge was applied to the old size and then they changed it to something else\n //then we need to remove the old size upcharge and apply the other one;\n $scope.removeSizeUpcharge(poItem, poItem.sizeGroup.sizes, oldSize);\n $scope.applySizeUpcharge(poItem, poItem.sizeGroup.sizes, newSize);\n }\n }\n };\n\n $scope.hasSizeUpcharge = function (availableSizes, itemSize) {\n if (availableSizes && availableSizes.length > 0) {\n for (var i = 0; i < availableSizes.length; i++) {\n if (availableSizes[i].size === itemSize) {\n if (availableSizes[i].upchargeCost) {\n return true;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n return false;\n };\n\n $scope.applySizeUpcharge = function (poItem, availableSizes, itemSize) {\n if (availableSizes && availableSizes.length) {\n for (var i = 0; i < availableSizes.length; i++) {\n if (availableSizes[i].size === itemSize) {\n if (availableSizes[i].upchargeCost) {\n\n //detect which type of upcharge to apply\n if(availableSizes[i].upchargeCostType === 0) {\n poItem.cost = poItem.cost + availableSizes[i].upchargeCost;\n } else if(availableSizes[i].upchargeCostType === 1) {\n var upchargePercent = availableSizes[i].upchargeCost/100;\n poItem.cost = poItem.cost + (upchargePercent * poItem.cost);\n poItem.cost = Number(poItem.cost.toFixed(2));\n } else {\n continue;\n }\n\n poItem.sizeUpchargeApplied = true;\n break;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n };\n\n $scope.removeSizeUpcharge = function (poItem, availableSizes, itemSize) {\n if (availableSizes && availableSizes.length) {\n for (var i = 0; i < availableSizes.length; i++) {\n if (availableSizes[i].size === itemSize) {\n if (availableSizes[i].upchargeCost) {\n\n //detect which type of upcharge to apply\n if(availableSizes[i].upchargeCostType === 0) {\n poItem.cost = poItem.cost - availableSizes[i].upchargeCost;\n } else if(availableSizes[i].upchargeCostType === 1) {\n var upchargePercent = availableSizes[i].upchargeCost/100;\n poItem.cost = poItem.cost / (1 + upchargePercent);\n poItem.cost = Number(poItem.cost.toFixed(2));\n } else {\n continue;\n }\n\n poItem.sizeUpchargeApplied = false;\n break;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n };\n\n $scope.triggerSmartFlow = function() {\n if($scope.wasConfirmed) {\n $scope.wasConfirmed = false;\n\n var purchaseOrder = {};\n angular.extend(purchaseOrder, $scope.purchaseOrder);\n _(purchaseOrder.lineItems).each(function (lineItem) {\n delete lineItem.checked;\n });\n\n eventsBus.publish(\"purchase-order:confirmed\", [purchaseOrder]);\n }\n };\n\n $timeout(function () {\n $scope.$broadcast('show-errors-check-validity');\n });\n\n $scope.$watch('purchaseOrder.confirmationNumber', function (newValue, oldValue) {\n if(!oldValue && newValue) {\n //it was blank before and we have a value now, so we are confirming\n $scope.wasConfirmed = true;\n }\n });\n\n });\n\n});\n\n","\ndefine('text!modules/item/item-search-typeahead-grid-directive/items-search-typeahead-grid.tpl.html',[],function () { return '
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n {{ item.name }} \\n {{ item.vendorItemName }} \\n {{ item.name }} \\n {{ item.itemNumber }} \\n \\n \\n {{ item.color }} \\n \\n {{ item.size }} \\n \\n = item.regularPrice\">{{ item.regularPrice | currency }} \\n 0 && item.salePrice < item.regularPrice\">\\n {{ item.regularPrice | currency }} \\n {{ item.salePrice | currency }} \\n \\n \\n {{ item.vendorCode }} \\n {{ item.departmentCode }} \\n \\n \\n \\n
\\n\\n
\\n
\\n';});\n\n","define('modules/quote/details/quote-details-ctrl',['../module', 'angular', 'underscore', 'moment'], function (module, angular, _, moment) {\n 'use strict';\n\n module.controller('QuoteDetailsCtrl', function ($compile,\n $modal,\n $scope,\n $rootScope,\n $state,\n $stateParams,\n $timeout,\n $window,\n $locale,\n $location,\n $translate,\n $filter,\n AppLicense,\n alerts,\n eventsBus,\n Contact,\n EventMember,\n EventItem,\n EmailService,\n Sms,\n employees,\n Enums,\n Item,\n ItemModal,\n modalService,\n ResourcesHelper,\n quoteSettings,\n posSettings,\n clientPortalSettings,\n taxCodes,\n Quote,\n quote,\n ItemAddOn,\n ItemHelper,\n itemSizesModal,\n QuoteAddress,\n TaxJarSettings,\n QuoteItem,\n User,\n QuoteSignature,\n TransactionPaymentsModalService,\n FULLSTEAM,\n FullSteam,\n SmartFlow,\n security,\n Permissions,\n quoteAgreements,\n quoteTerminalContracts) {\n\n /*\n\n Init\n */\n var license = AppLicense.get();\n $scope.security = security;\n $scope.quote = quote;\n $scope.employees = ResourcesHelper.adaptList('employees', employees);\n $scope.taxCodes = _.filter(taxCodes, function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n $scope.locale = $locale;\n $scope.isNF525 = $locale.isNF525;\n $scope.isNF525AndNotTrialing = $locale.isNF525 === true && license.status !== 'T';\n $scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;\n $scope.isSmartFlowsEnabled = security.hasPlanPermission('smartFlows');\n $scope.hasAllFullSteamSettings = TransactionPaymentsModalService.hasAllFullSteamSettings(posSettings);\n $scope.hasAllChargeItProSettings = TransactionPaymentsModalService.hasAllChargeItProSettings(posSettings);\n $scope.hasAllStripeSettings = TransactionPaymentsModalService.hasAllStripeSettings(posSettings);\n $scope.convertedStatusValue = Enums.statuses.converted.value;\n $scope.pendingStatusValue = Enums.statuses.pending.value;\n $scope.voidedStatusValue = Enums.statuses.voided.value;\n\n var quoteTypeId = '5';\n\n $scope.readonly = $scope.quote.status === $scope.convertedStatusValue || $scope.quote.status === $scope.voidedStatusValue || (!security.hasPermission(\"EDIT_QUOTES\"));\n\n $scope.quoteAgreements = quoteAgreements;\n $scope.quoteTerminalContracts = quoteTerminalContracts;\n\n $scope.defaultQuoteAgreement = getDefaultQuoteAgreement(quoteSettings);\n\n $scope.perms = {\n convert: security.hasPermission(Permissions.CONVERT_QUOTES)\n };\n\n $scope.itemAddOnsPerm = {\n add: security.hasPermission(Permissions.ADD_QUOTE_ITEM_ADD_ON),\n set: security.hasPermission(Permissions.SELECT_QUOTE_ITEM_ADD_ON),\n editNotes: security.hasPermission(Permissions.EDIT_QUOTE_ITEM_ADD_ON_NOTE),\n editDesc: security.hasPermission(Permissions.EDIT_QUOTE_ITEM_ADD_ON_DESC),\n editPrice: security.hasPermission(Permissions.EDIT_QUOTE_ITEM_ADD_ON_PRICE),\n remove: security.hasPermission(Permissions.REMOVE_QUOTE_ITEM_ADD_ON),\n addOnRequired: security.hasPermission(Permissions.QUOTE_ITEM_ADD_ON_REQUIRED)\n };\n\n var setting = _(quoteSettings).findWhere({ type: 'QUOTE_ALT_PRINT' });\n $scope.doShowDescription = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_MANUAL_TAX_CALC' });\n $scope.enableManualTaxCalculation = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_COLOR_SIZE_VALID' });\n $scope.colorOrSizeAllowCustomVal = setting && setting.value === 'N';\n\n setting = _(posSettings).findWhere({ type: 'POS_COLOR_SIZE_REQUIRED' });\n $scope.colorAndSizeRequired = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_COLOR_SIZE_BLANK' });\n var colorAndSizeBlank = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'USE_DIG_SIGN' });\n $scope.digitalSignatureEnabled = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_TRX_DISCOUNT' });\n $scope.trxDiscountEnabled = setting && setting.value === 'Y';\n\n var licenseHasDigitalSignatureFeature = license.plan['digitalSignature'];\n if(!licenseHasDigitalSignatureFeature) {\n $scope.digitalSignatureEnabled = false;\n }\n\n // determine visibility of client portal feature\n var portalFeature = _(clientPortalSettings).findWhere({ type: 'CP_FEATURE_DIGITAL_SIGNATURES' });\n var portalFeatureEnabled = portalFeature ? portalFeature.value === 'Y' : true;\n var userHasPermission = security.hasPermission(\"REQUEST_SIGNATURES\");\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n\n var onlinePaymentsEnabled = $scope.hasAllFullSteamSettings || $scope.hasAllChargeItProSettings || $scope.hasAllStripeSettings;\n $scope.requestSignatureAvailable = (onlinePaymentsEnabled && userHasPermission) || (licenseHasPortalAccess && portalFeatureEnabled && userHasPermission);\n\n var onAddLineItem = function (item, searchField, ignoreAddOns, addOnItemCallback) {\n var quoteItem = itemToQuoteItem(item);\n quoteItem.checkForUpCharge = !!(colorAndSizeBlank);\n\n $scope.savingInProgress = true;\n Quote.addLineItem(quoteItem, function (quote) {\n $scope.savingInProgress = false;\n $scope.isNewQuote = false;\n angular.extend($scope.quote, _(quote).omit('quoteLineItems'));\n _($scope.quote.quoteLineItems).each(function (quoteItem, index) {\n quoteItem.version = quote.quoteLineItems[index].version;\n\n _(quoteItem.quoteItemAddOns).each(function (addOn, i) {\n if(quote.quoteLineItems[index].quoteItemAddOns && quote.quoteLineItems[index].quoteItemAddOns.length && quote.quoteLineItems[index].quoteItemAddOns[i]) {\n addOn.version = quote.quoteLineItems[index].quoteItemAddOns[i].version;\n }\n });\n });\n\n if(quote.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n if (!$scope.quote.smartFlowsTriggeredDateTime && security.hasPlanPermission('smartFlows')) {\n var req = { triggerId: Enums.smartFlowTriggers.quote_created.id };\n req.loggedInEmployee = User;\n req.quote = $scope.quote;\n\n SmartFlow.getTriggeredFlowsForDisplay(req, function (flows) {\n if (flows && flows.length > 0) {\n $scope.quote.smartFlowsTriggeredDateTime = new Date().valueOf();\n }\n });\n }\n\n var addedItem = _(quote.quoteLineItems).last();\n $scope.quote.quoteLineItems.push(addedItem);\n $scope.quote.modifiedDate = Date.now();\n\n if(!ignoreAddOns) {\n $scope.getAvailableAddOnsAndOpenModal(addedItem, item, { showModalOnlyIfAddOnsAvailable: true, fetchItemAddOns: $scope.itemAddOnsPerm.set });\n }\n\n validateQuoteItems();\n\n if(addOnItemCallback) {\n addOnItemCallback();\n }\n });\n };\n\n $scope.itemSelectorOptions = {\n addNewItemSaveButtonLabel: $translate.instant('transaction.save-and-add-to') + $translate.instant('quote.quote'),\n onAddNewItem: getAllItemValues,\n onChooseItem: getAllItemValues\n };\n\n function getAllItemValues(item, field) {\n ItemAddOn.listByItemId(item.id, function(itemAddOns) {\n item.itemAddOns = itemAddOns;\n onAddLineItem(item, field);\n });\n }\n\n var ignoreRedirectWarning = false;\n\n /**\n * Methods\n */\n\n $scope.queriesInProgress = 0;\n\n $scope.saveQuoteItem = function (item, callback) {\n $scope.savingInProgress = true;\n $scope.queriesInProgress += 1;\n item.modifiedDate = Date.now();\n item.modifiedByUser = User.username;\n\n Quote.updateLineItem(item, function (savedQuote) {\n $scope.savingInProgress = false;\n\n angular.extend($scope.quote, _(savedQuote).omit(\n 'quoteLineItems',\n 'employeeId',\n 'shippingAndHandling',\n 'fee',\n 'notes'\n ));\n\n if(savedQuote.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n var savedQuoteItem = _(savedQuote.quoteLineItems).findWhere({ id: item.id });\n\n // Update special field of current line item\n item.adjustedPrice = savedQuoteItem.adjustedPrice;\n item.discountPercent = savedQuoteItem.discountPercent;\n\n item.shippingTax = savedQuoteItem.shippingTax;\n item.taxAmount = savedQuoteItem.taxAmount;\n\n // Update version of all line items\n _($scope.quote.quoteLineItems).each(function (quoteItem, index) {\n quoteItem.version = savedQuote.quoteLineItems[index].version;\n quoteItem.modifiedDate = savedQuote.quoteLineItems[index].modifiedDate;\n quoteItem.modifiedByUser = savedQuote.quoteLineItems[index].modifiedByUser;\n quoteItem.quoteItemAddOns = savedQuote.quoteLineItems[index].quoteItemAddOns;\n\n _(quoteItem.quoteItemAddOns).each(function (addOn, i) {\n if(savedQuote.quoteLineItems[index].quoteItemAddOns && savedQuote.quoteLineItems[index].quoteItemAddOns.length && savedQuote.quoteLineItems[index].quoteItemAddOns[i]) {\n addOn.version = savedQuote.quoteLineItems[index].quoteItemAddOns[i].version;\n addOn.modifiedDate = savedQuote.quoteLineItems[index].quoteItemAddOns[i].modifiedDate;\n addOn.modifiedByUser = savedQuote.quoteLineItems[index].quoteItemAddOns[i].modifiedByUser;\n }\n });\n });\n }, function (data, status) {\n Quote.defaultErrorHandler(data, status);\n $scope.savingInProgress = false;\n }).finally(function () {\n // without timeout saving state will be \"jumpy\"\n $timeout(function () {\n $scope.queriesInProgress -= 1;\n }, 1000);\n\n if(callback) {\n callback();\n }\n });\n };\n\n $scope.saveQuote = $scope.quote.save = _.debounce(function (callback) {\n $scope.queriesInProgress += 1;\n $scope.savingInProgress = true;\n $scope.quote.modifiedDate = Date.now();\n $scope.quote.modifiedByUser = User.username;\n\n //make sure the employeeName is set properly\n if($scope.quote.employeeId >= 0 && $scope.employees) {\n var employee = _($scope.employees).findWhere({ id: $scope.quote.employeeId });\n if(employee) {\n $scope.quote.employeeName = employee.fullName;\n }\n }\n\n Quote.put(_($scope.quote).omit('save'), function (savedQuote) {\n $scope.savingInProgress = false;\n $scope.isNewQuote = false;\n\n angular.extend($scope.quote, _(savedQuote).omit(\n 'quoteLineItems',\n 'employeeId',\n 'shippingAndHandling',\n 'fee',\n 'notes'\n ));\n _($scope.quote.quoteLineItems).each(function (item, index) {\n item.quantitySold = savedQuote.quoteLineItems[index].quantitySold;\n item.version = savedQuote.quoteLineItems[index].version;\n\n _(item.quoteItemAddOns).each(function (addOn, i) {\n if(savedQuote.quoteLineItems[index].quoteItemAddOns && savedQuote.quoteLineItems[index].quoteItemAddOns.length && savedQuote.quoteLineItems[index].quoteItemAddOns[i]) {\n addOn.version = savedQuote.quoteLineItems[index].quoteItemAddOns[i].version;\n }\n });\n });\n\n $scope.readonly = $scope.quote.status === $scope.convertedStatusValue || $scope.quote.status === $scope.voidedStatusValue;\n\n if (callback) {\n callback(savedQuote);\n }\n }, function (data, status) {\n Quote.defaultErrorHandler(data, status);\n $scope.savingInProgress = false;\n }).finally(function () {\n $scope.queriesInProgress -= 1;\n });\n }, 1000);\n\n $scope.loadSignatures = function () {\n setting = _(posSettings).findWhere({ type: 'SALES_AGREEMENT_REQ' });\n var isSignatureRequiredForThisType = setting && setting.value && setting.value.split(',').indexOf(quoteTypeId) !== -1;\n\n QuoteSignature.listAllForQuote(quote.id, function (signatures) {\n var activeAgreementSigned = false;\n //now mark each sales agreement as signed, if we have a signature for it\n _(signatures).each(function(signature) {\n if(signature.salesAgreementId && signature.quoteId) {\n var agreement = _($scope.quoteAgreements).findWhere({ id: signature.salesAgreementId });\n\n if(!agreement) {\n agreement = _($scope.quoteTerminalContracts).findWhere({ id: signature.salesAgreementId });\n }\n\n if(agreement) {\n agreement.signed = true;\n activeAgreementSigned = true;\n }\n }\n });\n\n $scope.markSignSalesAgreementGreen = activeAgreementSigned;\n $scope.markSignSalesAgreementRed = !$scope.markSignSalesAgreementGreen && isSignatureRequiredForThisType;\n });\n };\n\n $scope.signTerminalContract = function(customContract) {\n if(!$window.localStorage.fullsteamTerminalId) {\n openSetTerminalModal(customContract, null, false);\n } else {\n openTerminalContractModal(customContract, $scope.quote.id);\n }\n };\n\n function openTerminalContractModal(contract, quoteId, tempTerminal) {\n if((contract && contract.signed) || (tempTerminal && tempTerminal.terminalId) || isDeviceTypeValid($window.localStorage.fullsteamTerminalType, contract)) {\n $modal\n .open({\n templateUrl: 'js/modules/quote/details/modals/sign-terminal-contract.tpl.html',\n controller: 'SignTerminalQuoteContractCtrl',\n windowClass: '__protected __large',\n backdrop: 'static',\n resolve: {\n agreement: function () {\n return contract;\n },\n signatures: function (QuoteSignature) {\n return QuoteSignature.query({ filterObject: { quoteId: quote.id} });\n },\n tempTerminal: function () {\n return tempTerminal;\n }\n }\n })\n .result\n .then(function (savedSignature) {\n var agreement = $scope.defaultQuoteAgreement;\n if(contract) {\n agreement = contract;\n }\n\n if (savedSignature) {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n\n //mark the sales agreement as signed\n if(agreement) {\n agreement.signed = true;\n }\n } else {\n //if the signature was deleted, reset the state of the signature button\n //mark the sales agreement as not signed\n if(agreement) {\n agreement.signed = false;\n }\n\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = false;\n\n //if we don't have any signed sales agreements, turn the button red now\n setting = _(posSettings).findWhere({ type: 'SALES_AGREEMENT_REQ' });\n var isSignatureRequiredForThisType = setting && setting.value && setting.value.split(',').indexOf(quoteTypeId) !== -1;\n\n if(!$scope.defaultQuoteAgreement.signed) {\n var otherSignedSalesAgreement = _($scope.quoteAgreements).findWhere({ signed: true });\n\n if(!otherSignedSalesAgreement && $scope.quoteTerminalContracts) {\n otherSignedSalesAgreement = _($scope.quoteTerminalContracts).findWhere({ signed: true });\n }\n\n if(!otherSignedSalesAgreement) {\n $scope.markSignSalesAgreementRed = isSignatureRequiredForThisType;\n $scope.markSignSalesAgreementGreen = false;\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n }\n });\n }\n }\n\n function isDeviceTypeValid(device, agreement) {\n if(!device) {\n return true;\n }\n\n if(FULLSTEAM.DEVICES.MX915 !== device && FULLSTEAM.DEVICES.MX925 !== device && FULLSTEAM.DEVICES.L7000 !== device) {\n FullSteam.getTerminals(function (result) {\n var terminals = _((result && result.terminals)).filter(function (terminal) {\n return FULLSTEAM.DEVICES.MX915 === terminal.modelNumber || FULLSTEAM.DEVICES.MX925 === terminal.modelNumber || FULLSTEAM.DEVICES.L7000 === terminal.modelNumber;\n });\n\n if(terminals && terminals.length) {\n openSetTerminalModal(agreement, terminals, true);\n } else {\n modalService.showMessage('settings.custom-sales-agreement.devices-eligible');\n }\n\n }, function () {\n modalService.showMessage('settings.custom-sales-agreement.devices-eligible');\n });\n\n return false;\n }\n\n return true;\n }\n\n function openSetTerminalModal(agreement, terminals, selectingContractTerminal) {\n return $modal.open({\n templateUrl: 'js/modules/quote/details/modals/set-terminal-modal.tpl.html',\n controller: 'SetTerminalModalCtrl',\n resolve: {\n terminalResult: function () {\n if(terminals && terminals.length) {\n return { terminals: terminals };\n } else {\n return FullSteam.getTerminals();\n }\n },\n selectingContractTerminal: function () {\n return selectingContractTerminal;\n }\n }\n }).result.then(function(response) {\n if(response) {\n openTerminalContractModal(agreement, $scope.quote.id, response);\n } else {\n return modalService.showMessage('alerts.merchant-account.please-set-terminal-capture','settings.merchant-account.required-terminal');\n }\n });\n }\n\n $scope.signSalesAgreementModal = function (quoteAgreement) {\n if(!quoteAgreement && $scope.defaultQuoteAgreement) {\n quoteAgreement = $scope.defaultQuoteAgreement;\n }\n $modal\n .open({\n templateUrl: 'js/modules/quote/details/modals/sign-quote-agreement.tpl.html',\n controller: 'SignQuoteAgreementCtrl',\n windowClass: '__protected __large',\n backdrop: 'static',\n keyboard: false,\n resolve: {\n quoteSignatures: function(QuoteSignature) {\n return QuoteSignature.listAllForQuote(quote.id);\n },\n quoteSettings: function () {\n return quoteSettings;\n },\n awsPolicyAndSignature: function(Signature) {\n return Signature.getS3Policy();\n },\n customQuoteAgreements: function () {\n return $scope.quoteAgreements;\n },\n agreement: function () {\n return quoteAgreement;\n }\n }\n })\n .result\n .then(function (savedSignature) {\n var agreement = $scope.defaultQuoteAgreement;\n if(quoteAgreement) {\n agreement = quoteAgreement;\n }\n\n if (savedSignature) {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n\n //mark the sales agreement as signed\n if(agreement) {\n agreement.signed = true;\n }\n } else {\n //if the signature was deleted, reset the state of the signature button\n //mark the sales agreement as not signed\n if(agreement) {\n agreement.signed = false;\n }\n\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = false;\n\n setting = _(posSettings).findWhere({ type: 'SALES_AGREEMENT_REQ' });\n var isSignatureRequiredForThisType = setting && setting.value && setting.value.split(',').indexOf(quoteTypeId) !== -1;\n\n\n if(!$scope.defaultQuoteAgreement || !$scope.defaultQuoteAgreement.signed) {\n var otherSignedSalesAgreement = _($scope.quoteAgreements).findWhere({ signed: true });\n\n if(!otherSignedSalesAgreement && $scope.quoteTerminalContracts) {\n otherSignedSalesAgreement = _($scope.quoteTerminalContracts).findWhere({ signed: true });\n }\n\n if(!otherSignedSalesAgreement) {\n $scope.markSignSalesAgreementRed = isSignatureRequiredForThisType;\n $scope.markSignSalesAgreementGreen = false;\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n }\n });\n };\n\n $scope.requestSignature = function () {\n if (!$scope.quote.contactId) {\n modalService.showMessage('transaction.message.assign-contact-first');\n } else {\n $modal.open({\n templateUrl: 'js/modules/quote/details/modals/request-signature.tpl.html',\n controller: 'QuoteRequestSignatureCtrl',\n windowClass: '__small',\n resolve: {\n companyProfile: function (Company) {\n return Company.get();\n },\n posSettings: function (CompanySetting) {\n return CompanySetting.getPOSSettings();\n },\n quoteAgreements: function (CompanySetting) {\n return CompanySetting.getQuoteCustomAgreements();\n },\n smsTemplates: function (SMSTemplate) {\n return SMSTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n emailTemplates: function (EmailTemplate) {\n return EmailTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n twilioSettings: function (TwilioSettings) {\n return TwilioSettings.get();\n },\n contact: function (Contact) {\n return Contact.getById($scope.quote.contactId);\n },\n quote: function () {\n return $scope.quote;\n }\n }\n });\n }\n };\n\n $scope.removeLineItem = function (quoteItem) {\n if($scope.readonly) {\n return;\n }\n\n modalService.showConfirmation('purchase-order.would-like-to-delete-item?', function () {\n Quote.deleteLineItem(quoteItem, function (quote) {\n $scope.quote.quoteLineItems = _($scope.quote.quoteLineItems).without(quoteItem);\n $scope.quote.modifiedDate = Date.now();\n $scope.quote.modifiedByUser = User.username;\n angular.extend($scope.quote, _(quote).omit('quoteLineItems'));\n _($scope.quote.quoteLineItems).each(function (item, index) {\n item.version = quote.quoteLineItems[index].version;\n item.adjustedPrice = quote.quoteLineItems[index].adjustedPrice;\n\n _(item.quoteItemAddOns).each(function (addOn, i) {\n if(quote.quoteLineItems[index].quoteItemAddOns && quote.quoteLineItems[index].quoteItemAddOns.length && quote.quoteLineItems[index].quoteItemAddOns[i]) {\n addOn.version = quote.quoteLineItems[index].quoteItemAddOns[i].version;\n }\n });\n });\n alerts.pushSuccess('alerts.transaction.successfully-deleted-item');\n });\n });\n };\n\n $scope.removeAddOnItem = function (addOnItem, quoteItem) {\n if($scope.readonly) {\n return;\n }\n\n modalService.showConfirmation('purchase-order.would-like-to-delete-add-on-item?', function () {\n Quote.deleteLineItemAddOn(addOnItem, function (quote) {\n for(var i = 0; i < $scope.quote.quoteLineItems.length; i++) {\n for(var j = 0; j < $scope.quote.quoteLineItems[i].quoteItemAddOns.length; j++) {\n var addOn = $scope.quote.quoteLineItems[i].quoteItemAddOns[j];\n\n if(addOn && addOn.id === addOnItem.id) {\n $scope.quote.quoteLineItems[i].quoteItemAddOns = _($scope.quote.quoteLineItems[i].quoteItemAddOns).without(addOnItem);\n break;\n }\n }\n }\n\n $scope.quote.modifiedDate = Date.now();\n $scope.quote.modifiedByUser = User.username;\n\n var savedQuoteItem = _(quote.quoteLineItems).findWhere({ id: quoteItem.id });\n // Update special field of current line item\n quoteItem.adjustedPrice = savedQuoteItem.adjustedPrice;\n quoteItem.discountPercent = savedQuoteItem.discountPercent;\n quoteItem.shippingTax = savedQuoteItem.shippingTax;\n quoteItem.taxAmount = savedQuoteItem.taxAmount;\n quoteItem.price = savedQuoteItem.price;\n\n angular.extend($scope.quote, _(quote).omit('quoteLineItems'));\n _($scope.quote.quoteLineItems).each(function (item, index) {\n item.version = quote.quoteLineItems[index].version;\n item.modifiedDate = quote.quoteLineItems[index].modifiedDate;\n item.modifiedByUser = quote.quoteLineItems[index].modifiedByUser;\n\n _(item.quoteItemAddOns).each(function (addOn, i) {\n if(quote.quoteLineItems[index].quoteItemAddOns && quote.quoteLineItems[index].quoteItemAddOns.length && quote.quoteLineItems[index].quoteItemAddOns[i]) {\n addOn.version = quote.quoteLineItems[index].quoteItemAddOns[i].version;\n }\n });\n });\n alerts.pushSuccess('alerts.add-ons.successfully-deleted-item-add-on');\n });\n });\n };\n\n $scope.convertQuoteToTransaction = function(typeId) {\n var type = $scope.getTrxType(typeId);\n validateThenConvert(type.value, function () {\n modalService.showConfirmation({\n key: 'quote.messages.would-like-to-convert?',\n params: {\n type: $translate.instant(type.description)\n }}, function () {\n Quote.convertToTransaction($scope.quote, User.id, type.value, function (newlyCreatedTransaction) {\n ignoreRedirectWarning = true;\n $state.go(getTrxRoute(type.value), { id: newlyCreatedTransaction.id, promptLeadTimes: true });\n });\n });\n });\n };\n\n $scope.getAvailableAddOnsAndOpenModal = function(quoteItem, item, options, callback) {\n var addOnRequest = {\n leadTimes: false,\n itemAddOns: options.fetchItemAddOns,\n quote: $scope.quote,\n quoteItem: quoteItem,\n item: item\n };\n\n QuoteItem.getAvailableAddOns(addOnRequest, function(addOnsResult) {\n //if we asked to load lead times and we have some, show the modal\n if(addOnsResult.leadTimes === null) {\n addOnsResult.leadTimes = [];\n }\n\n if(addOnsResult.itemAddOns === null) {\n addOnsResult.itemAddOns = [];\n }\n\n var quoteItemAddOns = _(quoteItem.quoteItemAddOns).filter(function(addOn) {\n return addOn.addOnId !== 0 && addOn.addOnId !== null;\n });\n\n if(options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnRequest.itemAddOns && addOnsResult.itemAddOns.length > 0 && quoteItemAddOns.length !== addOnsResult.itemAddOns.length) {\n openAddOnModal(quoteItem, addOnsResult, { userSelected: quoteItem.quoteItemAddOns && quoteItem.quoteItemAddOns.length });\n }\n\n if(callback) {\n callback();\n }\n\n if(!options.showModalOnlyIfAddOnsAvailable || (options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnsResult.leadTimes.length > 0)) {\n openLeadTimesModal(quoteItem, addOnsResult, callback);\n } else {\n if (callback) {\n callback();\n }\n }\n });\n };\n\n $scope.shippingFieldChange = _.debounce(function(value) {\n if($rootScope.shippingModalOpen || value < 0 || value === null || value === undefined) {\n return;\n }\n\n var changeInShippingTax = false;\n var itemCount = 0;\n var shippingSum = 0;\n\n _($scope.quote.quoteLineItems).each(function(item) {\n if (item.shippingCost > 0) {\n changeInShippingTax = true;\n }\n\n itemCount++;\n shippingSum += item.shippingCost.valueOf();\n });\n\n if((value === 0 && changeInShippingTax) && (itemCount > 1 || itemCount === 1)) {\n updateShippingCostAndRecalculate();\n } else if(changeInShippingTax && value !== shippingSum && itemCount === 1) {\n updateShippingCostAndRecalculate();\n } else if(changeInShippingTax && value !== shippingSum && itemCount > 1) {\n modalService.showStaticConfirmationCustomButtons({\n message: 'modal-service.shipping-change.shipping-has-changed',\n btn_primary_text: 'modal-service.shipping-change.distribute-manually',\n btn_secondary_text: 'modal-service.shipping-change.distribute-evenly'\n },\n function() {\n $scope.openShipToCustomerModal();\n },\n function() {\n updateShippingCostAndRecalculate();\n });\n }\n }, 2500);\n\n $scope.openShipToCustomerModal = function () {\n if($rootScope.shippingModalOpen || $scope.savingInProgress) {\n return;\n }\n\n if(!$scope.quote.contactId) {\n return modalService.showMessage('transaction.ship-to-customer.please-assign-contact');\n }\n\n $rootScope.shippingModalOpen = true;\n\n $modal.open({\n templateUrl: 'js/modules/quote/details/modals/ship-to-customer-modal.tpl.html',\n controller: 'ShipToCustomerQuoteModalCtrl',\n resolve: {\n taxCodes: function (TaxCode) {\n return TaxCode.query({ filterObject: { status:Enums.statuses.active.value }});\n },\n quote: function () {\n return Quote.getById($scope.quote.id);\n },\n addresses: function () {\n return QuoteAddress.listAllForContact($scope.quote);\n },\n eventMembers: function () {\n if($scope.quote && $scope.quote.eventId) {\n return Quote.listEventMemberContactInformation($scope.quote.eventId, $scope.quote.eventContactId);\n } else {\n return null;\n }\n },\n settings: function () {\n return TaxJarSettings.get();\n }\n },\n windowClass: '__large',\n backdrop: 'static',\n keyboard: false\n }).result.then(function (response) {\n $rootScope.shippingModalOpen = false;\n\n if(response && response.quote) {\n\n var savedQuote = response.quote;\n\n if(savedQuote.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n angular.extend($scope.quote, _(savedQuote).omit(\n 'quoteLineItems',\n 'employeeId',\n 'fee',\n 'notes'\n ));\n\n updateLineItemsAfterUpdatingShippingInfo(savedQuote);\n }\n });\n };\n\n $scope.print = function (agreement) {\n Quote.print($scope.quote.id, agreement);\n };\n\n $scope.linkItemToWeddingRegistry = function (item) {\n if($scope.readonly) {\n return;\n }\n\n var eventId = $scope.quote.eventId;\n var contactId = $scope.quote.contactId;\n\n if (!eventId) {\n return modalService.showMessage('transaction.message.link-to-wedding-party');\n }\n\n EventMember.query({ filterObject: { eventId: eventId } }).then(function (members) {\n Contact.getById($scope.quote.eventContactId, function (eventContact) {\n var isNotMemberOfWedding = _(members)\n .chain()\n .pluck('contactId')\n .indexOf(contactId)\n .value() === -1;\n\n if (isNotMemberOfWedding) {\n var message = $translate.instant('transaction.message.would-like-add-to-wedding-party?', { contactName: $scope.quote.contactName, firstName: eventContact.firstName, lastName: eventContact.lastName });\n var contactName = $scope.quote.contactName;\n\n return $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/add-member-to-wedding.tpl.html',\n controller: function ($scope, Enums) {\n $scope.message = message;\n $scope.contactName = contactName;\n var translatedEventMemberTypes = {};\n\n // Translate event members' descriptions\n _(Enums.eventMemberTypes).each(function (value, key) {\n value.description = $translate.instant(value.description);\n translatedEventMemberTypes[key] = value;\n });\n\n $scope.eventMemberTypes = _(translatedEventMemberTypes).values();\n $scope.eventMemberType = translatedEventMemberTypes.bridesmaid;\n }\n })\n .result\n .then(function (eventMemberType) {\n // Add contact to event\n var contact = $scope.quote.contact;\n var newEventMember = new EventMember({\n bestPhoneNumber: contact.bestPhoneNumber,\n bestPhoneNumberType: contact.bestPhoneNumberType,\n contactId: contact.id,\n emailAddress: contact.emailAddress,\n eventId: eventId,\n firstName: contact.firstName,\n lastName: contact.lastName,\n typeId: eventMemberType.value,\n roleDescription: eventMemberType.description,\n createdByUser: User.username,\n modifiedByUser: User.username\n });\n\n newEventMember.$save(function (savedEventMember) {\n // Register item\n var eventItemResource = new EventItem({\n createdByUser: User.username,\n modifiedByUser: User.username,\n eventMemberId: savedEventMember.id,\n inventoryItemId: item.inventoryItemId,\n itemColor: item.itemColor,\n itemColor2: item.itemColor2,\n itemSize: item.itemSize,\n itemName: item.itemName\n });\n\n eventItemResource.$save(function (savedEventItem) {\n QuoteItem.updateEventItemId(item.id, savedEventItem.id, function (updatedItem) {\n _(item).extend(updatedItem);\n alerts.pushSuccess('alerts.transaction.successfully-linked-to-wedding-registry');\n });\n });\n });\n });\n }\n\n $modal.open({\n templateUrl: 'js/modules/quote/details/modals/register-to-wedding.tpl.html',\n controller: 'RegisterQuoteItemToWeddingCtrl',\n resolve: {\n quote: _.constant($scope.quote),\n item: _.constant(item),\n eventId: _.constant(eventId),\n contactId: _.constant(contactId),\n eventContactName: _.constant(eventContact.firstName + ' ' + eventContact.lastName),\n contactName: _.constant($scope.quote.contactName),\n members: _.constant(members)\n }\n });\n });\n });\n };\n\n $scope.unlinkItemFromWeddingRegistry = function (item) {\n if($scope.readonly) {\n return;\n }\n\n QuoteItem.updateEventItemId(item.id, 0, function(updatedItem) {\n item.version = updatedItem.version;\n item.eventItemId = 0;\n alerts.pushSuccess('alerts.transaction.successfully-unlinked-from-wedding-registry');\n });\n };\n\n $scope.addItemAddOn = function(quoteItem) {\n if($scope.readonly || !$scope.itemAddOnsPerm.set) {\n return;\n }\n\n getAvailableAddItemsOnsAndOpenModal(quoteItem, null, { fetchItemAddOns: true, showModalOnlyIfAddOnsAvailable: false, userSelected: true });\n };\n\n $scope.buildTaxAmountTooltip = function(quoteItem) {\n var text = $translate.instant('transaction.total-tax') + ': ' + $filter('currency')(quoteItem.taxAmount) + '\\n';\n\n if(quoteItem.shippingTax) {\n text += $translate.instant('transaction.shipping-tax') + ': ' + $filter('currency')(quoteItem.shippingTax);\n }\n return text;\n };\n\n $scope.buildSalePriceTooltip = function (quoteItem) {\n if(!quoteItem || !quoteItem.itemSalePrice || !quoteItem.price || quoteItem.itemSalePrice <= 0 || quoteItem.itemSalePrice >= quoteItem.price) {\n return '';\n }\n\n return $translate.instant('item.on-sale') + ': ' + $filter('currency')(quoteItem.itemSalePrice);\n };\n\n $scope.validateAddOnItemValue = function(type, addOn, oldValue) {\n if(oldValue === addOn[type]) {\n return false;\n }\n\n if(type === 'price') {\n if(addOn[type] < 0) {\n modalService.showMessage('alerts.add-ons.invalid-price');\n return false;\n } else if(!addOn[type]) {\n addOn[type] = 0;\n }\n } else if(type === 'description') {\n if(addOn[type] === '') {\n modalService.showMessage('alerts.add-ons.invalid-description');\n return false;\n }\n }\n\n return true;\n };\n\n $scope.collapseRow = function(event, quoteItem) {\n if(event) {\n event.preventDefault();\n }\n\n quoteItem.collapsed = !quoteItem.collapsed;\n };\n\n $scope.switchToEdit = function(event, addOn, type, index) {\n if(event) {\n event.preventDefault();\n }\n\n addOn[type + 'OldValue'] = addOn[type];\n addOn.show = {};\n addOn.show[type] = index;\n };\n\n $scope.switchToView = function (event, addOn, quoteItem, type, index) {\n if(!addOn.show) {\n return;\n }\n\n if(event) {\n event.preventDefault();\n }\n\n var oldValue = addOn[type + 'OldValue'];\n\n if(oldValue !== undefined && oldValue !== addOn[type]) {\n if($scope.validateAddOnItemValue(type, addOn, oldValue, quoteItem)) {\n if(type === 'price') {\n if(!addOn.price) {\n addOn.price = 0;\n }\n\n $scope.onAddOnItemPriceChange(quoteItem, addOn.price, oldValue, index);\n } else if(type === 'description') {\n $scope.saveQuoteItem(quoteItem, function () {\n alerts.pushSuccess('alerts.transaction.successfully-updated-add-on-item');\n });\n }\n } else {\n addOn[type] = oldValue;\n delete addOn[type + 'OldValue'];\n }\n }\n\n delete addOn.show;\n };\n\n $scope.onAddOnItemPriceChange = function(quoteItem, newValue, oldValue) {\n newValue = +newValue;\n\n if(newValue > oldValue) {\n quoteItem.price = quoteItem.price + (newValue - oldValue);\n } else {\n quoteItem.price = quoteItem.price - (oldValue - newValue);\n }\n\n if(quoteItem.discountType === 0) {\n quoteItem.discountPercent = 0; //let the backend compute the new discount percent\n quoteItem.adjustedPrice = quoteItem.price - quoteItem.discountAmount;\n } else {\n quoteItem.discountAmount = null; //let the backend compute the new discount amount\n }\n\n $scope.saveQuoteItem(quoteItem, function () {\n alerts.pushSuccess('alerts.transaction.successfully-updated-add-on-item');\n });\n };\n\n $scope.applySizeChange = function (quoteItem, oldSize, newSize) {\n if (!quoteItem.sizeGroup) {\n return;\n }\n\n if(($scope.isColorRequired(quoteItem) || $scope.isSizeRequired(quoteItem)) && !newSize) {\n $scope.allowInvalidSave = true;\n }\n\n //check to see if there was an upcharge on the old size, if so, the upcharge will be removed before adding another one.\n //this helps fix the issue of having an inventory item that has the upcharge already added\n //into the price.\n var oldSizeObj = _(quoteItem.sizeGroup.sizes).findWhere({ size: oldSize });\n var newSizeObj = _(quoteItem.sizeGroup.sizes).findWhere({ size: newSize });\n\n if(!newSizeObj) {\n newSizeObj = { upchargePrice: 0, upchargePriceType: 0 };\n }\n\n //if their was no size chart for the old size, create a dummy object for comparison\n if(!oldSizeObj) {\n oldSizeObj = { upchargePrice: 0, upchargePriceType: 0 };\n }\n\n var sizeUpchargeAppliedToOldSize = oldSizeObj && oldSizeObj.upchargePrice;\n\n //only change pricing if the old size upcharge is not the same as the new size upcharge\n if (oldSizeObj && newSizeObj && (oldSizeObj.upchargePrice !== newSizeObj.upchargePrice || oldSizeObj.upchargePriceType !== newSizeObj.upchargePriceType)) {\n if (!sizeUpchargeAppliedToOldSize) {\n //look up the upcharge and apply\n $scope.applySizeUpcharge(quoteItem, quoteItem.sizeGroup.sizes, newSize);\n\n if(quoteItem.discountType === 0) {\n quoteItem.discountPercent = 0; //let the backend compute the new discount percent\n quoteItem.adjustedPrice = quoteItem.price - quoteItem.discountAmount;\n } else {\n quoteItem.discountAmount = null; //let the backend compute the new discount amount\n }\n\n } else {\n //if the upcharge was applied to the old size and then they changed it to something else\n //then we need to remove the old size upcharge and apply the other one;\n $scope.removeSizeUpcharge(quoteItem, quoteItem.sizeGroup.sizes, oldSize);\n $scope.applySizeUpcharge(quoteItem, quoteItem.sizeGroup.sizes, newSize);\n\n if(quoteItem.discountType === 0) {\n quoteItem.discountPercent = 0; //let the backend compute the new discount percent\n quoteItem.adjustedPrice = quoteItem.price - quoteItem.discountAmount;\n } else {\n quoteItem.discountAmount = null; //let the backend compute the new discount amount\n }\n\n }\n }\n };\n\n $scope.applySizeUpcharge = function (quoteItem, availableSizes, itemSize) {\n if (availableSizes && availableSizes.length) {\n for (var i = 0; i < availableSizes.length; i++) {\n if (availableSizes[i].size === itemSize) {\n if (availableSizes[i].upchargePrice) {\n\n //detect which type of upcharge to apply\n if(availableSizes[i].upchargePriceType === 0) {\n quoteItem.price = quoteItem.price + availableSizes[i].upchargePrice;\n } else if(availableSizes[i].upchargePriceType === 1) {\n var upchargePercent = availableSizes[i].upchargePrice/100;\n quoteItem.price = quoteItem.price + (upchargePercent * quoteItem.price);\n quoteItem.price = Number(quoteItem.price.toFixed(2));\n } else {\n continue;\n }\n\n quoteItem.sizeUpchargeApplied = true;\n break;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n };\n\n $scope.removeSizeUpcharge = function (quoteItem, availableSizes, itemSize, updateSalePrice) {\n if (availableSizes && availableSizes.length) {\n for (var i = 0; i < availableSizes.length; i++) {\n if (availableSizes[i].size === itemSize) {\n if (availableSizes[i].upchargePrice) {\n\n //detect which type of upcharge to apply\n if(availableSizes[i].upchargePriceType === 0) {\n // When add an item to a transaction and we plan to remove the up size charge. Then go ahead and do it for sales price also if it exists.\n if(updateSalePrice && quoteItem.itemSalePrice && quoteItem.itemSalePrice > 0 && quoteItem.itemSalePrice < quoteItem.price) {\n quoteItem.adjustedPrice = quoteItem.itemSalePrice - availableSizes[i].upchargePrice;\n }\n\n quoteItem.price = quoteItem.price - availableSizes[i].upchargePrice;\n } else if(availableSizes[i].upchargePriceType === 1) {\n var upchargePercent = availableSizes[i].upchargePrice/100;\n\n // When add an item to a transaction and we plan to remove the up size charge. Then go ahead and do it for sales price also if it exists.\n if(updateSalePrice && quoteItem.itemSalePrice && quoteItem.itemSalePrice > 0 && quoteItem.itemSalePrice < quoteItem.price) {\n quoteItem.adjustedPrice = quoteItem.itemSalePrice / (1 + upchargePercent);\n quoteItem.adjustedPrice = Number(quoteItem.itemSalePrice.toFixed(2));\n }\n\n quoteItem.price = quoteItem.price / (1 + upchargePercent);\n quoteItem.price = Number(quoteItem.price.toFixed(2));\n } else {\n continue;\n }\n\n quoteItem.sizeUpchargeApplied = false;\n break;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n };\n\n $scope.showSizeModal = function(quoteItem, sizeGroup) {\n itemSizesModal.view(quoteItem, sizeGroup, $scope.quote.contact, true, function(size) {\n if(size) {\n $scope.applySizeChange(quoteItem, quoteItem.itemSize, size);\n quoteItem.itemSize = size;\n $scope.saveQuoteItem(quoteItem);\n }\n });\n };\n\n $scope.moreActionsPermissions = function() {\n return security.hasPermission(Permissions.DUPLICATE_QUOTES) || security.hasPermission(Permissions.DELETE_QUOTES) || security.hasPermission(Permissions.VOID_QUOTES) || (security.hasPermission(Permissions.MESSAGES) && $scope.quote.contactId);\n };\n\n $scope.showSendEmailModal = function () {\n Contact.getById($scope.quote.contactId, function (contact) {\n EmailService.showSendModal(contact, null, null, null, null, null, buildCustomEmailOptions(), null, null, null, null, $scope.quote);\n });\n };\n\n $scope.showSendTextMessageModal = function() {\n Contact.getById($scope.quote.contactId, function (contact) {\n Sms.showSendModal(contact, null, null, null, null, null, $scope.quote);\n });\n };\n\n $scope.duplicateQuote = function () {\n modalService.showConfirmation('quote.messages.would-like-to-duplicate?', function () {\n Quote.duplicate($scope.quote, User, function (newQuote) {\n ignoreRedirectWarning = true;\n $state.go('quote.edit', { id: newQuote.id });\n\n alerts.pushSuccess('alerts.quote.successfully-duplicated-quote');\n });\n });\n };\n\n $scope.voidQuote = function () {\n modalService.showConfirmation('quote.messages.would-like-to-void?',\n function () {\n Quote.void($scope.quote.id, function (quote) {\n alerts.pushSuccess('alerts.transaction.successfully-voided-order');\n $scope.quote.status = $scope.voidedStatusValue;\n $scope.readonly = true;\n });\n });\n };\n\n $scope.deleteQuote = function () {\n modalService.showConfirmation('quote.messages.would-like-to-delete-quote?',\n function () {\n Quote.delete($scope.quote).then(function () {\n ignoreRedirectWarning = true;\n alerts.pushSuccess('alerts.quote.has-been-deleted-successfully');\n $state.go('quotes');\n });\n }\n );\n };\n\n $scope.processSmartFlows = function () {\n var successCallback = function () {\n $scope.quote.smartFlowsProcessedDateTime = new Date().valueOf();\n $scope.saveQuote();\n };\n\n var failureCallback = function () {\n $scope.quote.smartFlowsProcessedDateTime = new Date().valueOf();\n $scope.saveQuote();\n };\n\n if (!$scope.quote.contactId) {\n //if no contact is assigned, see if they have toggled on the Untracked Contact feature. If so, allow the flows to process with an empty contact\n if($scope.quote.contactName) {\n eventsBus.publish('quote:created', [$scope.quote, {}, { eventDate: null }, successCallback, failureCallback]);\n } else {\n modalService.showMessage('transaction.message.assign-contact-first');\n }\n } else {\n Contact.getById($scope.quote.contactId, function (contact) {\n eventsBus.publish('quote:created', [$scope.quote, contact, { eventDate: contact.eventDate }, successCallback, failureCallback]);\n });\n }\n };\n\n $scope.isColorRequired = function(quoteItem) {\n if(quoteItem.quantity !== 0 && !quoteItem.isNonInventoryItem && !quoteItem.isService) {\n if($scope.colorAndSizeRequired || (!$scope.colorOrSizeAllowCustomVal && $scope.itemsColorOptions[quoteItem.id].length)) {\n return true;\n }\n }\n return false;\n };\n\n $scope.isSizeRequired = function(quoteItem) {\n if(quoteItem.quantity !== 0 && !quoteItem.isNonInventoryItem && !quoteItem.isService) {\n if($scope.colorAndSizeRequired || (!$scope.colorOrSizeAllowCustomVal && $scope.itemsSizeOptions[quoteItem.id].length)) {\n return true;\n }\n }\n return false;\n };\n\n $scope.goToTransaction = function(typeId, trxId) {\n $state.go(getTrxRoute(typeId), { id: trxId });\n };\n\n $scope.getTrxType = function(trxTypeId) {\n return _.findWhere(Enums.trxTypes, { value: trxTypeId });\n };\n\n function getTrxRoute(typeId) {\n var trxRoutes = {\n 1: 'transaction.sale',\n 2: 'transaction.specialOrder',\n 3: 'transaction.return',\n 4: 'transaction.layaway'\n };\n\n return trxRoutes[typeId];\n }\n\n\n $scope.buildSalePriceTooltip = function (quoteItem) {\n if(!quoteItem || !quoteItem.itemSalePrice || !quoteItem.price || quoteItem.itemSalePrice <= 0 || quoteItem.itemSalePrice >= quoteItem.price) {\n return '';\n }\n\n return $translate.instant('item.on-sale') + ': ' + $filter('currency')(quoteItem.itemSalePrice);\n };\n\n /*\n * Helpers\n */\n\n function openLeadTimesModal(quoteItem, addOnsResult, callback) {\n $modal.open({\n templateUrl: 'js/modules/quote/details/modals/add-ons.tpl.html',\n controller: 'QuoteAddOnsCtrl',\n windowClass: '__large',\n resolve: {\n quoteItem: function () {\n return quoteItem;\n },\n addOnsResult: function () {\n return addOnsResult;\n }\n }\n })\n .result\n .then(function (addOnsModalResult) {\n if(!addOnsModalResult) {\n if(callback) {\n callback();\n }\n return;\n }\n\n var quoteItemModifications = addOnsModalResult.quoteItemModifications;\n var addOnItem = addOnsModalResult.addOnItem;\n\n if(quoteItemModifications) {\n var clonedQuoteItem = angular.copy(quoteItem);\n clonedQuoteItem - _.extend(clonedQuoteItem, quoteItemModifications);\n\n $scope.saveQuoteItem(clonedQuoteItem, function () {\n quoteItem.estimatedDeliveryDate = clonedQuoteItem.estimatedDeliveryDate;\n if(addOnItem) {\n onAddLineItem(addOnItem, null, true, callback);\n }\n });\n } else if(addOnItem) {\n onAddLineItem(addOnItem, null, true, callback);\n }\n });\n }\n\n function buildCustomEmailOptions() {\n var agreements = [{ ticked: true, icon: '', description: $translate.instant('modal-service.select-a-quote-agreement') }];\n var terminalIcon = '
';\n var signedIcon = $translate.instant('common.modal.signed-pen-icon');\n\n _($scope.quoteAgreements).each(function(agreement) {\n agreements.push(_.extend(agreement, {\n icon: agreement.signed ? signedIcon : '',\n ticked: false\n }));\n });\n\n _($scope.quoteTerminalContracts).each(function(contract) {\n agreements.push(_.extend(contract, {\n icon: contract.signed ? terminalIcon + ' ' + signedIcon : terminalIcon,\n ticked: false\n }));\n });\n\n return {\n showOptions: true,\n showQuoteAgreements: true,\n quoteAgreements: agreements\n };\n }\n\n function getAvailableAddItemsOnsAndOpenModal(quoteItem, item, options) {\n if(options.fetchItemAddOns) {\n var addOnRequest = {\n leadTimes: false,\n itemAddOns: true,\n quote: $scope.quote,\n quoteItem: quoteItem,\n item: item\n };\n\n QuoteItem.getAvailableAddOns(addOnRequest, function (addOnsResult) {\n //if we asked to load lead times and we have some, show the modal\n if(addOnsResult.itemAddOns === null) {\n addOnsResult.itemAddOns = [];\n }\n\n if(!options.showModalOnlyIfAddOnsAvailable || (options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnsResult.itemAddOns.length > 0)) {\n openAddOnModal(quoteItem, addOnsResult, options);\n }\n });\n }\n }\n\n function openAddOnModal(quoteItem, addOnsResult, options) {\n var allowedToClose = !$scope.itemAddOnsPerm.addOnRequired;\n\n if(options && options.userSelected) {\n allowedToClose = true;\n }\n\n $modal.open({\n templateUrl: 'js/modules/quote/details/modals/add-item-add-ons.tpl.html',\n controller: 'AddQuoteItemAddOnsCtrl',\n windowClass: '__large',\n keyboard: allowedToClose,\n backdrop: 'static',\n resolve: {\n entityType: function() {\n return 'quote';\n },\n entityItem: function () {\n return quoteItem;\n },\n addOnsResult: function () {\n return addOnsResult;\n },\n item: function () {\n return addOnsResult.item;\n },\n allowedToClose: function () {\n return allowedToClose;\n }\n }\n })\n .result\n .then(function (addOnsModalResult) {\n var quoteItemModifications = addOnsModalResult && addOnsModalResult.quoteItemModifications;\n if(quoteItemModifications && quoteItemModifications.quoteItemAddOns && quoteItemModifications.quoteItemAddOns.length) {\n $scope.saveQuoteItem(_.extend(quoteItem, quoteItemModifications));\n }\n });\n }\n\n function getAddOnItems(lineItems, currentIndex, fetchItems) {\n var quoteLineItem = lineItems[currentIndex];\n\n if(quoteLineItem && quoteLineItem.inventoryItemId) {\n Item.getById(quoteLineItem.inventoryItemId, function(item) {\n $scope.getAvailableAddOnsAndOpenModal(quoteLineItem, item, {\n showModalOnlyIfAddOnsAvailable: true,\n fetchItemAddOns: fetchItems\n }, function() {\n currentIndex++;\n var nextLineItem = lineItems[currentIndex];\n if(nextLineItem) {\n getAddOnItems(lineItems, currentIndex, fetchItems);\n }\n });\n });\n }\n }\n\n function openCustomerAddressModal(quote) {\n $modal.open({\n templateUrl: 'js/modules/quote/details/modals/customer-address-modal.tpl.html',\n controller: 'QuoteCustomerAddressCtrl',\n resolve: {\n contact: function (Contact) {\n if(quote.contactId) {\n return Contact.getById(quote.contactId);\n } else {\n return null;\n }\n },\n quote: function () {\n return quote;\n }\n },\n windowClass: '__medium'\n }).result.then(function(updatedQuote) {\n $scope.quote = updatedQuote;\n\n if(quote && quote.contactId) {\n // Update contact's address\n Contact.updateAddressFieldsFromQuote(quote, function (contact) {\n if(contact && contact.id) {\n $scope.quote.contact = contact;\n }\n });\n }\n });\n }\n\n function isCustomerAddressInvalid(quote) {\n if(quote.contactId === 0) {\n if(!quote.shipToZip) {\n return true;\n }\n } else {\n if(!quote.shipToAddress1 || !quote.shipToZip || !quote.shipToCity ||!quote.nf525InvoiceCountry) {\n return true;\n }\n }\n\n return false;\n }\n\n function updateShippingCostAndRecalculate() {\n $scope.queriesInProgress += 1;\n $scope.recalculatingTax = true;\n\n Quote.updateLineItemsAndCalculateTaxes($scope.quote, true, function(response) {\n if(response && response.quote) {\n var savedQuote = response.quote;\n\n angular.extend($scope.quote, _(savedQuote).omit(\n 'quoteLineItems',\n 'employeeId',\n 'fee',\n 'notes'\n ));\n\n updateLineItemsAfterUpdatingShippingInfo(savedQuote);\n }\n }).finally(function () {\n // without timeout saving state will be \"jumpy\"\n $timeout(function () {\n $scope.queriesInProgress -= 1;\n }, 1000);\n });\n }\n\n function updateLineItemsAfterUpdatingShippingInfo(savedQuote) {\n // Update version of all line items after updating shipping information.\n _($scope.quote.quoteLineItems).each(function (quoteItem, index) {\n quoteItem.version = savedQuote.quoteLineItems[index].version;\n quoteItem.modifiedDate = savedQuote.quoteLineItems[index].modifiedDate;\n quoteItem.modifiedByUser = savedQuote.quoteLineItems[index].modifiedByUser;\n quoteItem.addressId = savedQuote.quoteLineItems[index].addressId;\n quoteItem.taxAmount = savedQuote.quoteLineItems[index].taxAmount;\n quoteItem.shippingTax = savedQuote.quoteLineItems[index].shippingTax;\n quoteItem.shippingCost = savedQuote.quoteLineItems[index].shippingCost;\n quoteItem.shippingTaxPercent = savedQuote.quoteLineItems[index].shippingTaxPercent;\n quoteItem.shippingTaxCodeId = savedQuote.quoteLineItems[index].shippingTaxCodeId;\n quoteItem.shippingTaxable = savedQuote.quoteLineItems[index].shippingTaxable;\n quoteItem.taxCodeId = savedQuote.quoteLineItems[index].taxCodeId;\n quoteItem.taxCodeDescription = savedQuote.quoteLineItems[index].taxCodeDescription;\n quoteItem.taxCodeIsComputed = savedQuote.quoteLineItems[index].taxCodeIsComputed;\n\n _(quoteItem.quoteItemAddOns).each(function (addOn, i) {\n if(savedQuote.quoteLineItems[index].quoteItemAddOns && savedQuote.quoteLineItems[index].quoteItemAddOns.length && savedQuote.quoteLineItems[index].quoteItemAddOns[i]) {\n addOn.version = savedQuote.quoteLineItems[index].quoteItemAddOns[i].version;\n }\n });\n });\n\n $rootScope.quoteHasShippingAddress = quoteHasShippingAddress($scope.quote.quoteLineItems);\n }\n\n function quoteHasShippingAddress(items) {\n return _(items).filter(function(item) { return item.addressId; }).length;\n }\n\n function quoteHasLineItems(items) {\n return !!(items && items.length > 0);\n }\n\n function validateQuoteItems() {\n $timeout(function () {\n $scope.$broadcast('show-errors-check-validity');\n });\n }\n\n function validateThenConvert(typeId, callback) {\n if(!$scope.quote.employeeId) {\n modalService.showMessage('quote.messages.assign-associate-before-converting');\n return;\n }\n\n if($scope.markSignSalesAgreementRed) {\n modalService.showMessage('quote.messages.obtain-signature-before-converting');\n return;\n }\n\n if(!$scope.quote.smartFlowsProcessedDateTime && $scope.quote.smartFlowsTriggeredDateTime && security.hasPlanPermission('smartFlows')) {\n modalService.showMessage('quote.messages.process-smart-flow-before-converting');\n return;\n }\n\n Quote.validateBeforeConvertToTrx($scope.quote, typeId, function(result) {\n if(result) {\n if(result.errors && result.errors.length) {\n modalService.showMessage(buildConvertErrors(result.errors), 'modal-service.error');\n } else if(callback) {\n callback();\n }\n }\n });\n }\n\n function buildConvertErrors(errors) {\n var errorMessage = '';\n\n if(errors && errors.length) {\n var heading = $translate.instant('modal-service.please-correct-following-errors');\n\n errorMessage += '
' + heading + ' ';\n _(errors).each(function(error) {\n if(error.errorMessages && error.errorMessages.length) {\n _(error.errorMessages).each(function(message) {\n errorMessage += '' + message + ' ';\n });\n }\n });\n\n errorMessage += ' ';\n }\n\n return errorMessage;\n }\n\n function itemToQuoteItem(item) {\n var taxCode = _.findWhere($scope.taxCodes, { id: item.taxCodeId });\n var quoteItemAddOns = ItemHelper.buildLineItemAddOnsTrx(item.itemAddOns, item, null, { default: true });\n var addOnTotalPrice = _.reduce(quoteItemAddOns, function(memo, num){ return memo + num.price; }, 0);\n var addOnTotalCost = _.reduce(quoteItemAddOns, function(memo, num){ return memo + num.cost; }, 0);\n\n var taxPercent = 0;\n var includedTaxPercent = 0;\n if(taxCode) {\n if(taxCode.inclusive) {\n includedTaxPercent = taxCode.percent || 0;\n } else {\n taxPercent = taxCode.percent || 0;\n }\n }\n\n var regularPrice = item.regularPrice + addOnTotalPrice;\n var adjustedPrice = regularPrice;\n var discountAmount;\n if(item.salePrice && item.salePrice > 0 && item.salePrice < item.regularPrice) {\n adjustedPrice = item.salePrice + addOnTotalPrice;\n discountAmount = regularPrice - adjustedPrice;\n }\n\n return {\n // converting\n inventoryItemId: item.id,\n price: regularPrice,\n taxCodeId: item.taxCodeId,\n taxCodeDescription: taxCode && taxCode.description,\n taxCodeIsComputed: taxCode && taxCode.computed,\n taxPercent: taxPercent,\n includedTaxPercent: includedTaxPercent,\n adjustedPrice: adjustedPrice,\n discountAmount: discountAmount,\n isInventoryItem: item.inventoryItem,\n isNonInventoryItem: item.nonInventoryItem,\n isRental: item.rental,\n isService: item.service,\n itemNumber: item.itemNumber,\n itemName: item.name,\n itemDescription: item.description,\n itemColor: colorAndSizeBlank ? '' : item.color,\n itemColor2: colorAndSizeBlank ? '' : item.color2,\n itemSize: colorAndSizeBlank ? '' : item.size,\n itemSalePrice: item.salePrice,\n itemDepartmentId: item.departmentId,\n itemVendorItemName: item.vendorItemName,\n itemOrderCost: item.orderCost + addOnTotalCost,\n doNotAllowDiscounts: item.doNotAllowDiscounts,\n quoteItemAddOns: quoteItemAddOns,\n\n // defaults\n quantity: $scope.isReturn ? -1 : 1,\n quoteId: $scope.quote.id,\n sequenceNumber: $scope.quote.quoteLineItems.length,\n createdByUser: User.username,\n createdDate: new Date().valueOf(),\n shippingCost: 0,\n shippingTax: 0,\n shippingTaxable: false\n };\n }\n\n function getDefaultQuoteAgreement(quoteSettings) {\n var setting = _(quoteSettings).findWhere({ type: 'QUOTE_DEFAULT_AGREEMENT'});\n if(setting && setting.value && setting.value > 0) {\n return _($scope.quoteAgreements).findWhere({ id: parseInt(setting.value)});\n }\n return null;\n }\n\n\n // Note popover\n (function () {\n var $notePopover;\n var notePopoverScope;\n\n var closeNotePopover = function (event) {\n if (!angular.element(event.target).parents('.popover').length && $notePopover) {\n $notePopover.popover('destroy');\n }\n };\n\n // get a Receiving Voucher item attached to passed DOM element by tracking parent tr element\n var getQuote = function (element) {\n var index = parseInt(angular.element(element).parents('tr').attr('data-index'), 10);\n return $scope.quote.quoteLineItems[index];\n };\n\n var getAddOnItem = function (element) {\n var parentIndex = parseInt(angular.element(element).parents('tr').attr('data-parent-index'), 10);\n var childIndex = parseInt(angular.element(element).parents('tr').attr('data-child-index'), 10);\n return $scope.quote.quoteLineItems[parentIndex].quoteItemAddOns[childIndex];\n };\n\n angular.element('.transaction_items').on('click', '.transaction_item-notes', function (event) {\n event.stopPropagation();\n\n if ($notePopover) {\n $notePopover.popover('destroy');\n }\n\n if (notePopoverScope) {\n notePopoverScope.$destroy();\n }\n\n var quote = getQuote(this);\n notePopoverScope = $rootScope.$new();\n notePopoverScope.item = quote;\n notePopoverScope.readonly = $scope.readonly;\n\n var template = '
';\n\n $notePopover = angular.element(this).popover({\n content: $compile(template)(notePopoverScope),\n placement: 'right',\n trigger: 'manual',\n html: true\n }).popover('show');\n\n notePopoverScope.$digest();\n\n angular.element($window).on('click', closeNotePopover);\n\n // When popover is closed\n angular.element('.transaction_items').off('hidden.bs.popover').on('hidden.bs.popover', '.transaction_item-notes', function () {\n // Disable close popover handler\n angular.element($window).off('click', closeNotePopover);\n });\n });\n\n // Save quote on any input changing\n angular.element(document).off('change', '.save-trigger').on('change', '.save-trigger', function (event) {\n if (event.target.getAttribute('ng-model') === 'quote.addtlDiscountPercent') {\n $scope.quote.addtlDiscountAmount = 0;\n $scope.quote.addtlDiscountType = 1; //Percent discount type\n } else if (event.target.getAttribute('ng-model') === 'quote.addtlDiscountAmount') {\n $scope.quote.addtlDiscountPercent = 0;\n $scope.quote.addtlDiscountType = 0; //Dollar discount type\n }\n $scope.saveQuote();\n });\n\n angular.element('.transaction_items').on('click', '.transaction_item-add-on-notes', function (event) {\n event.stopPropagation();\n\n if ($notePopover) {\n $notePopover.popover('destroy');\n }\n\n if (notePopoverScope) {\n notePopoverScope.$destroy();\n }\n\n var addOnItem = getAddOnItem(this);\n notePopoverScope = $rootScope.$new();\n notePopoverScope.addOn = addOnItem;\n notePopoverScope.readonly = $scope.readonly;\n notePopoverScope.editNotes = $scope.itemAddOnsPerm.editNotes;\n\n var template = '
';\n\n $notePopover = angular.element(this).popover({\n content: $compile(template)(notePopoverScope),\n placement: 'right',\n trigger: 'manual',\n html: true\n }).popover('show');\n\n notePopoverScope.$digest();\n\n angular.element($window).on('click', closeNotePopover);\n\n // When popover is closed\n angular.element('.transaction_items').off('hidden.bs.popover').on('hidden.bs.popover', '.transaction_item-add-on-notes', function () {\n // Disable close popover handler\n angular.element($window).off('click', closeNotePopover);\n });\n });\n })();\n\n if($stateParams && $stateParams.referrer === 'contact.favorites' && $stateParams.promptItemAddOns && $scope.quote && $scope.quote.quoteLineItems && $scope.quote.quoteLineItems.length) {\n getAddOnItems($scope.quote.quoteLineItems, 0, true);\n }\n\n /**\n * Handlers\n */\n\n $scope.$on('$stateChangeStart', function (event, toState, toParams) {\n if (ignoreRedirectWarning || $scope.readonly) {\n return;\n }\n\n if ($scope.isNewQuote) {\n event.preventDefault();\n\n modalService.showConfirmation('quote.messages.would-like-to-discard?', function () {\n $scope.quote.$remove(function () {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, {location: 'replace'});\n });\n });\n } else if (!$scope.quote.smartFlowsProcessedDateTime && $scope.quote.smartFlowsTriggeredDateTime && security.hasPlanPermission('smartFlows')) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.process-smart-flow-before-leaving');\n\n } else if ($scope.markSignSalesAgreementRed) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.obtain-signature-before-leaving');\n\n } else if (!$scope.quote.employeeId) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.assign-associate-before-leaving');\n } else if ($scope.isNF525 && !$scope.quote.expirationDate) {\n event.preventDefault();\n modalService.showMessage('quote.messages.set-expiration-date-before-leaving');\n }\n });\n\n /**\n * Watchers\n */\n\n var ignoreFirstTime = true;\n\n $scope.$watchCollection('[quote.contactId, quote.contactName, quote.contactPhone, quote.contactPhoneType, quote.eventDate, quote.expirationDate]', function () {\n if (!ignoreFirstTime) {\n $scope.saveQuote();\n }\n\n ignoreFirstTime = false;\n });\n\n $scope.itemsColorOptions = {};\n $scope.itemsColor2Options = {};\n $scope.itemsSizeOptions = {};\n\n $scope.$watchCollection('quote.quoteLineItems', function (items) {\n // Determine if any lineItems has a pos address assign to it.\n $rootScope.quoteHasShippingAddress = quoteHasShippingAddress(items);\n $scope.quoteHasLineItems = quoteHasLineItems(items);\n\n _(items).each(function(item) {\n $scope.itemsColorOptions[item.id] = item.colorString ? item.colorString.trim().split(',') : [];\n $scope.itemsColor2Options[item.id] = item.color2String ? item.color2String.trim().split(',') : [];\n $scope.itemsSizeOptions[item.id] = item.sizeGroup ? item.sizeGroup.sizes.map(function (size) { return size.size; }) : [];\n });\n });\n\n });\n});\n\n","define('modules/quote/details/modals/ship-to-customer-ctrl',['../../module','underscore'], function (module, _) {\n 'use strict';\n\n module.controller('ShipToCustomerQuoteModalCtrl', function($scope, $rootScope, $modalInstance, $modal, $locale, $translate, AppLicense, alerts, User, Enums, modalService, Quote, QuoteAddress, Contact, Event, EventMember, quote, addresses, eventMembers, settings, taxCodes) {\n\n var license = AppLicense.get();\n\n $scope.locale = $locale;\n $scope.states = $locale.states;\n $scope.taxCodes = _.filter(taxCodes, function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n\n $scope.showTaxableColumn = !(settings && settings.apiKey && settings.apiKey.length);\n\n var defaultTaxCode = _(taxCodes).findWhere({ shippingDefault: true });\n\n if(!defaultTaxCode && taxCodes && taxCodes.length) {\n defaultTaxCode = _(taxCodes).findWhere({ default: true });\n }\n\n $scope.countries = Enums.countries;\n $scope.quote = angular.copy(quote);\n\n //Preselect all the lineItems\n var customerAddress = _(addresses).findWhere({ contactId: quote.contactId });\n\n _($scope.quote.quoteLineItems).each(function (item) {\n if($scope.showTaxableColumn) {\n if(!item.shippingTaxCodeId && defaultTaxCode) {\n item.shippingTaxCodeId = defaultTaxCode.id;\n item.shippingTaxPercent = defaultTaxCode.percent;\n }\n }\n\n if(!item.addressId) {\n item.addressId = customerAddress ? customerAddress.id : null;\n }\n\n item.selected = true;\n });\n\n $scope.company = license && license.company;\n $scope.validAddress = validateAddress($scope.company);\n\n $scope.contactAddressNotValid = true;\n $scope.quoteAddressNotValid = false;\n $scope.selectAll = true;\n $scope.lineItemUpdated = false;\n $scope.flatShipping = 0;\n\n $scope.step = 1;\n\n if(!addresses || !addresses.length) {\n var contactId = $scope.quote && $scope.quote.contactId;\n\n if(contactId) {\n Contact.getById(contactId, function (contact) {\n $scope.address = buildAddressObject(contact, contactId, false, true);\n });\n }\n } else {\n var invalidQuoteAddress = _(addresses).findWhere({ quoteAddressInvalid: true });\n\n if(invalidQuoteAddress) {\n addresses = _(addresses).without(invalidQuoteAddress);\n\n $scope.step = 1;\n $scope.quoteAddressNotValid = true;\n\n $scope.address = buildAddressObject(invalidQuoteAddress, null, true, false);\n } else {\n $scope.step = 0;\n }\n\n $scope.contactAddressNotValid = false;\n }\n\n $scope.addressOptions = addresses;\n $scope.eventMembers = eventMembers;\n\n _($scope.eventMembers).each(function (member) {\n var memberType = member.eventMemberRoleDescription ? ' - ' + member.eventMemberRoleDescription : '';\n member.label = member.firstName + ' ' + (member.lastName || '') + memberType;\n });\n\n var defaultAddress = { id: null, addressHtml: buildAddressHmtl($scope.company), addressString: $translate.instant('transaction.ship-to-customer.in-store-pick-up') };\n\n $scope.addressOptions.unshift(defaultAddress);\n updateAddressDropDown($scope);\n\n $scope.selectAllEnabledItems = function (selectAll) {\n $scope.selectAll = !selectAll;\n\n _($scope.quote.quoteLineItems).each(function (item) {\n item.selected = selectAll;\n });\n };\n\n $scope.isQuoteItemDisabled = function(quoteItem) {\n if(quoteItem) {\n if(quoteItem.purchaseOrderItemId) {\n return false;\n } else {\n return quoteItem.quantity <= quoteItem.quantitySold || quoteItem.itemStatus;\n }\n }\n };\n\n $scope.addNew = function () {\n $scope.address = buildAddressObject(null, $scope.quote.contactId, false, false);\n $scope.previousStep = $scope.step;\n $scope.step = 1;\n };\n\n $scope.edit = function (address, index) {\n $scope.previousStep = $scope.step;\n $scope.addressIndex = index;\n $scope.address = buildAddressObject(address, address.contactId, true, false);\n $scope.step = 2;\n };\n\n $scope.back = function() {\n $scope.step = $scope.previousStep;\n $scope.address = buildAddressObject();\n $scope.addressIndex = null;\n };\n\n $scope.getShippingName = function(addressId) {\n var address = _($scope.addressOptions).findWhere({ id: addressId });\n\n if(address) {\n return address.firstName + ' ' + address.lastName;\n }\n\n return '';\n };\n\n $scope.onSaveClick = function () {\n if(isAddressInvalid($scope.address)) {\n return modalService.showMessage('transaction.ship-to-customer.required-address-fields');\n }\n\n if ($scope.onSaveClick.isInProgress) {\n // quit if save is already going on\n return;\n }\n\n $scope.onSaveClick.isInProgress = true;\n\n var isUpdate = false;\n\n if($scope.address && $scope.address.id) {\n $scope.address.modifiedByUser = User.username;\n $scope.address.modifiedDate = new Date();\n\n isUpdate = true;\n } else {\n $scope.address.createdByUser = User.username;\n $scope.address.createdDate = new Date();\n }\n\n $scope.address.$saveOrUpdate(function (updatedAddress) {\n $scope.contactAddressNotValid = false;\n\n if(isUpdate && !$scope.quoteAddressNotValid) {\n $scope.addressOptions[$scope.addressIndex] = updatedAddress;\n $scope.addressIndex = null;\n } else {\n $scope.addressOptions.push(updatedAddress);\n }\n\n $scope.quoteAddressNotValid = false;\n\n updateAddressDropDown($scope);\n\n $scope.step = 0;\n\n $scope.address = buildAddressObject();\n }, null).finally(function () {\n $scope.onSaveClick.isInProgress = false;\n });\n };\n $scope.onSaveClick.isInProgress = false;\n\n $scope.removeAddress = function (address, e) {\n if(e) {\n e.stopPropagation();\n }\n\n var posAddress = _(address).clone();\n\n var html = '
';\n html += ' ' + address.address1 + (address.address2 ? \", \" + address.address2 : '') + ' ';\n html += address.city + ', ' + address.state + ' ' + address.zip + ' ' + address.country + '
';\n\n modalService.showConfirmation({\n key: 'transaction.ship-to-customer.remove-confirmation',\n params: { addressHtml: html }\n }, function () {\n posAddress.$remove(function () {\n $scope.addressOptions = _($scope.addressOptions).without(address);\n updateAddressDropDown($scope);\n });\n });\n };\n\n $scope.onMemberChange = function(member) {\n $scope.address = buildAddressObject(member, (member && member.id));\n };\n\n $scope.setSelected = function (addressId) {\n _($scope.quote.quoteLineItems).each(function (item) {\n if(item.selected) {\n _(item.addresses).each(function (address) {\n address.ticked = address.id === addressId;\n });\n\n item.addressId = addressId;\n }\n });\n };\n\n $scope.distributeFlatShipping = function(shippingIn) {\n if(!$scope.quote.quoteLineItems || !$scope.quote.quoteLineItems.length) {\n return;\n }\n\n var selectedCount = 0;\n _($scope.quote.quoteLineItems).each( function(item) {\n if (item.selected) {\n selectedCount++;\n }\n });\n\n var shippingSplit = shippingIn / selectedCount;\n shippingSplit = Math.round(shippingSplit * 100) / 100;\n _($scope.quote.quoteLineItems).each( function(item) {\n item.shippingCost = shippingSplit;\n });\n\n $scope.quote.quoteLineItems[0].shippingCost += shippingIn - (shippingSplit * selectedCount);\n };\n\n $scope.saveAndCalculate = function () {\n var quote = angular.copy($scope.quote);\n\n _(quote.quoteLineItems).each(function (item) {\n var address = item.address[0];\n\n if(address && address.id) {\n item.addressId = address.id;\n } else {\n item.addressId = null;\n }\n\n delete item.address;\n });\n\n $scope.saving = true;\n Quote.updateLineItemsAndCalculateTaxes(quote, false, function (response) {\n alerts.pushSuccess('transaction.ship-to-customer.successfully-saved-shipping-address');\n $scope.saving = false;\n $scope.$close(response);\n }, function (data, status) {\n $scope.saving = false;\n Quote.defaultErrorHandler(data, status);\n });\n };\n\n $scope.onClose = function() {\n $scope.$close(null);\n };\n\n $scope.getCountry = function (entity) {\n return getCountryName(entity);\n };\n\n $scope.onAddressChanged = function (data, quoteItem) {\n if(data) {\n quoteItem.addressId = data.id;\n }\n };\n\n $scope.onTaxCodeChange = function (quoteItem) {\n var taxCode = _(taxCodes).findWhere({ id: quoteItem.shippingTaxCodeId });\n if(taxCode) {\n quoteItem.shippingTaxPercent = taxCode.percent;\n quoteItem.shippingTaxCodeId = taxCode.id;\n }\n };\n\n /**\n * Helpers\n */\n\n function getCountryName(entity) {\n var countryValue = entity && entity.locale ? entity.locale : entity && entity.country;\n var country = _(Enums.locales).findWhere({ value: countryValue });\n\n if(country) {\n return $translate.instant(country.label);\n }\n\n return countryValue;\n }\n\n function buildAddressHmtl(entity) {\n var name = entity && entity.firstName ? entity.firstName + (entity.lastName ? ' ' + entity.lastName : '') : $translate.instant('transaction.ship-to-customer.in-store-pick-up');\n\n var html = '
' + name + '
';\n\n if(entity && entity.name) {\n html += entity.name + ' ';\n }\n\n html += entity.address1 + (entity.address2 ? ', ' + entity.address2 : '') + ' ';\n html += entity.city + ', ' + entity.state + ' ' + entity.zip + ' ' + getCountryName(entity) + ' ';\n return html;\n }\n\n function updateAddressDropDown($scope) {\n _($scope.addressOptions).each(function (address) {\n if(address.id) {\n address.addressHtml = buildAddressHmtl(address);\n }\n });\n\n _($scope.quote.quoteLineItems).each(function (item) {\n item.addresses = angular.copy($scope.addressOptions);\n\n var address = _(item.addresses).findWhere({ id: item.addressId });\n\n if(address) {\n item.address = [address];\n } else {\n item.addressId = null;\n item.address = [defaultAddress];\n }\n\n _(item.addresses).each(function (address) {\n address.ticked = item.addressId === address.id;\n });\n });\n }\n\n function isAddressInvalid(address) {\n if(!address || !address.firstName || !address.lastName || !address.address1 || !address.city || !address.state || !address.zip || !address.country) {\n return true;\n }\n\n return false;\n }\n\n function validateAddress(company) {\n if(!company) {\n return false;\n }\n\n if(!company.address1 || !company.city || !company.state || !company.zip || !company.locale) {\n return false;\n }\n\n return true;\n }\n\n function buildAddressObject(address, contactId, isEdit, isNew) {\n if(isEdit) {\n address.updateContactAddress = address.quoteId ? false : true;\n\n return address;\n } else {\n return new QuoteAddress ({\n firstName: address && address.firstName ? address.firstName : '',\n lastName: address && address.lastName ? address.lastName : '',\n state: address && address.state ? findState(address.state) : '',\n city: address && address.city ? address.city : '',\n zip: address && address.zip ? address.zip : '',\n address1: address && address.address1 ? address.address1 : '',\n address2: address && address.address2 ? address.address2 : '',\n country: address && address.country ? address.country : $locale.defaultCountryValue ? $locale.defaultCountryValue : '',\n addressString: address ? buildAddressString(address) : '',\n updateContactAddress: !!isNew,\n contactId: contactId ? contactId : null\n });\n }\n }\n\n function findState(stateValue) {\n var state = _($locale.states).findWhere({ value: stateValue });\n if(state) {\n return state.value;\n }\n }\n\n function buildAddressString(addressIn) {\n return addressIn.address1 + (addressIn.address2 ? \", \" + addressIn.address2 : \"\") + \"\\n\" + addressIn.city + \", \" + addressIn.state + \", \" + addressIn.zip;\n }\n });\n});\n\n","define('modules/quote/search/search-ctrl',['./../module', 'angular', 'underscore'], function (module, angular, _) {\n 'use strict';\n\n module.controller('QuoteSearchCtrl', function ($scope, $state, $stateParams, $controller, $modal, $translate, $timeout, $locale, $filter,\n Enums, Quote, employees, alerts, modalService, ResourcesHelper, Transaction, security, Contact, User) {\n\n $scope.locale = $locale;\n\n $scope.isUS = $locale.isUS;\n\n $scope.statuses = [\n Enums.statuses.all,\n Enums.statuses.pending,\n Enums.statuses.converted,\n Enums.statuses.voided,\n ];\n\n $scope.enumStatuses = Enums.statuses;\n\n var headers = [\n {\n title: ''\n },\n {\n title: 'quote.quote-#',\n sortField: 'quoteNumber'\n },\n {\n title: 'contact.purchase.headers.trx-#',\n sortField: 'convertedTransactionNumber'\n },\n {\n title: 'reports.headers.created-date',\n sortField: 'createdDate',\n active: true\n },\n {\n title: 'common.contact',\n sortField: 'contactName'\n },\n {\n title: 'common.modal.event-date',\n sortField: 'eventDate'\n },\n {\n title: 'transaction.event-contact',\n sortField: 'eventContactName'\n },\n {\n title: 'common.associate',\n sortField: 'employeeName',\n hideOnTablets: true\n },\n {\n title: 'purchase-order.total',\n sortField: 'totalAmount'\n },\n ];\n\n $scope.doIgnoreMemorizeSearch = !!_($stateParams).chain().values().compact().value().length;\n\n $scope.perms = {\n create: security.hasPermission('QUOTES'),\n view: security.hasPermission('VIEW_QUOTES'),\n edit: security.hasPermission('EDIT_QUOTES'),\n delete: security.hasPermission('DELETE_QUOTES'),\n void: security.hasPermission('VOID_QUOTES'),\n duplicate: security.hasPermission('DUPLICATE_QUOTES'),\n convert: security.hasPermission('CONVERT_QUOTES')\n };\n\n angular.extend(this, $controller('SearchCtrl', { $scope: $scope, headers: headers }));\n\n var status = $stateParams.status || Enums.statuses.all.value;\n $scope.initialValues = { eventDateBefore: $stateParams.eventDateBefore, status: status, createdDate: $stateParams.quoteStartDate, createdDateBefore: $stateParams.quoteEndDate};\n $scope.formValues = angular.copy($scope.initialValues);\n $scope.memorizeSearch();\n\n if(employees) {\n employees = ResourcesHelper.adaptList('employees', employees);\n employees.unshift({ fullName: $translate.instant('common.all') });\n }\n\n $scope.employees = employees;\n\n $scope.results = $scope.selectedResults = [];\n $scope.search = function () {\n Quote.query($scope.buildFilterObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.bulkPrint = function () {\n if (!$scope.selectedResults.length) {\n return modalService.showMessage('transaction.message.select-at-least-one', 'common.message.wooops');\n }\n\n Quote.bulkPrint(_($scope.selectedResults).pluck('id'));\n };\n\n // $scope.bulkConvert = function (typeId) {\n // validateThenBulkConvert(typeId, function () {\n // modalService.showConfirmation({\n // key: 'quote.messages.are-you-sure-you-would-like-to-convert-quotes?',\n // params: { number: $scope.selectedResults.length, type: $translate.instant($scope.getTrxType(typeId).description)}\n // }, function () {\n // Quote.bulkConvertToTransaction(User.id, typeId, _($scope.selectedResults).pluck('id'), function (result) {\n // $scope.search();\n // alerts.pushSuccess({\n // key: 'quote.messages.successfully-converted',\n // params: { number: result.nbrRowsAffected }\n // });\n // });\n // });\n // });\n // };\n\n function validateThenBulkConvert(typeId, callback) {\n if(!$scope.perms.convert) {\n return modalService.showMessage('security.permission-denied', 'security.permission-denied');\n }\n\n if(!$scope.selectedResults.length) {\n return modalService.showMessage('transaction.message.select-at-least-one', 'common.message.wooops');\n }\n\n if($scope.selectedResults.length > 10) {\n return modalService.showMessage('quote.messages.too-many-to-convert', 'common.message.wooops');\n }\n\n var statuses = _($scope.selectedResults).pluck('status');\n if (_.contains(statuses, Enums.statuses.converted.value)) {\n return modalService.showMessage('quote.messages.already-converted', 'common.message.wooops');\n }\n\n for(var i = 0; i < $scope.selectedResults.length; i++) {\n var quote = $scope.selectedResults[i];\n\n if(quote && !quote.smartFlowsProcessedDateTime && quote.smartFlowsTriggeredDateTime && security.hasPlanPermission('smartFlows')) {\n modalService.showMessage('transaction.message.process-smart-flow-before-completing', 'transaction.smart-flows');\n return;\n }\n }\n\n Quote.validateBeforeBulkConvertToTrx($scope.selectedResults, typeId, function(result) {\n if(result) {\n if(result.errors && result.errors.length) {\n modalService.showMessage(buildConvertErrors(result.errors), 'modal-service.error');\n } else if(callback) {\n callback();\n }\n }\n });\n }\n\n function buildConvertErrors(errors) {\n var errorMessage = '';\n\n if(errors && errors.length) {\n var heading = $translate.instant('modal-service.please-correct-following-errors');\n\n errorMessage += '
' + heading + ' ';\n _(errors).each(function(error) {\n if(error.errorMessages && error.errorMessages.length) {\n _(error.errorMessages).each(function(message) {\n errorMessage += '' + message + ' ';\n });\n }\n });\n\n errorMessage += ' ';\n }\n\n return errorMessage;\n }\n\n $scope.duplicate = function(quote) {\n if($scope.perms.duplicate) {\n Quote.duplicate(quote, User, function () {\n $scope.search();\n });\n } else {\n modalService.showMessage('security.permission-denied', 'security.permission-denied');\n }\n };\n\n $scope.createQuote = function() {\n if($scope.perms && $scope.perms.create) {\n $state.go('quote.create');\n }\n };\n\n $scope.delete = function(quote) {\n if($scope.perms.delete) {\n modalService.showConfirmation('quote.messages.would-like-to-delete-quote?', function () {\n quote.$remove(function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'quote.messages.successfully-deleted',\n params: { number: 1 }\n });\n });\n });\n } else {\n modalService.showMessage('security.permission-denied', 'security.permission-denied');\n }\n };\n\n $scope.bulkDelete = function() {\n var selectedItems = _($scope.results).where({ checked: true });\n\n if(!selectedItems.length) {\n return modalService.showMessage('quote.messages.select-one-quote-to-delete');\n }\n\n if($scope.selectedResults.length > 100) {\n return modalService.showMessage('quote.messages.too-many-to-delete', 'common.message.wooops');\n }\n\n modalService.showConfirmation({ key: 'quote.messages.are-you-sure-you-would-like-to-delete-quotes?', params: { number: $scope.selectedResults.length }}, function () {\n Quote.bulkDelete({ ids: _(selectedItems).pluck('id'), contactIds: _(selectedItems).pluck('contactId') }, function (result) {\n $scope.search();\n alerts.pushSuccess({\n key: 'quote.messages.successfully-deleted',\n params: { number: result.nbrRowsAffected }\n });\n });\n });\n };\n\n $scope.bulkVoid = function() {\n var selectedItems = _($scope.results).where({ checked: true });\n\n if(!selectedItems.length) {\n return modalService.showMessage('quote.messages.select-one-quote-to-void');\n }\n\n if($scope.selectedResults.length > 100) {\n return modalService.showMessage('quote.messages.too-many-to-void', 'common.message.wooops');\n }\n\n modalService.showConfirmation({ key: 'quote.messages.are-you-sure-you-would-like-to-void-quotes?', params: { number: $scope.selectedResults.length }}, function () {\n Quote.bulkVoid({ ids: _(selectedItems).pluck('id'), contactIds: _(selectedItems).pluck('contactId') }, function (result) {\n $scope.search();\n alerts.pushSuccess({\n key: 'quote.messages.successfully-voided',\n params: { number: result.nbrRowsAffected }\n });\n });\n });\n };\n\n $scope.changeEmployee = function (emp) {\n if(!$scope.selectedResults.length) {\n modalService.showMessage('quote.messages.select-at-least-one-quote', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'quote.messages.successfully-assigned',\n params: {\n number: $scope.selectedResults.length,\n firstName: emp.firstName,\n lastName: emp.lastName\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'quote.messages.are-you-sure-you-would-like-to-assign?',\n params: {\n number: $scope.selectedResults.length,\n firstName: emp.firstName,\n lastName: emp.lastName\n }\n }, function () {\n Quote.bulkUpdateEmployee(emp.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.exportToExcel = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('transaction.message.select-at-least-one', 'common.message.wooops');\n return false;\n }\n\n modalService.showConfirmation({\n key: 'transaction.message.would-like-export-to-excel?',\n params: { number: $scope.selectedResults.length }\n }, function () {\n Quote.exportToExcel(_($scope.selectedResults).pluck('id'));\n });\n };\n\n $scope.gotoQuote = function (quote) {\n $state.go('quote.edit', { id: quote.id });\n };\n\n $scope.chooseEventContact = function(eventContact) {\n $scope.eventContact = eventContact;\n $scope.formValues.eventContactId = eventContact.id;\n $scope.formValues.eventContactName = eventContact.firstName + ' ' + eventContact.lastName;\n $scope.eventContactName = $scope.formValues.eventContactName;\n };\n\n $scope.unselectEventContact = function() {\n $scope.eventContact = null;\n $scope.formValues.eventContactId = null;\n $scope.formValues.eventContactName = null;\n $scope.eventContactName = null;\n };\n\n $scope.buildStatusTooltip = function(quote) {\n if(quote.status === $scope.enumStatuses.pending.value) {\n return $translate.instant($scope.enumStatuses.pending.description);\n } else if(quote.status === $scope.enumStatuses.converted.value && quote.convertedDate) {\n return $filter('date')(quote.convertedDate, 'mediumDate');\n }\n\n return '';\n };\n\n $scope.goToTransaction = function(typeId, trxId) {\n $state.go(getTrxRoute(typeId), { id: trxId });\n };\n\n $scope.getTrxType = function(trxTypeId) {\n return _.findWhere(Enums.trxTypes, { value: trxTypeId });\n };\n\n function getTrxRoute(typeId) {\n var trxRoutes = {\n 1: 'transaction.sale',\n 2: 'transaction.specialOrder',\n 3: 'transaction.return',\n 4: 'transaction.layaway'\n };\n\n return trxRoutes[typeId];\n }\n\n /**\n * Init\n */\n\n $timeout(function () {\n if($scope.formValues.eventContactId) {\n Contact.getById($scope.formValues.eventContactId, $scope.chooseEventContact);\n }\n });\n });\n});\n\n","define('modules/reports/common-ctrl',['angular', 'underscore'], function (angular, _) {\n 'use strict';\n /**\n * This common controller contains all of the common functions\n * for running reports.\n */\n return angular.module('bl.reports.common', [])\n .controller('CommonReportsCtrl', function ($scope, $translate, headers, security, AppLicense) {\n $scope.security = security;\n $scope.sortHeaders = headers;\n $scope.colspan = _(headers).filter(function(header) {\n return !header.isHidden;\n }).length;\n\n var license = AppLicense.get();\n var multiLocations = [];\n if(license && license.linkedAccounts && license.linkedAccounts.length && license.plan && license.plan.multiStore) {\n multiLocations = angular.copy(license.linkedAccounts);\n }\n\n var isAllPerPage = function () {\n return $scope.pagination.perPage === 'All';\n };\n\n var isAllLocations = function () {\n return $scope.formValues.retailerId === 'ALL_LOCATIONS';\n };\n\n $scope.buildReportsObject = function () {\n var reportsObject = {\n criteria: $scope.formValues,\n page: $scope.pagination.page,\n size: isAllPerPage() ? '' : $scope.pagination.perPage,\n sortField: $scope.pagination.sortField,\n sortDirection: $scope.pagination.sortDirection\n };\n\n if($scope.formValues.hasOwnProperty(\"selectedDepartments\")) {\n reportsObject.criteria.departmentIds = _($scope.formValues.selectedDepartments).pluck(\"id\");\n }\n\n if($scope.formValues.hasOwnProperty(\"selectedVendors\")) {\n reportsObject.criteria.vendorIds = _($scope.formValues.selectedVendors).pluck(\"id\");\n }\n\n if($scope.formValues.hasOwnProperty(\"selectedMethods\")) {\n reportsObject.criteria.methodIds = _($scope.formValues.selectedMethods).pluck(\"id\");\n }\n\n if($scope.formValues.hasOwnProperty(\"selectedAttributes\")) {\n reportsObject.criteria.attributeIds = _($scope.formValues.selectedAttributes).chain().pluck(\"attributeIds\").flatten().value();\n }\n\n if($scope.formValues.hasOwnProperty(\"appointmentTypes\")) {\n reportsObject.criteria.appointmentTypeIds = _($scope.formValues.appointmentTypes).pluck(\"id\");\n }\n\n return reportsObject;\n };\n\n function setSortProperties(header) {\n if (!header.sortField) {\n return false;\n }\n\n if ($scope.pagination.sortField === header.sortField) {\n $scope.pagination.sortDirection = $scope.pagination.sortDirection === 'asc' ? 'desc' : 'asc';\n }\n\n $scope.pagination.sortField = header.sortField;\n\n if ($scope.activeHeader) {\n $scope.activeHeader.active = false;\n }\n\n header.active = true;\n $scope.activeHeader = header;\n }\n\n // pagination settings wrapper\n $scope.pagination = {\n page: 1,\n perPage: 25,\n perPageOptions: [\n { size: 10, label: 10 },\n { size: 25, label: 25 },\n { size: 50, label: 50 },\n { size: 100, label: 100 },\n { size: '', label: $translate.instant('common.all') }\n ],\n totalItems: 0,\n sortField: $scope.sortHeaders[0].sortField,\n sortDirection: 'desc'\n };\n\n $scope.activeHeader = $scope.sortHeaders[0];\n setSortProperties($scope.activeHeader);\n\n $scope.sort = function (header) {\n setSortProperties(header);\n $scope.newSearch();\n $scope.runReport();\n };\n\n /**\n * Returns user back to first page and fires runReport\n */\n $scope.newSearch = function () {\n $scope.pagination.page = 1;\n };\n\n /**\n * Goes 1 page forward\n * @param $event\n */\n $scope.nextPage = function ($event) {\n if ($event) {\n $event.preventDefault();\n }\n\n if ($scope.pagination.nextDisabled) {\n return;\n }\n\n $scope.pagination.page += 1;\n $scope.selectAll = false;\n $scope.runReport();\n };\n\n /**\n * Goes 1 page backward\n * @param $event\n */\n $scope.previousPage = function ($event) {\n if ($event) {\n $event.preventDefault();\n }\n\n if ($scope.pagination.previousDisabled) {\n return;\n }\n\n if ($scope.pagination.page > 0) {\n $scope.pagination.page -= 1;\n }\n $scope.selectAll = false;\n $scope.runReport();\n };\n\n $scope.reset = function () {\n $scope.formValues = $scope.initialValues;\n };\n\n $scope.syncSelectedResults = function (task) {\n var checkedTaskWasSelected = _($scope.selectedResults).findWhere({ id: task.id });\n if (checkedTaskWasSelected) {\n $scope.selectedResults = _($scope.selectedResults).without(checkedTaskWasSelected);\n } else {\n $scope.selectedResults.push(task);\n }\n };\n\n $scope.selectAll = false;\n $scope.toggleSelectAll = function (removeAll) {\n $scope.selectAll = !$scope.selectAll;\n if ($scope.selectAll) {\n $scope.selectedResults = _.union($scope.selectedResults, $scope.results);\n } else {\n $scope.selectedResults = removeAll ? [] : _($scope.selectedResults).difference($scope.results);\n }\n\n //toggle all contacts\n $scope.results = _($scope.results).map(function (task) {\n task.checked = $scope.selectAll;\n return task;\n });\n };\n\n $scope.uncheckSelectedResults = function () {\n $scope.selectAll = true;\n $scope.toggleSelectAll(true);\n };\n\n $scope.updateResults = function (resultModel) {\n populateRetailerLocation(resultModel);\n $scope.results = resultModel.result;\n $scope.pagination.totalItems = resultModel.total;\n $scope.pagination.page = resultModel.page;\n $scope.pagination.totalAfterFilter = resultModel.totalAfterFilter;\n $scope.pagination.previousDisabled = ($scope.pagination.page === 1);\n $scope.pagination.nextDisabled = (isAllPerPage() || $scope.pagination.totalItems <= $scope.pagination.page * $scope.pagination.perPage);\n\n if(isAllPerPage()) {\n $scope.pagination.showingFrom = 1;\n $scope.pagination.showingTo = $scope.pagination.totalItems;\n } else {\n $scope.pagination.showingFrom = ($scope.pagination.page - 1) * $scope.pagination.perPage + 1;\n $scope.pagination.showingTo = Math.min($scope.pagination.page * $scope.pagination.perPage, $scope.pagination.totalItems);\n }\n\n // Check all previously selected contacts\n if(($scope.selectedResults && $scope.selectedResults.length) || ($scope.results && $scope.results.length)) {\n $scope.results = _($scope.results).map(function (task) {\n var foundTask = _($scope.selectedResults).findWhere({ id: task.id });\n if (foundTask && foundTask.checked) {\n task.checked = true;\n }\n return task;\n });\n }\n\n if($scope.drawGraph) {\n $scope.drawGraph($scope.results);\n }\n\n // calculates permissions to conditionally hide/show layout items in results table\n // is done in a function to\n $scope.permissions = {\n ITEM_COST: security.hasPermission('ITEM_COST')\n };\n };\n\n $scope.initializeResults = function() {\n $scope.results = [];\n $scope.pagination.totalItems = 0;\n $scope.pagination.page = 1;\n $scope.pagination.previousDisabled = true;\n $scope.pagination.nextDisabled = true;\n $scope.pagination.showingFrom = 1;\n $scope.pagination.showingTo = 1;\n };\n\n /**\n * Populates location name if needed\n * @param resultModel\n */\n function populateRetailerLocation(resultModel) {\n if(isAllLocations() && multiLocations && multiLocations.length && resultModel.criteria.retailerIds && resultModel.criteria.retailerIds.length) {\n var retailerMap = {};\n _.each(resultModel.result, function(result) {\n if(result.retailerId) {\n if(!retailerMap.hasOwnProperty(result.retailerId)) {\n retailerMap[result.retailerId] = getCompanyNickName(result.retailerId, multiLocations);\n }\n\n result.location = retailerMap[result.retailerId];\n result.locationRetailerId = result.retailerId;\n }\n });\n }\n }\n\n function getCompanyNickName(retailerId, locations) {\n var location = _(locations).findWhere({ linkedRetailerId: retailerId });\n return location && location.nickName;\n }\n\n // Listen deep changes of formValues\n $scope.$watch('formValues', _.throttle($scope.newSearch, 500), true);\n });\n});\n\n","/*!\n * Chart.js\n * http://chartjs.org/\n * Version: 2.5.0\n *\n * Copyright 2017 Nick Downie\n * Released under the MIT license\n * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md\n */\n(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define('chart',[],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o
lum2) {\n\t\t\treturn (lum1 + 0.05) / (lum2 + 0.05);\n\t\t}\n\t\treturn (lum2 + 0.05) / (lum1 + 0.05);\n\t},\n\n\tlevel: function (color2) {\n\t\tvar contrastRatio = this.contrast(color2);\n\t\tif (contrastRatio >= 7.1) {\n\t\t\treturn 'AAA';\n\t\t}\n\n\t\treturn (contrastRatio >= 4.5) ? 'AA' : '';\n\t},\n\n\tdark: function () {\n\t\t// YIQ equation from http://24ways.org/2010/calculating-color-contrast\n\t\tvar rgb = this.values.rgb;\n\t\tvar yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;\n\t\treturn yiq < 128;\n\t},\n\n\tlight: function () {\n\t\treturn !this.dark();\n\t},\n\n\tnegate: function () {\n\t\tvar rgb = [];\n\t\tfor (var i = 0; i < 3; i++) {\n\t\t\trgb[i] = 255 - this.values.rgb[i];\n\t\t}\n\t\tthis.setValues('rgb', rgb);\n\t\treturn this;\n\t},\n\n\tlighten: function (ratio) {\n\t\tvar hsl = this.values.hsl;\n\t\thsl[2] += hsl[2] * ratio;\n\t\tthis.setValues('hsl', hsl);\n\t\treturn this;\n\t},\n\n\tdarken: function (ratio) {\n\t\tvar hsl = this.values.hsl;\n\t\thsl[2] -= hsl[2] * ratio;\n\t\tthis.setValues('hsl', hsl);\n\t\treturn this;\n\t},\n\n\tsaturate: function (ratio) {\n\t\tvar hsl = this.values.hsl;\n\t\thsl[1] += hsl[1] * ratio;\n\t\tthis.setValues('hsl', hsl);\n\t\treturn this;\n\t},\n\n\tdesaturate: function (ratio) {\n\t\tvar hsl = this.values.hsl;\n\t\thsl[1] -= hsl[1] * ratio;\n\t\tthis.setValues('hsl', hsl);\n\t\treturn this;\n\t},\n\n\twhiten: function (ratio) {\n\t\tvar hwb = this.values.hwb;\n\t\thwb[1] += hwb[1] * ratio;\n\t\tthis.setValues('hwb', hwb);\n\t\treturn this;\n\t},\n\n\tblacken: function (ratio) {\n\t\tvar hwb = this.values.hwb;\n\t\thwb[2] += hwb[2] * ratio;\n\t\tthis.setValues('hwb', hwb);\n\t\treturn this;\n\t},\n\n\tgreyscale: function () {\n\t\tvar rgb = this.values.rgb;\n\t\t// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale\n\t\tvar val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;\n\t\tthis.setValues('rgb', [val, val, val]);\n\t\treturn this;\n\t},\n\n\tclearer: function (ratio) {\n\t\tvar alpha = this.values.alpha;\n\t\tthis.setValues('alpha', alpha - (alpha * ratio));\n\t\treturn this;\n\t},\n\n\topaquer: function (ratio) {\n\t\tvar alpha = this.values.alpha;\n\t\tthis.setValues('alpha', alpha + (alpha * ratio));\n\t\treturn this;\n\t},\n\n\trotate: function (degrees) {\n\t\tvar hsl = this.values.hsl;\n\t\tvar hue = (hsl[0] + degrees) % 360;\n\t\thsl[0] = hue < 0 ? 360 + hue : hue;\n\t\tthis.setValues('hsl', hsl);\n\t\treturn this;\n\t},\n\n\t/**\n\t * Ported from sass implementation in C\n\t * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209\n\t */\n\tmix: function (mixinColor, weight) {\n\t\tvar color1 = this;\n\t\tvar color2 = mixinColor;\n\t\tvar p = weight === undefined ? 0.5 : weight;\n\n\t\tvar w = 2 * p - 1;\n\t\tvar a = color1.alpha() - color2.alpha();\n\n\t\tvar w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;\n\t\tvar w2 = 1 - w1;\n\n\t\treturn this\n\t\t\t.rgb(\n\t\t\t\tw1 * color1.red() + w2 * color2.red(),\n\t\t\t\tw1 * color1.green() + w2 * color2.green(),\n\t\t\t\tw1 * color1.blue() + w2 * color2.blue()\n\t\t\t)\n\t\t\t.alpha(color1.alpha() * p + color2.alpha() * (1 - p));\n\t},\n\n\ttoJSON: function () {\n\t\treturn this.rgb();\n\t},\n\n\tclone: function () {\n\t\t// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,\n\t\t// making the final build way to big to embed in Chart.js. So let's do it manually,\n\t\t// assuming that values to clone are 1 dimension arrays containing only numbers,\n\t\t// except 'alpha' which is a number.\n\t\tvar result = new Color();\n\t\tvar source = this.values;\n\t\tvar target = result.values;\n\t\tvar value, type;\n\n\t\tfor (var prop in source) {\n\t\t\tif (source.hasOwnProperty(prop)) {\n\t\t\t\tvalue = source[prop];\n\t\t\t\ttype = ({}).toString.call(value);\n\t\t\t\tif (type === '[object Array]') {\n\t\t\t\t\ttarget[prop] = value.slice(0);\n\t\t\t\t} else if (type === '[object Number]') {\n\t\t\t\t\ttarget[prop] = value;\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error('unexpected color value:', value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n};\n\nColor.prototype.spaces = {\n\trgb: ['red', 'green', 'blue'],\n\thsl: ['hue', 'saturation', 'lightness'],\n\thsv: ['hue', 'saturation', 'value'],\n\thwb: ['hue', 'whiteness', 'blackness'],\n\tcmyk: ['cyan', 'magenta', 'yellow', 'black']\n};\n\nColor.prototype.maxes = {\n\trgb: [255, 255, 255],\n\thsl: [360, 100, 100],\n\thsv: [360, 100, 100],\n\thwb: [360, 100, 100],\n\tcmyk: [100, 100, 100, 100]\n};\n\nColor.prototype.getValues = function (space) {\n\tvar values = this.values;\n\tvar vals = {};\n\n\tfor (var i = 0; i < space.length; i++) {\n\t\tvals[space.charAt(i)] = values[space][i];\n\t}\n\n\tif (values.alpha !== 1) {\n\t\tvals.a = values.alpha;\n\t}\n\n\t// {r: 255, g: 255, b: 255, a: 0.4}\n\treturn vals;\n};\n\nColor.prototype.setValues = function (space, vals) {\n\tvar values = this.values;\n\tvar spaces = this.spaces;\n\tvar maxes = this.maxes;\n\tvar alpha = 1;\n\tvar i;\n\n\tif (space === 'alpha') {\n\t\talpha = vals;\n\t} else if (vals.length) {\n\t\t// [10, 10, 10]\n\t\tvalues[space] = vals.slice(0, space.length);\n\t\talpha = vals[space.length];\n\t} else if (vals[space.charAt(0)] !== undefined) {\n\t\t// {r: 10, g: 10, b: 10}\n\t\tfor (i = 0; i < space.length; i++) {\n\t\t\tvalues[space][i] = vals[space.charAt(i)];\n\t\t}\n\n\t\talpha = vals.a;\n\t} else if (vals[spaces[space][0]] !== undefined) {\n\t\t// {red: 10, green: 10, blue: 10}\n\t\tvar chans = spaces[space];\n\n\t\tfor (i = 0; i < space.length; i++) {\n\t\t\tvalues[space][i] = vals[chans[i]];\n\t\t}\n\n\t\talpha = vals.alpha;\n\t}\n\n\tvalues.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));\n\n\tif (space === 'alpha') {\n\t\treturn false;\n\t}\n\n\tvar capped;\n\n\t// cap values of the space prior converting all values\n\tfor (i = 0; i < space.length; i++) {\n\t\tcapped = Math.max(0, Math.min(maxes[space][i], values[space][i]));\n\t\tvalues[space][i] = Math.round(capped);\n\t}\n\n\t// convert to all the other color spaces\n\tfor (var sname in spaces) {\n\t\tif (sname !== space) {\n\t\t\tvalues[sname] = convert[space][sname](values[space]);\n\t\t}\n\t}\n\n\treturn true;\n};\n\nColor.prototype.setSpace = function (space, args) {\n\tvar vals = args[0];\n\n\tif (vals === undefined) {\n\t\t// color.rgb()\n\t\treturn this.getValues(space);\n\t}\n\n\t// color.rgb(10, 10, 10)\n\tif (typeof vals === 'number') {\n\t\tvals = Array.prototype.slice.call(args);\n\t}\n\n\tthis.setValues(space, vals);\n\treturn this;\n};\n\nColor.prototype.setChannel = function (space, index, val) {\n\tvar svalues = this.values[space];\n\tif (val === undefined) {\n\t\t// color.red()\n\t\treturn svalues[index];\n\t} else if (val === svalues[index]) {\n\t\t// color.red(color.red())\n\t\treturn this;\n\t}\n\n\t// color.red(100)\n\tsvalues[index] = val;\n\tthis.setValues(space, svalues);\n\n\treturn this;\n};\n\nif (typeof window !== 'undefined') {\n\twindow.Color = Color;\n}\n\nmodule.exports = Color;\n\n},{\"2\":2,\"5\":5}],4:[function(require,module,exports){\n/* MIT license */\n\nmodule.exports = {\n rgb2hsl: rgb2hsl,\n rgb2hsv: rgb2hsv,\n rgb2hwb: rgb2hwb,\n rgb2cmyk: rgb2cmyk,\n rgb2keyword: rgb2keyword,\n rgb2xyz: rgb2xyz,\n rgb2lab: rgb2lab,\n rgb2lch: rgb2lch,\n\n hsl2rgb: hsl2rgb,\n hsl2hsv: hsl2hsv,\n hsl2hwb: hsl2hwb,\n hsl2cmyk: hsl2cmyk,\n hsl2keyword: hsl2keyword,\n\n hsv2rgb: hsv2rgb,\n hsv2hsl: hsv2hsl,\n hsv2hwb: hsv2hwb,\n hsv2cmyk: hsv2cmyk,\n hsv2keyword: hsv2keyword,\n\n hwb2rgb: hwb2rgb,\n hwb2hsl: hwb2hsl,\n hwb2hsv: hwb2hsv,\n hwb2cmyk: hwb2cmyk,\n hwb2keyword: hwb2keyword,\n\n cmyk2rgb: cmyk2rgb,\n cmyk2hsl: cmyk2hsl,\n cmyk2hsv: cmyk2hsv,\n cmyk2hwb: cmyk2hwb,\n cmyk2keyword: cmyk2keyword,\n\n keyword2rgb: keyword2rgb,\n keyword2hsl: keyword2hsl,\n keyword2hsv: keyword2hsv,\n keyword2hwb: keyword2hwb,\n keyword2cmyk: keyword2cmyk,\n keyword2lab: keyword2lab,\n keyword2xyz: keyword2xyz,\n\n xyz2rgb: xyz2rgb,\n xyz2lab: xyz2lab,\n xyz2lch: xyz2lch,\n\n lab2xyz: lab2xyz,\n lab2rgb: lab2rgb,\n lab2lch: lab2lch,\n\n lch2lab: lch2lab,\n lch2xyz: lch2xyz,\n lch2rgb: lch2rgb\n}\n\n\nfunction rgb2hsl(rgb) {\n var r = rgb[0]/255,\n g = rgb[1]/255,\n b = rgb[2]/255,\n min = Math.min(r, g, b),\n max = Math.max(r, g, b),\n delta = max - min,\n h, s, l;\n\n if (max == min)\n h = 0;\n else if (r == max)\n h = (g - b) / delta;\n else if (g == max)\n h = 2 + (b - r) / delta;\n else if (b == max)\n h = 4 + (r - g)/ delta;\n\n h = Math.min(h * 60, 360);\n\n if (h < 0)\n h += 360;\n\n l = (min + max) / 2;\n\n if (max == min)\n s = 0;\n else if (l <= 0.5)\n s = delta / (max + min);\n else\n s = delta / (2 - max - min);\n\n return [h, s * 100, l * 100];\n}\n\nfunction rgb2hsv(rgb) {\n var r = rgb[0],\n g = rgb[1],\n b = rgb[2],\n min = Math.min(r, g, b),\n max = Math.max(r, g, b),\n delta = max - min,\n h, s, v;\n\n if (max == 0)\n s = 0;\n else\n s = (delta/max * 1000)/10;\n\n if (max == min)\n h = 0;\n else if (r == max)\n h = (g - b) / delta;\n else if (g == max)\n h = 2 + (b - r) / delta;\n else if (b == max)\n h = 4 + (r - g) / delta;\n\n h = Math.min(h * 60, 360);\n\n if (h < 0)\n h += 360;\n\n v = ((max / 255) * 1000) / 10;\n\n return [h, s, v];\n}\n\nfunction rgb2hwb(rgb) {\n var r = rgb[0],\n g = rgb[1],\n b = rgb[2],\n h = rgb2hsl(rgb)[0],\n w = 1/255 * Math.min(r, Math.min(g, b)),\n b = 1 - 1/255 * Math.max(r, Math.max(g, b));\n\n return [h, w * 100, b * 100];\n}\n\nfunction rgb2cmyk(rgb) {\n var r = rgb[0] / 255,\n g = rgb[1] / 255,\n b = rgb[2] / 255,\n c, m, y, k;\n\n k = Math.min(1 - r, 1 - g, 1 - b);\n c = (1 - r - k) / (1 - k) || 0;\n m = (1 - g - k) / (1 - k) || 0;\n y = (1 - b - k) / (1 - k) || 0;\n return [c * 100, m * 100, y * 100, k * 100];\n}\n\nfunction rgb2keyword(rgb) {\n return reverseKeywords[JSON.stringify(rgb)];\n}\n\nfunction rgb2xyz(rgb) {\n var r = rgb[0] / 255,\n g = rgb[1] / 255,\n b = rgb[2] / 255;\n\n // assume sRGB\n r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);\n g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);\n b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);\n\n var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);\n var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);\n var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);\n\n return [x * 100, y *100, z * 100];\n}\n\nfunction rgb2lab(rgb) {\n var xyz = rgb2xyz(rgb),\n x = xyz[0],\n y = xyz[1],\n z = xyz[2],\n l, a, b;\n\n x /= 95.047;\n y /= 100;\n z /= 108.883;\n\n x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);\n y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);\n z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);\n\n l = (116 * y) - 16;\n a = 500 * (x - y);\n b = 200 * (y - z);\n\n return [l, a, b];\n}\n\nfunction rgb2lch(args) {\n return lab2lch(rgb2lab(args));\n}\n\nfunction hsl2rgb(hsl) {\n var h = hsl[0] / 360,\n s = hsl[1] / 100,\n l = hsl[2] / 100,\n t1, t2, t3, rgb, val;\n\n if (s == 0) {\n val = l * 255;\n return [val, val, val];\n }\n\n if (l < 0.5)\n t2 = l * (1 + s);\n else\n t2 = l + s - l * s;\n t1 = 2 * l - t2;\n\n rgb = [0, 0, 0];\n for (var i = 0; i < 3; i++) {\n t3 = h + 1 / 3 * - (i - 1);\n t3 < 0 && t3++;\n t3 > 1 && t3--;\n\n if (6 * t3 < 1)\n val = t1 + (t2 - t1) * 6 * t3;\n else if (2 * t3 < 1)\n val = t2;\n else if (3 * t3 < 2)\n val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;\n else\n val = t1;\n\n rgb[i] = val * 255;\n }\n\n return rgb;\n}\n\nfunction hsl2hsv(hsl) {\n var h = hsl[0],\n s = hsl[1] / 100,\n l = hsl[2] / 100,\n sv, v;\n\n if(l === 0) {\n // no need to do calc on black\n // also avoids divide by 0 error\n return [0, 0, 0];\n }\n\n l *= 2;\n s *= (l <= 1) ? l : 2 - l;\n v = (l + s) / 2;\n sv = (2 * s) / (l + s);\n return [h, sv * 100, v * 100];\n}\n\nfunction hsl2hwb(args) {\n return rgb2hwb(hsl2rgb(args));\n}\n\nfunction hsl2cmyk(args) {\n return rgb2cmyk(hsl2rgb(args));\n}\n\nfunction hsl2keyword(args) {\n return rgb2keyword(hsl2rgb(args));\n}\n\n\nfunction hsv2rgb(hsv) {\n var h = hsv[0] / 60,\n s = hsv[1] / 100,\n v = hsv[2] / 100,\n hi = Math.floor(h) % 6;\n\n var f = h - Math.floor(h),\n p = 255 * v * (1 - s),\n q = 255 * v * (1 - (s * f)),\n t = 255 * v * (1 - (s * (1 - f))),\n v = 255 * v;\n\n switch(hi) {\n case 0:\n return [v, t, p];\n case 1:\n return [q, v, p];\n case 2:\n return [p, v, t];\n case 3:\n return [p, q, v];\n case 4:\n return [t, p, v];\n case 5:\n return [v, p, q];\n }\n}\n\nfunction hsv2hsl(hsv) {\n var h = hsv[0],\n s = hsv[1] / 100,\n v = hsv[2] / 100,\n sl, l;\n\n l = (2 - s) * v;\n sl = s * v;\n sl /= (l <= 1) ? l : 2 - l;\n sl = sl || 0;\n l /= 2;\n return [h, sl * 100, l * 100];\n}\n\nfunction hsv2hwb(args) {\n return rgb2hwb(hsv2rgb(args))\n}\n\nfunction hsv2cmyk(args) {\n return rgb2cmyk(hsv2rgb(args));\n}\n\nfunction hsv2keyword(args) {\n return rgb2keyword(hsv2rgb(args));\n}\n\n// http://dev.w3.org/csswg/css-color/#hwb-to-rgb\nfunction hwb2rgb(hwb) {\n var h = hwb[0] / 360,\n wh = hwb[1] / 100,\n bl = hwb[2] / 100,\n ratio = wh + bl,\n i, v, f, n;\n\n // wh + bl cant be > 1\n if (ratio > 1) {\n wh /= ratio;\n bl /= ratio;\n }\n\n i = Math.floor(6 * h);\n v = 1 - bl;\n f = 6 * h - i;\n if ((i & 0x01) != 0) {\n f = 1 - f;\n }\n n = wh + f * (v - wh); // linear interpolation\n\n switch (i) {\n default:\n case 6:\n case 0: r = v; g = n; b = wh; break;\n case 1: r = n; g = v; b = wh; break;\n case 2: r = wh; g = v; b = n; break;\n case 3: r = wh; g = n; b = v; break;\n case 4: r = n; g = wh; b = v; break;\n case 5: r = v; g = wh; b = n; break;\n }\n\n return [r * 255, g * 255, b * 255];\n}\n\nfunction hwb2hsl(args) {\n return rgb2hsl(hwb2rgb(args));\n}\n\nfunction hwb2hsv(args) {\n return rgb2hsv(hwb2rgb(args));\n}\n\nfunction hwb2cmyk(args) {\n return rgb2cmyk(hwb2rgb(args));\n}\n\nfunction hwb2keyword(args) {\n return rgb2keyword(hwb2rgb(args));\n}\n\nfunction cmyk2rgb(cmyk) {\n var c = cmyk[0] / 100,\n m = cmyk[1] / 100,\n y = cmyk[2] / 100,\n k = cmyk[3] / 100,\n r, g, b;\n\n r = 1 - Math.min(1, c * (1 - k) + k);\n g = 1 - Math.min(1, m * (1 - k) + k);\n b = 1 - Math.min(1, y * (1 - k) + k);\n return [r * 255, g * 255, b * 255];\n}\n\nfunction cmyk2hsl(args) {\n return rgb2hsl(cmyk2rgb(args));\n}\n\nfunction cmyk2hsv(args) {\n return rgb2hsv(cmyk2rgb(args));\n}\n\nfunction cmyk2hwb(args) {\n return rgb2hwb(cmyk2rgb(args));\n}\n\nfunction cmyk2keyword(args) {\n return rgb2keyword(cmyk2rgb(args));\n}\n\n\nfunction xyz2rgb(xyz) {\n var x = xyz[0] / 100,\n y = xyz[1] / 100,\n z = xyz[2] / 100,\n r, g, b;\n\n r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);\n g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);\n b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);\n\n // assume sRGB\n r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)\n : r = (r * 12.92);\n\n g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)\n : g = (g * 12.92);\n\n b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)\n : b = (b * 12.92);\n\n r = Math.min(Math.max(0, r), 1);\n g = Math.min(Math.max(0, g), 1);\n b = Math.min(Math.max(0, b), 1);\n\n return [r * 255, g * 255, b * 255];\n}\n\nfunction xyz2lab(xyz) {\n var x = xyz[0],\n y = xyz[1],\n z = xyz[2],\n l, a, b;\n\n x /= 95.047;\n y /= 100;\n z /= 108.883;\n\n x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);\n y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);\n z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);\n\n l = (116 * y) - 16;\n a = 500 * (x - y);\n b = 200 * (y - z);\n\n return [l, a, b];\n}\n\nfunction xyz2lch(args) {\n return lab2lch(xyz2lab(args));\n}\n\nfunction lab2xyz(lab) {\n var l = lab[0],\n a = lab[1],\n b = lab[2],\n x, y, z, y2;\n\n if (l <= 8) {\n y = (l * 100) / 903.3;\n y2 = (7.787 * (y / 100)) + (16 / 116);\n } else {\n y = 100 * Math.pow((l + 16) / 116, 3);\n y2 = Math.pow(y / 100, 1/3);\n }\n\n x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);\n\n z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);\n\n return [x, y, z];\n}\n\nfunction lab2lch(lab) {\n var l = lab[0],\n a = lab[1],\n b = lab[2],\n hr, h, c;\n\n hr = Math.atan2(b, a);\n h = hr * 360 / 2 / Math.PI;\n if (h < 0) {\n h += 360;\n }\n c = Math.sqrt(a * a + b * b);\n return [l, c, h];\n}\n\nfunction lab2rgb(args) {\n return xyz2rgb(lab2xyz(args));\n}\n\nfunction lch2lab(lch) {\n var l = lch[0],\n c = lch[1],\n h = lch[2],\n a, b, hr;\n\n hr = h / 360 * 2 * Math.PI;\n a = c * Math.cos(hr);\n b = c * Math.sin(hr);\n return [l, a, b];\n}\n\nfunction lch2xyz(args) {\n return lab2xyz(lch2lab(args));\n}\n\nfunction lch2rgb(args) {\n return lab2rgb(lch2lab(args));\n}\n\nfunction keyword2rgb(keyword) {\n return cssKeywords[keyword];\n}\n\nfunction keyword2hsl(args) {\n return rgb2hsl(keyword2rgb(args));\n}\n\nfunction keyword2hsv(args) {\n return rgb2hsv(keyword2rgb(args));\n}\n\nfunction keyword2hwb(args) {\n return rgb2hwb(keyword2rgb(args));\n}\n\nfunction keyword2cmyk(args) {\n return rgb2cmyk(keyword2rgb(args));\n}\n\nfunction keyword2lab(args) {\n return rgb2lab(keyword2rgb(args));\n}\n\nfunction keyword2xyz(args) {\n return rgb2xyz(keyword2rgb(args));\n}\n\nvar cssKeywords = {\n aliceblue: [240,248,255],\n antiquewhite: [250,235,215],\n aqua: [0,255,255],\n aquamarine: [127,255,212],\n azure: [240,255,255],\n beige: [245,245,220],\n bisque: [255,228,196],\n black: [0,0,0],\n blanchedalmond: [255,235,205],\n blue: [0,0,255],\n blueviolet: [138,43,226],\n brown: [165,42,42],\n burlywood: [222,184,135],\n cadetblue: [95,158,160],\n chartreuse: [127,255,0],\n chocolate: [210,105,30],\n coral: [255,127,80],\n cornflowerblue: [100,149,237],\n cornsilk: [255,248,220],\n crimson: [220,20,60],\n cyan: [0,255,255],\n darkblue: [0,0,139],\n darkcyan: [0,139,139],\n darkgoldenrod: [184,134,11],\n darkgray: [169,169,169],\n darkgreen: [0,100,0],\n darkgrey: [169,169,169],\n darkkhaki: [189,183,107],\n darkmagenta: [139,0,139],\n darkolivegreen: [85,107,47],\n darkorange: [255,140,0],\n darkorchid: [153,50,204],\n darkred: [139,0,0],\n darksalmon: [233,150,122],\n darkseagreen: [143,188,143],\n darkslateblue: [72,61,139],\n darkslategray: [47,79,79],\n darkslategrey: [47,79,79],\n darkturquoise: [0,206,209],\n darkviolet: [148,0,211],\n deeppink: [255,20,147],\n deepskyblue: [0,191,255],\n dimgray: [105,105,105],\n dimgrey: [105,105,105],\n dodgerblue: [30,144,255],\n firebrick: [178,34,34],\n floralwhite: [255,250,240],\n forestgreen: [34,139,34],\n fuchsia: [255,0,255],\n gainsboro: [220,220,220],\n ghostwhite: [248,248,255],\n gold: [255,215,0],\n goldenrod: [218,165,32],\n gray: [128,128,128],\n green: [0,128,0],\n greenyellow: [173,255,47],\n grey: [128,128,128],\n honeydew: [240,255,240],\n hotpink: [255,105,180],\n indianred: [205,92,92],\n indigo: [75,0,130],\n ivory: [255,255,240],\n khaki: [240,230,140],\n lavender: [230,230,250],\n lavenderblush: [255,240,245],\n lawngreen: [124,252,0],\n lemonchiffon: [255,250,205],\n lightblue: [173,216,230],\n lightcoral: [240,128,128],\n lightcyan: [224,255,255],\n lightgoldenrodyellow: [250,250,210],\n lightgray: [211,211,211],\n lightgreen: [144,238,144],\n lightgrey: [211,211,211],\n lightpink: [255,182,193],\n lightsalmon: [255,160,122],\n lightseagreen: [32,178,170],\n lightskyblue: [135,206,250],\n lightslategray: [119,136,153],\n lightslategrey: [119,136,153],\n lightsteelblue: [176,196,222],\n lightyellow: [255,255,224],\n lime: [0,255,0],\n limegreen: [50,205,50],\n linen: [250,240,230],\n magenta: [255,0,255],\n maroon: [128,0,0],\n mediumaquamarine: [102,205,170],\n mediumblue: [0,0,205],\n mediumorchid: [186,85,211],\n mediumpurple: [147,112,219],\n mediumseagreen: [60,179,113],\n mediumslateblue: [123,104,238],\n mediumspringgreen: [0,250,154],\n mediumturquoise: [72,209,204],\n mediumvioletred: [199,21,133],\n midnightblue: [25,25,112],\n mintcream: [245,255,250],\n mistyrose: [255,228,225],\n moccasin: [255,228,181],\n navajowhite: [255,222,173],\n navy: [0,0,128],\n oldlace: [253,245,230],\n olive: [128,128,0],\n olivedrab: [107,142,35],\n orange: [255,165,0],\n orangered: [255,69,0],\n orchid: [218,112,214],\n palegoldenrod: [238,232,170],\n palegreen: [152,251,152],\n paleturquoise: [175,238,238],\n palevioletred: [219,112,147],\n papayawhip: [255,239,213],\n peachpuff: [255,218,185],\n peru: [205,133,63],\n pink: [255,192,203],\n plum: [221,160,221],\n powderblue: [176,224,230],\n purple: [128,0,128],\n rebeccapurple: [102, 51, 153],\n red: [255,0,0],\n rosybrown: [188,143,143],\n royalblue: [65,105,225],\n saddlebrown: [139,69,19],\n salmon: [250,128,114],\n sandybrown: [244,164,96],\n seagreen: [46,139,87],\n seashell: [255,245,238],\n sienna: [160,82,45],\n silver: [192,192,192],\n skyblue: [135,206,235],\n slateblue: [106,90,205],\n slategray: [112,128,144],\n slategrey: [112,128,144],\n snow: [255,250,250],\n springgreen: [0,255,127],\n steelblue: [70,130,180],\n tan: [210,180,140],\n teal: [0,128,128],\n thistle: [216,191,216],\n tomato: [255,99,71],\n turquoise: [64,224,208],\n violet: [238,130,238],\n wheat: [245,222,179],\n white: [255,255,255],\n whitesmoke: [245,245,245],\n yellow: [255,255,0],\n yellowgreen: [154,205,50]\n};\n\nvar reverseKeywords = {};\nfor (var key in cssKeywords) {\n reverseKeywords[JSON.stringify(cssKeywords[key])] = key;\n}\n\n},{}],5:[function(require,module,exports){\nvar conversions = require(4);\n\nvar convert = function() {\n return new Converter();\n}\n\nfor (var func in conversions) {\n // export Raw versions\n convert[func + \"Raw\"] = (function(func) {\n // accept array or plain args\n return function(arg) {\n if (typeof arg == \"number\")\n arg = Array.prototype.slice.call(arguments);\n return conversions[func](arg);\n }\n })(func);\n\n var pair = /(\\w+)2(\\w+)/.exec(func),\n from = pair[1],\n to = pair[2];\n\n // export rgb2hsl and [\"rgb\"][\"hsl\"]\n convert[from] = convert[from] || {};\n\n convert[from][to] = convert[func] = (function(func) { \n return function(arg) {\n if (typeof arg == \"number\")\n arg = Array.prototype.slice.call(arguments);\n \n var val = conversions[func](arg);\n if (typeof val == \"string\" || val === undefined)\n return val; // keyword\n\n for (var i = 0; i < val.length; i++)\n val[i] = Math.round(val[i]);\n return val;\n }\n })(func);\n}\n\n\n/* Converter does lazy conversion and caching */\nvar Converter = function() {\n this.convs = {};\n};\n\n/* Either get the values for a space or\n set the values for a space, depending on args */\nConverter.prototype.routeSpace = function(space, args) {\n var values = args[0];\n if (values === undefined) {\n // color.rgb()\n return this.getValues(space);\n }\n // color.rgb(10, 10, 10)\n if (typeof values == \"number\") {\n values = Array.prototype.slice.call(args); \n }\n\n return this.setValues(space, values);\n};\n \n/* Set the values for a space, invalidating cache */\nConverter.prototype.setValues = function(space, values) {\n this.space = space;\n this.convs = {};\n this.convs[space] = values;\n return this;\n};\n\n/* Get the values for a space. If there's already\n a conversion for the space, fetch it, otherwise\n compute it */\nConverter.prototype.getValues = function(space) {\n var vals = this.convs[space];\n if (!vals) {\n var fspace = this.space,\n from = this.convs[fspace];\n vals = convert[fspace][space](from);\n\n this.convs[space] = vals;\n }\n return vals;\n};\n\n[\"rgb\", \"hsl\", \"hsv\", \"cmyk\", \"keyword\"].forEach(function(space) {\n Converter.prototype[space] = function(vals) {\n return this.routeSpace(space, arguments);\n }\n});\n\nmodule.exports = convert;\n},{\"4\":4}],6:[function(require,module,exports){\nmodule.exports = {\n\t\"aliceblue\": [240, 248, 255],\n\t\"antiquewhite\": [250, 235, 215],\n\t\"aqua\": [0, 255, 255],\n\t\"aquamarine\": [127, 255, 212],\n\t\"azure\": [240, 255, 255],\n\t\"beige\": [245, 245, 220],\n\t\"bisque\": [255, 228, 196],\n\t\"black\": [0, 0, 0],\n\t\"blanchedalmond\": [255, 235, 205],\n\t\"blue\": [0, 0, 255],\n\t\"blueviolet\": [138, 43, 226],\n\t\"brown\": [165, 42, 42],\n\t\"burlywood\": [222, 184, 135],\n\t\"cadetblue\": [95, 158, 160],\n\t\"chartreuse\": [127, 255, 0],\n\t\"chocolate\": [210, 105, 30],\n\t\"coral\": [255, 127, 80],\n\t\"cornflowerblue\": [100, 149, 237],\n\t\"cornsilk\": [255, 248, 220],\n\t\"crimson\": [220, 20, 60],\n\t\"cyan\": [0, 255, 255],\n\t\"darkblue\": [0, 0, 139],\n\t\"darkcyan\": [0, 139, 139],\n\t\"darkgoldenrod\": [184, 134, 11],\n\t\"darkgray\": [169, 169, 169],\n\t\"darkgreen\": [0, 100, 0],\n\t\"darkgrey\": [169, 169, 169],\n\t\"darkkhaki\": [189, 183, 107],\n\t\"darkmagenta\": [139, 0, 139],\n\t\"darkolivegreen\": [85, 107, 47],\n\t\"darkorange\": [255, 140, 0],\n\t\"darkorchid\": [153, 50, 204],\n\t\"darkred\": [139, 0, 0],\n\t\"darksalmon\": [233, 150, 122],\n\t\"darkseagreen\": [143, 188, 143],\n\t\"darkslateblue\": [72, 61, 139],\n\t\"darkslategray\": [47, 79, 79],\n\t\"darkslategrey\": [47, 79, 79],\n\t\"darkturquoise\": [0, 206, 209],\n\t\"darkviolet\": [148, 0, 211],\n\t\"deeppink\": [255, 20, 147],\n\t\"deepskyblue\": [0, 191, 255],\n\t\"dimgray\": [105, 105, 105],\n\t\"dimgrey\": [105, 105, 105],\n\t\"dodgerblue\": [30, 144, 255],\n\t\"firebrick\": [178, 34, 34],\n\t\"floralwhite\": [255, 250, 240],\n\t\"forestgreen\": [34, 139, 34],\n\t\"fuchsia\": [255, 0, 255],\n\t\"gainsboro\": [220, 220, 220],\n\t\"ghostwhite\": [248, 248, 255],\n\t\"gold\": [255, 215, 0],\n\t\"goldenrod\": [218, 165, 32],\n\t\"gray\": [128, 128, 128],\n\t\"green\": [0, 128, 0],\n\t\"greenyellow\": [173, 255, 47],\n\t\"grey\": [128, 128, 128],\n\t\"honeydew\": [240, 255, 240],\n\t\"hotpink\": [255, 105, 180],\n\t\"indianred\": [205, 92, 92],\n\t\"indigo\": [75, 0, 130],\n\t\"ivory\": [255, 255, 240],\n\t\"khaki\": [240, 230, 140],\n\t\"lavender\": [230, 230, 250],\n\t\"lavenderblush\": [255, 240, 245],\n\t\"lawngreen\": [124, 252, 0],\n\t\"lemonchiffon\": [255, 250, 205],\n\t\"lightblue\": [173, 216, 230],\n\t\"lightcoral\": [240, 128, 128],\n\t\"lightcyan\": [224, 255, 255],\n\t\"lightgoldenrodyellow\": [250, 250, 210],\n\t\"lightgray\": [211, 211, 211],\n\t\"lightgreen\": [144, 238, 144],\n\t\"lightgrey\": [211, 211, 211],\n\t\"lightpink\": [255, 182, 193],\n\t\"lightsalmon\": [255, 160, 122],\n\t\"lightseagreen\": [32, 178, 170],\n\t\"lightskyblue\": [135, 206, 250],\n\t\"lightslategray\": [119, 136, 153],\n\t\"lightslategrey\": [119, 136, 153],\n\t\"lightsteelblue\": [176, 196, 222],\n\t\"lightyellow\": [255, 255, 224],\n\t\"lime\": [0, 255, 0],\n\t\"limegreen\": [50, 205, 50],\n\t\"linen\": [250, 240, 230],\n\t\"magenta\": [255, 0, 255],\n\t\"maroon\": [128, 0, 0],\n\t\"mediumaquamarine\": [102, 205, 170],\n\t\"mediumblue\": [0, 0, 205],\n\t\"mediumorchid\": [186, 85, 211],\n\t\"mediumpurple\": [147, 112, 219],\n\t\"mediumseagreen\": [60, 179, 113],\n\t\"mediumslateblue\": [123, 104, 238],\n\t\"mediumspringgreen\": [0, 250, 154],\n\t\"mediumturquoise\": [72, 209, 204],\n\t\"mediumvioletred\": [199, 21, 133],\n\t\"midnightblue\": [25, 25, 112],\n\t\"mintcream\": [245, 255, 250],\n\t\"mistyrose\": [255, 228, 225],\n\t\"moccasin\": [255, 228, 181],\n\t\"navajowhite\": [255, 222, 173],\n\t\"navy\": [0, 0, 128],\n\t\"oldlace\": [253, 245, 230],\n\t\"olive\": [128, 128, 0],\n\t\"olivedrab\": [107, 142, 35],\n\t\"orange\": [255, 165, 0],\n\t\"orangered\": [255, 69, 0],\n\t\"orchid\": [218, 112, 214],\n\t\"palegoldenrod\": [238, 232, 170],\n\t\"palegreen\": [152, 251, 152],\n\t\"paleturquoise\": [175, 238, 238],\n\t\"palevioletred\": [219, 112, 147],\n\t\"papayawhip\": [255, 239, 213],\n\t\"peachpuff\": [255, 218, 185],\n\t\"peru\": [205, 133, 63],\n\t\"pink\": [255, 192, 203],\n\t\"plum\": [221, 160, 221],\n\t\"powderblue\": [176, 224, 230],\n\t\"purple\": [128, 0, 128],\n\t\"rebeccapurple\": [102, 51, 153],\n\t\"red\": [255, 0, 0],\n\t\"rosybrown\": [188, 143, 143],\n\t\"royalblue\": [65, 105, 225],\n\t\"saddlebrown\": [139, 69, 19],\n\t\"salmon\": [250, 128, 114],\n\t\"sandybrown\": [244, 164, 96],\n\t\"seagreen\": [46, 139, 87],\n\t\"seashell\": [255, 245, 238],\n\t\"sienna\": [160, 82, 45],\n\t\"silver\": [192, 192, 192],\n\t\"skyblue\": [135, 206, 235],\n\t\"slateblue\": [106, 90, 205],\n\t\"slategray\": [112, 128, 144],\n\t\"slategrey\": [112, 128, 144],\n\t\"snow\": [255, 250, 250],\n\t\"springgreen\": [0, 255, 127],\n\t\"steelblue\": [70, 130, 180],\n\t\"tan\": [210, 180, 140],\n\t\"teal\": [0, 128, 128],\n\t\"thistle\": [216, 191, 216],\n\t\"tomato\": [255, 99, 71],\n\t\"turquoise\": [64, 224, 208],\n\t\"violet\": [238, 130, 238],\n\t\"wheat\": [245, 222, 179],\n\t\"white\": [255, 255, 255],\n\t\"whitesmoke\": [245, 245, 245],\n\t\"yellow\": [255, 255, 0],\n\t\"yellowgreen\": [154, 205, 50]\n};\n},{}],7:[function(require,module,exports){\n/**\n * @namespace Chart\n */\nvar Chart = require(28)();\n\nrequire(26)(Chart);\nrequire(42)(Chart);\nrequire(22)(Chart);\nrequire(31)(Chart);\nrequire(25)(Chart);\nrequire(21)(Chart);\nrequire(23)(Chart);\nrequire(24)(Chart);\nrequire(29)(Chart);\nrequire(33)(Chart);\nrequire(34)(Chart);\nrequire(32)(Chart);\nrequire(35)(Chart);\nrequire(30)(Chart);\nrequire(27)(Chart);\nrequire(36)(Chart);\n\nrequire(37)(Chart);\nrequire(38)(Chart);\nrequire(39)(Chart);\nrequire(40)(Chart);\n\nrequire(45)(Chart);\nrequire(43)(Chart);\nrequire(44)(Chart);\nrequire(46)(Chart);\nrequire(47)(Chart);\nrequire(48)(Chart);\n\n// Controllers must be loaded after elements\n// See Chart.core.datasetController.dataElementType\nrequire(15)(Chart);\nrequire(16)(Chart);\nrequire(17)(Chart);\nrequire(18)(Chart);\nrequire(19)(Chart);\nrequire(20)(Chart);\n\nrequire(8)(Chart);\nrequire(9)(Chart);\nrequire(10)(Chart);\nrequire(11)(Chart);\nrequire(12)(Chart);\nrequire(13)(Chart);\nrequire(14)(Chart);\n\nwindow.Chart = module.exports = Chart;\n\n},{\"10\":10,\"11\":11,\"12\":12,\"13\":13,\"14\":14,\"15\":15,\"16\":16,\"17\":17,\"18\":18,\"19\":19,\"20\":20,\"21\":21,\"22\":22,\"23\":23,\"24\":24,\"25\":25,\"26\":26,\"27\":27,\"28\":28,\"29\":29,\"30\":30,\"31\":31,\"32\":32,\"33\":33,\"34\":34,\"35\":35,\"36\":36,\"37\":37,\"38\":38,\"39\":39,\"40\":40,\"42\":42,\"43\":43,\"44\":44,\"45\":45,\"46\":46,\"47\":47,\"48\":48,\"8\":8,\"9\":9}],8:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tChart.Bar = function(context, config) {\n\t\tconfig.type = 'bar';\n\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],9:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tChart.Bubble = function(context, config) {\n\t\tconfig.type = 'bubble';\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],10:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tChart.Doughnut = function(context, config) {\n\t\tconfig.type = 'doughnut';\n\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],11:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tChart.Line = function(context, config) {\n\t\tconfig.type = 'line';\n\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],12:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tChart.PolarArea = function(context, config) {\n\t\tconfig.type = 'polarArea';\n\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],13:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tChart.Radar = function(context, config) {\n\t\tconfig.type = 'radar';\n\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],14:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar defaultConfig = {\n\t\thover: {\n\t\t\tmode: 'single'\n\t\t},\n\n\t\tscales: {\n\t\t\txAxes: [{\n\t\t\t\ttype: 'linear', // scatter should not use a category axis\n\t\t\t\tposition: 'bottom',\n\t\t\t\tid: 'x-axis-1' // need an ID so datasets can reference the scale\n\t\t\t}],\n\t\t\tyAxes: [{\n\t\t\t\ttype: 'linear',\n\t\t\t\tposition: 'left',\n\t\t\t\tid: 'y-axis-1'\n\t\t\t}]\n\t\t},\n\n\t\ttooltips: {\n\t\t\tcallbacks: {\n\t\t\t\ttitle: function() {\n\t\t\t\t\t// Title doesn't make sense for scatter since we format the data as a point\n\t\t\t\t\treturn '';\n\t\t\t\t},\n\t\t\t\tlabel: function(tooltipItem) {\n\t\t\t\t\treturn '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t// Register the default config for this type\n\tChart.defaults.scatter = defaultConfig;\n\n\t// Scatter charts use line controllers\n\tChart.controllers.scatter = Chart.controllers.line;\n\n\tChart.Scatter = function(context, config) {\n\t\tconfig.type = 'scatter';\n\t\treturn new Chart(context, config);\n\t};\n\n};\n\n},{}],15:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.bar = {\n\t\thover: {\n\t\t\tmode: 'label'\n\t\t},\n\n\t\tscales: {\n\t\t\txAxes: [{\n\t\t\t\ttype: 'category',\n\n\t\t\t\t// Specific to Bar Controller\n\t\t\t\tcategoryPercentage: 0.8,\n\t\t\t\tbarPercentage: 0.9,\n\n\t\t\t\t// grid line settings\n\t\t\t\tgridLines: {\n\t\t\t\t\toffsetGridLines: true\n\t\t\t\t}\n\t\t\t}],\n\t\t\tyAxes: [{\n\t\t\t\ttype: 'linear'\n\t\t\t}]\n\t\t}\n\t};\n\n\tChart.controllers.bar = Chart.DatasetController.extend({\n\n\t\tdataElementType: Chart.elements.Rectangle,\n\n\t\tinitialize: function(chart, datasetIndex) {\n\t\t\tChart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);\n\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar dataset = me.getDataset();\n\n\t\t\tmeta.stack = dataset.stack;\n\t\t\t// Use this to indicate that this is a bar dataset.\n\t\t\tmeta.bar = true;\n\t\t},\n\n\t\t// Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible\n\t\tgetStackCount: function() {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\n\t\t\tvar stacks = [];\n\t\t\thelpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tvar dsMeta = me.chart.getDatasetMeta(datasetIndex);\n\t\t\t\tif (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) &&\n\t\t\t\t\t(yScale.options.stacked === false ||\n\t\t\t\t\t(yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||\n\t\t\t\t\t(yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {\n\t\t\t\t\tstacks.push(dsMeta.stack);\n\t\t\t\t}\n\t\t\t}, me);\n\n\t\t\treturn stacks.length;\n\t\t},\n\n\t\tupdate: function(reset) {\n\t\t\tvar me = this;\n\t\t\thelpers.each(me.getMeta().data, function(rectangle, index) {\n\t\t\t\tme.updateElement(rectangle, index, reset);\n\t\t\t}, me);\n\t\t},\n\n\t\tupdateElement: function(rectangle, index, reset) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar scaleBase = yScale.getBasePixel();\n\t\t\tvar rectangleElementOptions = me.chart.options.elements.rectangle;\n\t\t\tvar custom = rectangle.custom || {};\n\t\t\tvar dataset = me.getDataset();\n\n\t\t\trectangle._xScale = xScale;\n\t\t\trectangle._yScale = yScale;\n\t\t\trectangle._datasetIndex = me.index;\n\t\t\trectangle._index = index;\n\n\t\t\tvar ruler = me.getRuler(index); // The index argument for compatible\n\t\t\trectangle._model = {\n\t\t\t\tx: me.calculateBarX(index, me.index, ruler),\n\t\t\t\ty: reset ? scaleBase : me.calculateBarY(index, me.index),\n\n\t\t\t\t// Tooltip\n\t\t\t\tlabel: me.chart.data.labels[index],\n\t\t\t\tdatasetLabel: dataset.label,\n\n\t\t\t\t// Appearance\n\t\t\t\thorizontal: false,\n\t\t\t\tbase: reset ? scaleBase : me.calculateBarBase(me.index, index),\n\t\t\t\twidth: me.calculateBarWidth(ruler),\n\t\t\t\tbackgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),\n\t\t\t\tborderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,\n\t\t\t\tborderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),\n\t\t\t\tborderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)\n\t\t\t};\n\n\t\t\trectangle.pivot();\n\t\t},\n\n\t\tcalculateBarBase: function(datasetIndex, index) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar base = yScale.getBaseValue();\n\t\t\tvar original = base;\n\n\t\t\tif ((yScale.options.stacked === true) ||\n\t\t\t\t(yScale.options.stacked === undefined && meta.stack !== undefined)) {\n\t\t\t\tvar chart = me.chart;\n\t\t\t\tvar datasets = chart.data.datasets;\n\t\t\t\tvar value = Number(datasets[datasetIndex].data[index]);\n\n\t\t\t\tfor (var i = 0; i < datasetIndex; i++) {\n\t\t\t\t\tvar currentDs = datasets[i];\n\t\t\t\t\tvar currentDsMeta = chart.getDatasetMeta(i);\n\t\t\t\t\tif (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i) &&\n\t\t\t\t\t\tmeta.stack === currentDsMeta.stack) {\n\t\t\t\t\t\tvar currentVal = Number(currentDs.data[index]);\n\t\t\t\t\t\tbase += value < 0 ? Math.min(currentVal, original) : Math.max(currentVal, original);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn yScale.getPixelForValue(base);\n\t\t\t}\n\n\t\t\treturn yScale.getBasePixel();\n\t\t},\n\n\t\tgetRuler: function() {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar stackCount = me.getStackCount();\n\n\t\t\tvar tickWidth = xScale.width / xScale.ticks.length;\n\t\t\tvar categoryWidth = tickWidth * xScale.options.categoryPercentage;\n\t\t\tvar categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;\n\t\t\tvar fullBarWidth = categoryWidth / stackCount;\n\n\t\t\tvar barWidth = fullBarWidth * xScale.options.barPercentage;\n\t\t\tvar barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);\n\n\t\t\treturn {\n\t\t\t\tstackCount: stackCount,\n\t\t\t\ttickWidth: tickWidth,\n\t\t\t\tcategoryWidth: categoryWidth,\n\t\t\t\tcategorySpacing: categorySpacing,\n\t\t\t\tfullBarWidth: fullBarWidth,\n\t\t\t\tbarWidth: barWidth,\n\t\t\t\tbarSpacing: barSpacing\n\t\t\t};\n\t\t},\n\n\t\tcalculateBarWidth: function(ruler) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tif (xScale.options.barThickness) {\n\t\t\t\treturn xScale.options.barThickness;\n\t\t\t}\n\t\t\treturn ruler.barWidth;\n\t\t},\n\n\t\t// Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible\n\t\tgetStackIndex: function(datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.chart.getDatasetMeta(datasetIndex);\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar dsMeta, j;\n\t\t\tvar stacks = [meta.stack];\n\n\t\t\tfor (j = 0; j < datasetIndex; ++j) {\n\t\t\t\tdsMeta = this.chart.getDatasetMeta(j);\n\t\t\t\tif (dsMeta.bar && this.chart.isDatasetVisible(j) &&\n\t\t\t\t\t(yScale.options.stacked === false ||\n\t\t\t\t\t(yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||\n\t\t\t\t\t(yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {\n\t\t\t\t\tstacks.push(dsMeta.stack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn stacks.length - 1;\n\t\t},\n\n\t\tcalculateBarX: function(index, datasetIndex, ruler) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar stackIndex = me.getStackIndex(datasetIndex);\n\t\t\tvar leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);\n\t\t\tleftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0;\n\n\t\t\treturn leftTick +\n\t\t\t\t(ruler.barWidth / 2) +\n\t\t\t\truler.categorySpacing +\n\t\t\t\t(ruler.barWidth * stackIndex) +\n\t\t\t\t(ruler.barSpacing / 2) +\n\t\t\t\t(ruler.barSpacing * stackIndex);\n\t\t},\n\n\t\tcalculateBarY: function(index, datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar value = Number(me.getDataset().data[index]);\n\n\t\t\tif (yScale.options.stacked ||\n\t\t\t\t(yScale.options.stacked === undefined && meta.stack !== undefined)) {\n\t\t\t\tvar base = yScale.getBaseValue();\n\t\t\t\tvar sumPos = base,\n\t\t\t\t\tsumNeg = base;\n\n\t\t\t\tfor (var i = 0; i < datasetIndex; i++) {\n\t\t\t\t\tvar ds = me.chart.data.datasets[i];\n\t\t\t\t\tvar dsMeta = me.chart.getDatasetMeta(i);\n\t\t\t\t\tif (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i) &&\n\t\t\t\t\t\tmeta.stack === dsMeta.stack) {\n\t\t\t\t\t\tvar stackedVal = Number(ds.data[index]);\n\t\t\t\t\t\tif (stackedVal < 0) {\n\t\t\t\t\t\t\tsumNeg += stackedVal || 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsumPos += stackedVal || 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (value < 0) {\n\t\t\t\t\treturn yScale.getPixelForValue(sumNeg + value);\n\t\t\t\t}\n\t\t\t\treturn yScale.getPixelForValue(sumPos + value);\n\t\t\t}\n\n\t\t\treturn yScale.getPixelForValue(value);\n\t\t},\n\n\t\tdraw: function(ease) {\n\t\t\tvar me = this;\n\t\t\tvar easingDecimal = ease || 1;\n\t\t\tvar metaData = me.getMeta().data;\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar i, len;\n\n\t\t\tChart.canvasHelpers.clipArea(me.chart.chart.ctx, me.chart.chartArea);\n\t\t\tfor (i = 0, len = metaData.length; i < len; ++i) {\n\t\t\t\tvar d = dataset.data[i];\n\t\t\t\tif (d !== null && d !== undefined && !isNaN(d)) {\n\t\t\t\t\tmetaData[i].transition(easingDecimal).draw();\n\t\t\t\t}\n\t\t\t}\n\t\t\tChart.canvasHelpers.unclipArea(me.chart.chart.ctx);\n\t\t},\n\n\t\tsetHoverStyle: function(rectangle) {\n\t\t\tvar dataset = this.chart.data.datasets[rectangle._datasetIndex];\n\t\t\tvar index = rectangle._index;\n\n\t\t\tvar custom = rectangle.custom || {};\n\t\t\tvar model = rectangle._model;\n\t\t\tmodel.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));\n\t\t\tmodel.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));\n\t\t\tmodel.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);\n\t\t},\n\n\t\tremoveHoverStyle: function(rectangle) {\n\t\t\tvar dataset = this.chart.data.datasets[rectangle._datasetIndex];\n\t\t\tvar index = rectangle._index;\n\t\t\tvar custom = rectangle.custom || {};\n\t\t\tvar model = rectangle._model;\n\t\t\tvar rectangleElementOptions = this.chart.options.elements.rectangle;\n\n\t\t\tmodel.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);\n\t\t\tmodel.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);\n\t\t\tmodel.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);\n\t\t}\n\n\t});\n\n\n\t// including horizontalBar in the bar file, instead of a file of its own\n\t// it extends bar (like pie extends doughnut)\n\tChart.defaults.horizontalBar = {\n\t\thover: {\n\t\t\tmode: 'label'\n\t\t},\n\n\t\tscales: {\n\t\t\txAxes: [{\n\t\t\t\ttype: 'linear',\n\t\t\t\tposition: 'bottom'\n\t\t\t}],\n\t\t\tyAxes: [{\n\t\t\t\tposition: 'left',\n\t\t\t\ttype: 'category',\n\n\t\t\t\t// Specific to Horizontal Bar Controller\n\t\t\t\tcategoryPercentage: 0.8,\n\t\t\t\tbarPercentage: 0.9,\n\n\t\t\t\t// grid line settings\n\t\t\t\tgridLines: {\n\t\t\t\t\toffsetGridLines: true\n\t\t\t\t}\n\t\t\t}]\n\t\t},\n\t\telements: {\n\t\t\trectangle: {\n\t\t\t\tborderSkipped: 'left'\n\t\t\t}\n\t\t},\n\t\ttooltips: {\n\t\t\tcallbacks: {\n\t\t\t\ttitle: function(tooltipItems, data) {\n\t\t\t\t\t// Pick first xLabel for now\n\t\t\t\t\tvar title = '';\n\n\t\t\t\t\tif (tooltipItems.length > 0) {\n\t\t\t\t\t\tif (tooltipItems[0].yLabel) {\n\t\t\t\t\t\t\ttitle = tooltipItems[0].yLabel;\n\t\t\t\t\t\t} else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {\n\t\t\t\t\t\t\ttitle = data.labels[tooltipItems[0].index];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn title;\n\t\t\t\t},\n\t\t\t\tlabel: function(tooltipItem, data) {\n\t\t\t\t\tvar datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';\n\t\t\t\t\treturn datasetLabel + ': ' + tooltipItem.xLabel;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tChart.controllers.horizontalBar = Chart.controllers.bar.extend({\n\n\t\t// Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible\n\t\tgetStackCount: function() {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\n\t\t\tvar stacks = [];\n\t\t\thelpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tvar dsMeta = me.chart.getDatasetMeta(datasetIndex);\n\t\t\t\tif (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) &&\n\t\t\t\t\t(xScale.options.stacked === false ||\n\t\t\t\t\t(xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||\n\t\t\t\t\t(xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {\n\t\t\t\t\tstacks.push(dsMeta.stack);\n\t\t\t\t}\n\t\t\t}, me);\n\n\t\t\treturn stacks.length;\n\t\t},\n\n\t\tupdateElement: function(rectangle, index, reset) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar scaleBase = xScale.getBasePixel();\n\t\t\tvar custom = rectangle.custom || {};\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar rectangleElementOptions = me.chart.options.elements.rectangle;\n\n\t\t\trectangle._xScale = xScale;\n\t\t\trectangle._yScale = yScale;\n\t\t\trectangle._datasetIndex = me.index;\n\t\t\trectangle._index = index;\n\n\t\t\tvar ruler = me.getRuler(index); // The index argument for compatible\n\t\t\trectangle._model = {\n\t\t\t\tx: reset ? scaleBase : me.calculateBarX(index, me.index),\n\t\t\t\ty: me.calculateBarY(index, me.index, ruler),\n\n\t\t\t\t// Tooltip\n\t\t\t\tlabel: me.chart.data.labels[index],\n\t\t\t\tdatasetLabel: dataset.label,\n\n\t\t\t\t// Appearance\n\t\t\t\thorizontal: true,\n\t\t\t\tbase: reset ? scaleBase : me.calculateBarBase(me.index, index),\n\t\t\t\theight: me.calculateBarHeight(ruler),\n\t\t\t\tbackgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),\n\t\t\t\tborderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,\n\t\t\t\tborderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),\n\t\t\t\tborderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)\n\t\t\t};\n\n\t\t\trectangle.pivot();\n\t\t},\n\n\t\tcalculateBarBase: function(datasetIndex, index) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar base = xScale.getBaseValue();\n\t\t\tvar originalBase = base;\n\n\t\t\tif (xScale.options.stacked ||\n\t\t\t\t(xScale.options.stacked === undefined && meta.stack !== undefined)) {\n\t\t\t\tvar chart = me.chart;\n\t\t\t\tvar datasets = chart.data.datasets;\n\t\t\t\tvar value = Number(datasets[datasetIndex].data[index]);\n\n\t\t\t\tfor (var i = 0; i < datasetIndex; i++) {\n\t\t\t\t\tvar currentDs = datasets[i];\n\t\t\t\t\tvar currentDsMeta = chart.getDatasetMeta(i);\n\t\t\t\t\tif (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i) &&\n\t\t\t\t\t\tmeta.stack === currentDsMeta.stack) {\n\t\t\t\t\t\tvar currentVal = Number(currentDs.data[index]);\n\t\t\t\t\t\tbase += value < 0 ? Math.min(currentVal, originalBase) : Math.max(currentVal, originalBase);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn xScale.getPixelForValue(base);\n\t\t\t}\n\n\t\t\treturn xScale.getBasePixel();\n\t\t},\n\n\t\tgetRuler: function() {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar stackCount = me.getStackCount();\n\n\t\t\tvar tickHeight = yScale.height / yScale.ticks.length;\n\t\t\tvar categoryHeight = tickHeight * yScale.options.categoryPercentage;\n\t\t\tvar categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;\n\t\t\tvar fullBarHeight = categoryHeight / stackCount;\n\n\t\t\tvar barHeight = fullBarHeight * yScale.options.barPercentage;\n\t\t\tvar barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);\n\n\t\t\treturn {\n\t\t\t\tstackCount: stackCount,\n\t\t\t\ttickHeight: tickHeight,\n\t\t\t\tcategoryHeight: categoryHeight,\n\t\t\t\tcategorySpacing: categorySpacing,\n\t\t\t\tfullBarHeight: fullBarHeight,\n\t\t\t\tbarHeight: barHeight,\n\t\t\t\tbarSpacing: barSpacing\n\t\t\t};\n\t\t},\n\n\t\tcalculateBarHeight: function(ruler) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tif (yScale.options.barThickness) {\n\t\t\t\treturn yScale.options.barThickness;\n\t\t\t}\n\t\t\treturn ruler.barHeight;\n\t\t},\n\n\t\t// Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible\n\t\tgetStackIndex: function(datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.chart.getDatasetMeta(datasetIndex);\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar dsMeta, j;\n\t\t\tvar stacks = [meta.stack];\n\n\t\t\tfor (j = 0; j < datasetIndex; ++j) {\n\t\t\t\tdsMeta = this.chart.getDatasetMeta(j);\n\t\t\t\tif (dsMeta.bar && this.chart.isDatasetVisible(j) &&\n\t\t\t\t\t(xScale.options.stacked === false ||\n\t\t\t\t\t(xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||\n\t\t\t\t\t(xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {\n\t\t\t\t\tstacks.push(dsMeta.stack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn stacks.length - 1;\n\t\t},\n\n\t\tcalculateBarX: function(index, datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar value = Number(me.getDataset().data[index]);\n\n\t\t\tif (xScale.options.stacked ||\n\t\t\t\t(xScale.options.stacked === undefined && meta.stack !== undefined)) {\n\t\t\t\tvar base = xScale.getBaseValue();\n\t\t\t\tvar sumPos = base,\n\t\t\t\t\tsumNeg = base;\n\n\t\t\t\tfor (var i = 0; i < datasetIndex; i++) {\n\t\t\t\t\tvar ds = me.chart.data.datasets[i];\n\t\t\t\t\tvar dsMeta = me.chart.getDatasetMeta(i);\n\t\t\t\t\tif (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i) &&\n\t\t\t\t\t\tmeta.stack === dsMeta.stack) {\n\t\t\t\t\t\tvar stackedVal = Number(ds.data[index]);\n\t\t\t\t\t\tif (stackedVal < 0) {\n\t\t\t\t\t\t\tsumNeg += stackedVal || 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsumPos += stackedVal || 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (value < 0) {\n\t\t\t\t\treturn xScale.getPixelForValue(sumNeg + value);\n\t\t\t\t}\n\t\t\t\treturn xScale.getPixelForValue(sumPos + value);\n\t\t\t}\n\n\t\t\treturn xScale.getPixelForValue(value);\n\t\t},\n\n\t\tcalculateBarY: function(index, datasetIndex, ruler) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar stackIndex = me.getStackIndex(datasetIndex);\n\t\t\tvar topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);\n\t\t\ttopTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;\n\n\t\t\treturn topTick +\n\t\t\t\t(ruler.barHeight / 2) +\n\t\t\t\truler.categorySpacing +\n\t\t\t\t(ruler.barHeight * stackIndex) +\n\t\t\t\t(ruler.barSpacing / 2) +\n\t\t\t\t(ruler.barSpacing * stackIndex);\n\t\t}\n\t});\n};\n\n},{}],16:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.bubble = {\n\t\thover: {\n\t\t\tmode: 'single'\n\t\t},\n\n\t\tscales: {\n\t\t\txAxes: [{\n\t\t\t\ttype: 'linear', // bubble should probably use a linear scale by default\n\t\t\t\tposition: 'bottom',\n\t\t\t\tid: 'x-axis-0' // need an ID so datasets can reference the scale\n\t\t\t}],\n\t\t\tyAxes: [{\n\t\t\t\ttype: 'linear',\n\t\t\t\tposition: 'left',\n\t\t\t\tid: 'y-axis-0'\n\t\t\t}]\n\t\t},\n\n\t\ttooltips: {\n\t\t\tcallbacks: {\n\t\t\t\ttitle: function() {\n\t\t\t\t\t// Title doesn't make sense for scatter since we format the data as a point\n\t\t\t\t\treturn '';\n\t\t\t\t},\n\t\t\t\tlabel: function(tooltipItem, data) {\n\t\t\t\t\tvar datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';\n\t\t\t\t\tvar dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n\t\t\t\t\treturn datasetLabel + ': (' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ', ' + dataPoint.r + ')';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tChart.controllers.bubble = Chart.DatasetController.extend({\n\n\t\tdataElementType: Chart.elements.Point,\n\n\t\tupdate: function(reset) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar points = meta.data;\n\n\t\t\t// Update Points\n\t\t\thelpers.each(points, function(point, index) {\n\t\t\t\tme.updateElement(point, index, reset);\n\t\t\t});\n\t\t},\n\n\t\tupdateElement: function(point, index, reset) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar xScale = me.getScaleForId(meta.xAxisID);\n\t\t\tvar yScale = me.getScaleForId(meta.yAxisID);\n\n\t\t\tvar custom = point.custom || {};\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar data = dataset.data[index];\n\t\t\tvar pointElementOptions = me.chart.options.elements.point;\n\t\t\tvar dsIndex = me.index;\n\n\t\t\thelpers.extend(point, {\n\t\t\t\t// Utility\n\t\t\t\t_xScale: xScale,\n\t\t\t\t_yScale: yScale,\n\t\t\t\t_datasetIndex: dsIndex,\n\t\t\t\t_index: index,\n\n\t\t\t\t// Desired view properties\n\t\t\t\t_model: {\n\t\t\t\t\tx: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo),\n\t\t\t\t\ty: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex),\n\t\t\t\t\t// Appearance\n\t\t\t\t\tradius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data),\n\n\t\t\t\t\t// Tooltip\n\t\t\t\t\thitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Trick to reset the styles of the point\n\t\t\tChart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions);\n\n\t\t\tvar model = point._model;\n\t\t\tmodel.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y));\n\n\t\t\tpoint.pivot();\n\t\t},\n\n\t\tgetRadius: function(value) {\n\t\t\treturn value.r || this.chart.options.elements.point.radius;\n\t\t},\n\n\t\tsetHoverStyle: function(point) {\n\t\t\tvar me = this;\n\t\t\tChart.DatasetController.prototype.setHoverStyle.call(me, point);\n\n\t\t\t// Radius\n\t\t\tvar dataset = me.chart.data.datasets[point._datasetIndex];\n\t\t\tvar index = point._index;\n\t\t\tvar custom = point.custom || {};\n\t\t\tvar model = point._model;\n\t\t\tmodel.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]);\n\t\t},\n\n\t\tremoveHoverStyle: function(point) {\n\t\t\tvar me = this;\n\t\t\tChart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point);\n\n\t\t\tvar dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index];\n\t\t\tvar custom = point.custom || {};\n\t\t\tvar model = point._model;\n\n\t\t\tmodel.radius = custom.radius ? custom.radius : me.getRadius(dataVal);\n\t\t}\n\t});\n};\n\n},{}],17:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers,\n\t\tdefaults = Chart.defaults;\n\n\tdefaults.doughnut = {\n\t\tanimation: {\n\t\t\t// Boolean - Whether we animate the rotation of the Doughnut\n\t\t\tanimateRotate: true,\n\t\t\t// Boolean - Whether we animate scaling the Doughnut from the centre\n\t\t\tanimateScale: false\n\t\t},\n\t\taspectRatio: 1,\n\t\thover: {\n\t\t\tmode: 'single'\n\t\t},\n\t\tlegendCallback: function(chart) {\n\t\t\tvar text = [];\n\t\t\ttext.push('');\n\n\t\t\tvar data = chart.data;\n\t\t\tvar datasets = data.datasets;\n\t\t\tvar labels = data.labels;\n\n\t\t\tif (datasets.length) {\n\t\t\t\tfor (var i = 0; i < datasets[0].data.length; ++i) {\n\t\t\t\t\ttext.push(' ');\n\t\t\t\t\tif (labels[i]) {\n\t\t\t\t\t\ttext.push(labels[i]);\n\t\t\t\t\t}\n\t\t\t\t\ttext.push(' ');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttext.push(' ');\n\t\t\treturn text.join('');\n\t\t},\n\t\tlegend: {\n\t\t\tlabels: {\n\t\t\t\tgenerateLabels: function(chart) {\n\t\t\t\t\tvar data = chart.data;\n\t\t\t\t\tif (data.labels.length && data.datasets.length) {\n\t\t\t\t\t\treturn data.labels.map(function(label, i) {\n\t\t\t\t\t\t\tvar meta = chart.getDatasetMeta(0);\n\t\t\t\t\t\t\tvar ds = data.datasets[0];\n\t\t\t\t\t\t\tvar arc = meta.data[i];\n\t\t\t\t\t\t\tvar custom = arc && arc.custom || {};\n\t\t\t\t\t\t\tvar getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;\n\t\t\t\t\t\t\tvar arcOpts = chart.options.elements.arc;\n\t\t\t\t\t\t\tvar fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);\n\t\t\t\t\t\t\tvar stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);\n\t\t\t\t\t\t\tvar bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);\n\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\ttext: label,\n\t\t\t\t\t\t\t\tfillStyle: fill,\n\t\t\t\t\t\t\t\tstrokeStyle: stroke,\n\t\t\t\t\t\t\t\tlineWidth: bw,\n\t\t\t\t\t\t\t\thidden: isNaN(ds.data[i]) || meta.data[i].hidden,\n\n\t\t\t\t\t\t\t\t// Extra data used for toggling the correct item\n\t\t\t\t\t\t\t\tindex: i\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tonClick: function(e, legendItem) {\n\t\t\t\tvar index = legendItem.index;\n\t\t\t\tvar chart = this.chart;\n\t\t\t\tvar i, ilen, meta;\n\n\t\t\t\tfor (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {\n\t\t\t\t\tmeta = chart.getDatasetMeta(i);\n\t\t\t\t\t// toggle visibility of index if exists\n\t\t\t\t\tif (meta.data[index]) {\n\t\t\t\t\t\tmeta.data[index].hidden = !meta.data[index].hidden;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tchart.update();\n\t\t\t}\n\t\t},\n\n\t\t// The percentage of the chart that we cut out of the middle.\n\t\tcutoutPercentage: 50,\n\n\t\t// The rotation of the chart, where the first data arc begins.\n\t\trotation: Math.PI * -0.5,\n\n\t\t// The total circumference of the chart.\n\t\tcircumference: Math.PI * 2.0,\n\n\t\t// Need to override these to give a nice default\n\t\ttooltips: {\n\t\t\tcallbacks: {\n\t\t\t\ttitle: function() {\n\t\t\t\t\treturn '';\n\t\t\t\t},\n\t\t\t\tlabel: function(tooltipItem, data) {\n\t\t\t\t\tvar dataLabel = data.labels[tooltipItem.index];\n\t\t\t\t\tvar value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n\n\t\t\t\t\tif (helpers.isArray(dataLabel)) {\n\t\t\t\t\t\t// show value on first line of multiline label\n\t\t\t\t\t\t// need to clone because we are changing the value\n\t\t\t\t\t\tdataLabel = dataLabel.slice();\n\t\t\t\t\t\tdataLabel[0] += value;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdataLabel += value;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn dataLabel;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tdefaults.pie = helpers.clone(defaults.doughnut);\n\thelpers.extend(defaults.pie, {\n\t\tcutoutPercentage: 0\n\t});\n\n\n\tChart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({\n\n\t\tdataElementType: Chart.elements.Arc,\n\n\t\tlinkScales: helpers.noop,\n\n\t\t// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly\n\t\tgetRingIndex: function(datasetIndex) {\n\t\t\tvar ringIndex = 0;\n\n\t\t\tfor (var j = 0; j < datasetIndex; ++j) {\n\t\t\t\tif (this.chart.isDatasetVisible(j)) {\n\t\t\t\t\t++ringIndex;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn ringIndex;\n\t\t},\n\n\t\tupdate: function(reset) {\n\t\t\tvar me = this;\n\t\t\tvar chart = me.chart,\n\t\t\t\tchartArea = chart.chartArea,\n\t\t\t\topts = chart.options,\n\t\t\t\tarcOpts = opts.elements.arc,\n\t\t\t\tavailableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth,\n\t\t\t\tavailableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth,\n\t\t\t\tminSize = Math.min(availableWidth, availableHeight),\n\t\t\t\toffset = {\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: 0\n\t\t\t\t},\n\t\t\t\tmeta = me.getMeta(),\n\t\t\t\tcutoutPercentage = opts.cutoutPercentage,\n\t\t\t\tcircumference = opts.circumference;\n\n\t\t\t// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc\n\t\t\tif (circumference < Math.PI * 2.0) {\n\t\t\t\tvar startAngle = opts.rotation % (Math.PI * 2.0);\n\t\t\t\tstartAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);\n\t\t\t\tvar endAngle = startAngle + circumference;\n\t\t\t\tvar start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};\n\t\t\t\tvar end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};\n\t\t\t\tvar contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);\n\t\t\t\tvar contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);\n\t\t\t\tvar contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);\n\t\t\t\tvar contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);\n\t\t\t\tvar cutout = cutoutPercentage / 100.0;\n\t\t\t\tvar min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};\n\t\t\t\tvar max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};\n\t\t\t\tvar size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};\n\t\t\t\tminSize = Math.min(availableWidth / size.width, availableHeight / size.height);\n\t\t\t\toffset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};\n\t\t\t}\n\n\t\t\tchart.borderWidth = me.getMaxBorderWidth(meta.data);\n\t\t\tchart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);\n\t\t\tchart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);\n\t\t\tchart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();\n\t\t\tchart.offsetX = offset.x * chart.outerRadius;\n\t\t\tchart.offsetY = offset.y * chart.outerRadius;\n\n\t\t\tmeta.total = me.calculateTotal();\n\n\t\t\tme.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));\n\t\t\tme.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);\n\n\t\t\thelpers.each(meta.data, function(arc, index) {\n\t\t\t\tme.updateElement(arc, index, reset);\n\t\t\t});\n\t\t},\n\n\t\tupdateElement: function(arc, index, reset) {\n\t\t\tvar me = this;\n\t\t\tvar chart = me.chart,\n\t\t\t\tchartArea = chart.chartArea,\n\t\t\t\topts = chart.options,\n\t\t\t\tanimationOpts = opts.animation,\n\t\t\t\tcenterX = (chartArea.left + chartArea.right) / 2,\n\t\t\t\tcenterY = (chartArea.top + chartArea.bottom) / 2,\n\t\t\t\tstartAngle = opts.rotation, // non reset case handled later\n\t\t\t\tendAngle = opts.rotation, // non reset case handled later\n\t\t\t\tdataset = me.getDataset(),\n\t\t\t\tcircumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)),\n\t\t\t\tinnerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius,\n\t\t\t\touterRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius,\n\t\t\t\tvalueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;\n\n\t\t\thelpers.extend(arc, {\n\t\t\t\t// Utility\n\t\t\t\t_datasetIndex: me.index,\n\t\t\t\t_index: index,\n\n\t\t\t\t// Desired view properties\n\t\t\t\t_model: {\n\t\t\t\t\tx: centerX + chart.offsetX,\n\t\t\t\t\ty: centerY + chart.offsetY,\n\t\t\t\t\tstartAngle: startAngle,\n\t\t\t\t\tendAngle: endAngle,\n\t\t\t\t\tcircumference: circumference,\n\t\t\t\t\touterRadius: outerRadius,\n\t\t\t\t\tinnerRadius: innerRadius,\n\t\t\t\t\tlabel: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tvar model = arc._model;\n\t\t\t// Resets the visual styles\n\t\t\tthis.removeHoverStyle(arc);\n\n\t\t\t// Set correct angles if not resetting\n\t\t\tif (!reset || !animationOpts.animateRotate) {\n\t\t\t\tif (index === 0) {\n\t\t\t\t\tmodel.startAngle = opts.rotation;\n\t\t\t\t} else {\n\t\t\t\t\tmodel.startAngle = me.getMeta().data[index - 1]._model.endAngle;\n\t\t\t\t}\n\n\t\t\t\tmodel.endAngle = model.startAngle + model.circumference;\n\t\t\t}\n\n\t\t\tarc.pivot();\n\t\t},\n\n\t\tremoveHoverStyle: function(arc) {\n\t\t\tChart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);\n\t\t},\n\n\t\tcalculateTotal: function() {\n\t\t\tvar dataset = this.getDataset();\n\t\t\tvar meta = this.getMeta();\n\t\t\tvar total = 0;\n\t\t\tvar value;\n\n\t\t\thelpers.each(meta.data, function(element, index) {\n\t\t\t\tvalue = dataset.data[index];\n\t\t\t\tif (!isNaN(value) && !element.hidden) {\n\t\t\t\t\ttotal += Math.abs(value);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t/* if (total === 0) {\n\t\t\t\ttotal = NaN;\n\t\t\t}*/\n\n\t\t\treturn total;\n\t\t},\n\n\t\tcalculateCircumference: function(value) {\n\t\t\tvar total = this.getMeta().total;\n\t\t\tif (total > 0 && !isNaN(value)) {\n\t\t\t\treturn (Math.PI * 2.0) * (value / total);\n\t\t\t}\n\t\t\treturn 0;\n\t\t},\n\n\t\t// gets the max border or hover width to properly scale pie charts\n\t\tgetMaxBorderWidth: function(elements) {\n\t\t\tvar max = 0,\n\t\t\t\tindex = this.index,\n\t\t\t\tlength = elements.length,\n\t\t\t\tborderWidth,\n\t\t\t\thoverWidth;\n\n\t\t\tfor (var i = 0; i < length; i++) {\n\t\t\t\tborderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0;\n\t\t\t\thoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;\n\n\t\t\t\tmax = borderWidth > max ? borderWidth : max;\n\t\t\t\tmax = hoverWidth > max ? hoverWidth : max;\n\t\t\t}\n\t\t\treturn max;\n\t\t}\n\t});\n};\n\n},{}],18:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.line = {\n\t\tshowLines: true,\n\t\tspanGaps: false,\n\n\t\thover: {\n\t\t\tmode: 'label'\n\t\t},\n\n\t\tscales: {\n\t\t\txAxes: [{\n\t\t\t\ttype: 'category',\n\t\t\t\tid: 'x-axis-0'\n\t\t\t}],\n\t\t\tyAxes: [{\n\t\t\t\ttype: 'linear',\n\t\t\t\tid: 'y-axis-0'\n\t\t\t}]\n\t\t}\n\t};\n\n\tfunction lineEnabled(dataset, options) {\n\t\treturn helpers.getValueOrDefault(dataset.showLine, options.showLines);\n\t}\n\n\tChart.controllers.line = Chart.DatasetController.extend({\n\n\t\tdatasetElementType: Chart.elements.Line,\n\n\t\tdataElementType: Chart.elements.Point,\n\n\t\tupdate: function(reset) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar line = meta.dataset;\n\t\t\tvar points = meta.data || [];\n\t\t\tvar options = me.chart.options;\n\t\t\tvar lineElementOptions = options.elements.line;\n\t\t\tvar scale = me.getScaleForId(meta.yAxisID);\n\t\t\tvar i, ilen, custom;\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar showLine = lineEnabled(dataset, options);\n\n\t\t\t// Update Line\n\t\t\tif (showLine) {\n\t\t\t\tcustom = line.custom || {};\n\n\t\t\t\t// Compatibility: If the properties are defined with only the old name, use those values\n\t\t\t\tif ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {\n\t\t\t\t\tdataset.lineTension = dataset.tension;\n\t\t\t\t}\n\n\t\t\t\t// Utility\n\t\t\t\tline._scale = scale;\n\t\t\t\tline._datasetIndex = me.index;\n\t\t\t\t// Data\n\t\t\t\tline._children = points;\n\t\t\t\t// Model\n\t\t\t\tline._model = {\n\t\t\t\t\t// Appearance\n\t\t\t\t\t// The default behavior of lines is to break at null values, according\n\t\t\t\t\t// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158\n\t\t\t\t\t// This option gives lines the ability to span gaps\n\t\t\t\t\tspanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,\n\t\t\t\t\ttension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),\n\t\t\t\t\tbackgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),\n\t\t\t\t\tborderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),\n\t\t\t\t\tborderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),\n\t\t\t\t\tborderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),\n\t\t\t\t\tborderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),\n\t\t\t\t\tborderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),\n\t\t\t\t\tborderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),\n\t\t\t\t\tfill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),\n\t\t\t\t\tsteppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped),\n\t\t\t\t\tcubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.getValueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),\n\t\t\t\t\t// Scale\n\t\t\t\t\tscaleTop: scale.top,\n\t\t\t\t\tscaleBottom: scale.bottom,\n\t\t\t\t\tscaleZero: scale.getBasePixel()\n\t\t\t\t};\n\n\t\t\t\tline.pivot();\n\t\t\t}\n\n\t\t\t// Update Points\n\t\t\tfor (i=0, ilen=points.length; i');\n\n\t\t\tvar data = chart.data;\n\t\t\tvar datasets = data.datasets;\n\t\t\tvar labels = data.labels;\n\n\t\t\tif (datasets.length) {\n\t\t\t\tfor (var i = 0; i < datasets[0].data.length; ++i) {\n\t\t\t\t\ttext.push(' ');\n\t\t\t\t\tif (labels[i]) {\n\t\t\t\t\t\ttext.push(labels[i]);\n\t\t\t\t\t}\n\t\t\t\t\ttext.push(' ');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttext.push('');\n\t\t\treturn text.join('');\n\t\t},\n\t\tlegend: {\n\t\t\tlabels: {\n\t\t\t\tgenerateLabels: function(chart) {\n\t\t\t\t\tvar data = chart.data;\n\t\t\t\t\tif (data.labels.length && data.datasets.length) {\n\t\t\t\t\t\treturn data.labels.map(function(label, i) {\n\t\t\t\t\t\t\tvar meta = chart.getDatasetMeta(0);\n\t\t\t\t\t\t\tvar ds = data.datasets[0];\n\t\t\t\t\t\t\tvar arc = meta.data[i];\n\t\t\t\t\t\t\tvar custom = arc.custom || {};\n\t\t\t\t\t\t\tvar getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;\n\t\t\t\t\t\t\tvar arcOpts = chart.options.elements.arc;\n\t\t\t\t\t\t\tvar fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);\n\t\t\t\t\t\t\tvar stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);\n\t\t\t\t\t\t\tvar bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);\n\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\ttext: label,\n\t\t\t\t\t\t\t\tfillStyle: fill,\n\t\t\t\t\t\t\t\tstrokeStyle: stroke,\n\t\t\t\t\t\t\t\tlineWidth: bw,\n\t\t\t\t\t\t\t\thidden: isNaN(ds.data[i]) || meta.data[i].hidden,\n\n\t\t\t\t\t\t\t\t// Extra data used for toggling the correct item\n\t\t\t\t\t\t\t\tindex: i\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tonClick: function(e, legendItem) {\n\t\t\t\tvar index = legendItem.index;\n\t\t\t\tvar chart = this.chart;\n\t\t\t\tvar i, ilen, meta;\n\n\t\t\t\tfor (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {\n\t\t\t\t\tmeta = chart.getDatasetMeta(i);\n\t\t\t\t\tmeta.data[index].hidden = !meta.data[index].hidden;\n\t\t\t\t}\n\n\t\t\t\tchart.update();\n\t\t\t}\n\t\t},\n\n\t\t// Need to override these to give a nice default\n\t\ttooltips: {\n\t\t\tcallbacks: {\n\t\t\t\ttitle: function() {\n\t\t\t\t\treturn '';\n\t\t\t\t},\n\t\t\t\tlabel: function(tooltipItem, data) {\n\t\t\t\t\treturn data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tChart.controllers.polarArea = Chart.DatasetController.extend({\n\n\t\tdataElementType: Chart.elements.Arc,\n\n\t\tlinkScales: helpers.noop,\n\n\t\tupdate: function(reset) {\n\t\t\tvar me = this;\n\t\t\tvar chart = me.chart;\n\t\t\tvar chartArea = chart.chartArea;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar opts = chart.options;\n\t\t\tvar arcOpts = opts.elements.arc;\n\t\t\tvar minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);\n\t\t\tchart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);\n\t\t\tchart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);\n\t\t\tchart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();\n\n\t\t\tme.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);\n\t\t\tme.innerRadius = me.outerRadius - chart.radiusLength;\n\n\t\t\tmeta.count = me.countVisibleElements();\n\n\t\t\thelpers.each(meta.data, function(arc, index) {\n\t\t\t\tme.updateElement(arc, index, reset);\n\t\t\t});\n\t\t},\n\n\t\tupdateElement: function(arc, index, reset) {\n\t\t\tvar me = this;\n\t\t\tvar chart = me.chart;\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar opts = chart.options;\n\t\t\tvar animationOpts = opts.animation;\n\t\t\tvar scale = chart.scale;\n\t\t\tvar getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;\n\t\t\tvar labels = chart.data.labels;\n\n\t\t\tvar circumference = me.calculateCircumference(dataset.data[index]);\n\t\t\tvar centerX = scale.xCenter;\n\t\t\tvar centerY = scale.yCenter;\n\n\t\t\t// If there is NaN data before us, we need to calculate the starting angle correctly.\n\t\t\t// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data\n\t\t\tvar visibleCount = 0;\n\t\t\tvar meta = me.getMeta();\n\t\t\tfor (var i = 0; i < index; ++i) {\n\t\t\t\tif (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {\n\t\t\t\t\t++visibleCount;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// var negHalfPI = -0.5 * Math.PI;\n\t\t\tvar datasetStartAngle = opts.startAngle;\n\t\t\tvar distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);\n\t\t\tvar startAngle = datasetStartAngle + (circumference * visibleCount);\n\t\t\tvar endAngle = startAngle + (arc.hidden ? 0 : circumference);\n\n\t\t\tvar resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);\n\n\t\t\thelpers.extend(arc, {\n\t\t\t\t// Utility\n\t\t\t\t_datasetIndex: me.index,\n\t\t\t\t_index: index,\n\t\t\t\t_scale: scale,\n\n\t\t\t\t// Desired view properties\n\t\t\t\t_model: {\n\t\t\t\t\tx: centerX,\n\t\t\t\t\ty: centerY,\n\t\t\t\t\tinnerRadius: 0,\n\t\t\t\t\touterRadius: reset ? resetRadius : distance,\n\t\t\t\t\tstartAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,\n\t\t\t\t\tendAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,\n\t\t\t\t\tlabel: getValueAtIndexOrDefault(labels, index, labels[index])\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Apply border and fill style\n\t\t\tme.removeHoverStyle(arc);\n\n\t\t\tarc.pivot();\n\t\t},\n\n\t\tremoveHoverStyle: function(arc) {\n\t\t\tChart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);\n\t\t},\n\n\t\tcountVisibleElements: function() {\n\t\t\tvar dataset = this.getDataset();\n\t\t\tvar meta = this.getMeta();\n\t\t\tvar count = 0;\n\n\t\t\thelpers.each(meta.data, function(element, index) {\n\t\t\t\tif (!isNaN(dataset.data[index]) && !element.hidden) {\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn count;\n\t\t},\n\n\t\tcalculateCircumference: function(value) {\n\t\t\tvar count = this.getMeta().count;\n\t\t\tif (count > 0 && !isNaN(value)) {\n\t\t\t\treturn (2 * Math.PI) / count;\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t});\n};\n\n},{}],20:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.radar = {\n\t\taspectRatio: 1,\n\t\tscale: {\n\t\t\ttype: 'radialLinear'\n\t\t},\n\t\telements: {\n\t\t\tline: {\n\t\t\t\ttension: 0 // no bezier in radar\n\t\t\t}\n\t\t}\n\t};\n\n\tChart.controllers.radar = Chart.DatasetController.extend({\n\n\t\tdatasetElementType: Chart.elements.Line,\n\n\t\tdataElementType: Chart.elements.Point,\n\n\t\tlinkScales: helpers.noop,\n\n\t\tupdate: function(reset) {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar line = meta.dataset;\n\t\t\tvar points = meta.data;\n\t\t\tvar custom = line.custom || {};\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar lineElementOptions = me.chart.options.elements.line;\n\t\t\tvar scale = me.chart.scale;\n\n\t\t\t// Compatibility: If the properties are defined with only the old name, use those values\n\t\t\tif ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {\n\t\t\t\tdataset.lineTension = dataset.tension;\n\t\t\t}\n\n\t\t\thelpers.extend(meta.dataset, {\n\t\t\t\t// Utility\n\t\t\t\t_datasetIndex: me.index,\n\t\t\t\t// Data\n\t\t\t\t_children: points,\n\t\t\t\t_loop: true,\n\t\t\t\t// Model\n\t\t\t\t_model: {\n\t\t\t\t\t// Appearance\n\t\t\t\t\ttension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),\n\t\t\t\t\tbackgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),\n\t\t\t\t\tborderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),\n\t\t\t\t\tborderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),\n\t\t\t\t\tfill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),\n\t\t\t\t\tborderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),\n\t\t\t\t\tborderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),\n\t\t\t\t\tborderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),\n\t\t\t\t\tborderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),\n\n\t\t\t\t\t// Scale\n\t\t\t\t\tscaleTop: scale.top,\n\t\t\t\t\tscaleBottom: scale.bottom,\n\t\t\t\t\tscaleZero: scale.getBasePosition()\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tmeta.dataset.pivot();\n\n\t\t\t// Update Points\n\t\t\thelpers.each(points, function(point, index) {\n\t\t\t\tme.updateElement(point, index, reset);\n\t\t\t}, me);\n\n\t\t\t// Update bezier control points\n\t\t\tme.updateBezierControlPoints();\n\t\t},\n\t\tupdateElement: function(point, index, reset) {\n\t\t\tvar me = this;\n\t\t\tvar custom = point.custom || {};\n\t\t\tvar dataset = me.getDataset();\n\t\t\tvar scale = me.chart.scale;\n\t\t\tvar pointElementOptions = me.chart.options.elements.point;\n\t\t\tvar pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);\n\n\t\t\thelpers.extend(point, {\n\t\t\t\t// Utility\n\t\t\t\t_datasetIndex: me.index,\n\t\t\t\t_index: index,\n\t\t\t\t_scale: scale,\n\n\t\t\t\t// Desired view properties\n\t\t\t\t_model: {\n\t\t\t\t\tx: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales\n\t\t\t\t\ty: reset ? scale.yCenter : pointPosition.y,\n\n\t\t\t\t\t// Appearance\n\t\t\t\t\ttension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),\n\t\t\t\t\tradius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),\n\t\t\t\t\tbackgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),\n\t\t\t\t\tborderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),\n\t\t\t\t\tborderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),\n\t\t\t\t\tpointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),\n\n\t\t\t\t\t// Tooltip\n\t\t\t\t\thitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tpoint._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));\n\t\t},\n\t\tupdateBezierControlPoints: function() {\n\t\t\tvar chartArea = this.chart.chartArea;\n\t\t\tvar meta = this.getMeta();\n\n\t\t\thelpers.each(meta.data, function(point, index) {\n\t\t\t\tvar model = point._model;\n\t\t\t\tvar controlPoints = helpers.splineCurve(\n\t\t\t\t\thelpers.previousItem(meta.data, index, true)._model,\n\t\t\t\t\tmodel,\n\t\t\t\t\thelpers.nextItem(meta.data, index, true)._model,\n\t\t\t\t\tmodel.tension\n\t\t\t\t);\n\n\t\t\t\t// Prevent the bezier going outside of the bounds of the graph\n\t\t\t\tmodel.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);\n\t\t\t\tmodel.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);\n\n\t\t\t\tmodel.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);\n\t\t\t\tmodel.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);\n\n\t\t\t\t// Now pivot the point for animation\n\t\t\t\tpoint.pivot();\n\t\t\t});\n\t\t},\n\n\t\tdraw: function(ease) {\n\t\t\tvar meta = this.getMeta();\n\t\t\tvar easingDecimal = ease || 1;\n\n\t\t\t// Transition Point Locations\n\t\t\thelpers.each(meta.data, function(point) {\n\t\t\t\tpoint.transition(easingDecimal);\n\t\t\t});\n\n\t\t\t// Transition and Draw the line\n\t\t\tmeta.dataset.transition(easingDecimal).draw();\n\n\t\t\t// Draw the points\n\t\t\thelpers.each(meta.data, function(point) {\n\t\t\t\tpoint.draw();\n\t\t\t});\n\t\t},\n\n\t\tsetHoverStyle: function(point) {\n\t\t\t// Point\n\t\t\tvar dataset = this.chart.data.datasets[point._datasetIndex];\n\t\t\tvar custom = point.custom || {};\n\t\t\tvar index = point._index;\n\t\t\tvar model = point._model;\n\n\t\t\tmodel.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);\n\t\t\tmodel.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));\n\t\t\tmodel.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));\n\t\t\tmodel.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);\n\t\t},\n\n\t\tremoveHoverStyle: function(point) {\n\t\t\tvar dataset = this.chart.data.datasets[point._datasetIndex];\n\t\t\tvar custom = point.custom || {};\n\t\t\tvar index = point._index;\n\t\t\tvar model = point._model;\n\t\t\tvar pointElementOptions = this.chart.options.elements.point;\n\n\t\t\tmodel.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius);\n\t\t\tmodel.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);\n\t\t\tmodel.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);\n\t\t\tmodel.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);\n\t\t}\n\t});\n};\n\n},{}],21:[function(require,module,exports){\n/* global window: false */\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.global.animation = {\n\t\tduration: 1000,\n\t\teasing: 'easeOutQuart',\n\t\tonProgress: helpers.noop,\n\t\tonComplete: helpers.noop\n\t};\n\n\tChart.Animation = Chart.Element.extend({\n\t\tcurrentStep: null, // the current animation step\n\t\tnumSteps: 60, // default number of steps\n\t\teasing: '', // the easing to use for this animation\n\t\trender: null, // render function used by the animation service\n\n\t\tonAnimationProgress: null, // user specified callback to fire on each step of the animation\n\t\tonAnimationComplete: null // user specified callback to fire when the animation finishes\n\t});\n\n\tChart.animationService = {\n\t\tframeDuration: 17,\n\t\tanimations: [],\n\t\tdropFrames: 0,\n\t\trequest: null,\n\n\t\t/**\n\t\t * @function Chart.animationService.addAnimation\n\t\t * @param chartInstance {ChartController} the chart to animate\n\t\t * @param animationObject {IAnimation} the animation that we will animate\n\t\t * @param duration {Number} length of animation in ms\n\t\t * @param lazy {Boolean} if true, the chart is not marked as animating to enable more responsive interactions\n\t\t */\n\t\taddAnimation: function(chartInstance, animationObject, duration, lazy) {\n\t\t\tvar me = this;\n\n\t\t\tif (!lazy) {\n\t\t\t\tchartInstance.animating = true;\n\t\t\t}\n\n\t\t\tfor (var index = 0; index < me.animations.length; ++index) {\n\t\t\t\tif (me.animations[index].chartInstance === chartInstance) {\n\t\t\t\t\t// replacing an in progress animation\n\t\t\t\t\tme.animations[index].animationObject = animationObject;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tme.animations.push({\n\t\t\t\tchartInstance: chartInstance,\n\t\t\t\tanimationObject: animationObject\n\t\t\t});\n\n\t\t\t// If there are no animations queued, manually kickstart a digest, for lack of a better word\n\t\t\tif (me.animations.length === 1) {\n\t\t\t\tme.requestAnimationFrame();\n\t\t\t}\n\t\t},\n\t\t// Cancel the animation for a given chart instance\n\t\tcancelAnimation: function(chartInstance) {\n\t\t\tvar index = helpers.findIndex(this.animations, function(animationWrapper) {\n\t\t\t\treturn animationWrapper.chartInstance === chartInstance;\n\t\t\t});\n\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.animations.splice(index, 1);\n\t\t\t\tchartInstance.animating = false;\n\t\t\t}\n\t\t},\n\t\trequestAnimationFrame: function() {\n\t\t\tvar me = this;\n\t\t\tif (me.request === null) {\n\t\t\t\t// Skip animation frame requests until the active one is executed.\n\t\t\t\t// This can happen when processing mouse events, e.g. 'mousemove'\n\t\t\t\t// and 'mouseout' events will trigger multiple renders.\n\t\t\t\tme.request = helpers.requestAnimFrame.call(window, function() {\n\t\t\t\t\tme.request = null;\n\t\t\t\t\tme.startDigest();\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tstartDigest: function() {\n\t\t\tvar me = this;\n\n\t\t\tvar startTime = Date.now();\n\t\t\tvar framesToDrop = 0;\n\n\t\t\tif (me.dropFrames > 1) {\n\t\t\t\tframesToDrop = Math.floor(me.dropFrames);\n\t\t\t\tme.dropFrames = me.dropFrames % 1;\n\t\t\t}\n\n\t\t\tvar i = 0;\n\t\t\twhile (i < me.animations.length) {\n\t\t\t\tif (me.animations[i].animationObject.currentStep === null) {\n\t\t\t\t\tme.animations[i].animationObject.currentStep = 0;\n\t\t\t\t}\n\n\t\t\t\tme.animations[i].animationObject.currentStep += 1 + framesToDrop;\n\n\t\t\t\tif (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) {\n\t\t\t\t\tme.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps;\n\t\t\t\t}\n\n\t\t\t\tme.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject);\n\t\t\t\tif (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) {\n\t\t\t\t\tme.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]);\n\t\t\t\t}\n\n\t\t\t\tif (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) {\n\t\t\t\t\tif (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) {\n\t\t\t\t\t\tme.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]);\n\t\t\t\t\t}\n\n\t\t\t\t\t// executed the last frame. Remove the animation.\n\t\t\t\t\tme.animations[i].chartInstance.animating = false;\n\n\t\t\t\t\tme.animations.splice(i, 1);\n\t\t\t\t} else {\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar endTime = Date.now();\n\t\t\tvar dropFrames = (endTime - startTime) / me.frameDuration;\n\n\t\t\tme.dropFrames += dropFrames;\n\n\t\t\t// Do we have more stuff to animate?\n\t\t\tif (me.animations.length > 0) {\n\t\t\t\tme.requestAnimationFrame();\n\t\t\t}\n\t\t}\n\t};\n};\n\n},{}],22:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\t// Global Chart canvas helpers object for drawing items to canvas\n\tvar helpers = Chart.canvasHelpers = {};\n\n\thelpers.drawPoint = function(ctx, pointStyle, radius, x, y) {\n\t\tvar type, edgeLength, xOffset, yOffset, height, size;\n\n\t\tif (typeof pointStyle === 'object') {\n\t\t\ttype = pointStyle.toString();\n\t\t\tif (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {\n\t\t\t\tctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (isNaN(radius) || radius <= 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (pointStyle) {\n\t\t// Default includes circle\n\t\tdefault:\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(x, y, radius, 0, Math.PI * 2);\n\t\t\tctx.closePath();\n\t\t\tctx.fill();\n\t\t\tbreak;\n\t\tcase 'triangle':\n\t\t\tctx.beginPath();\n\t\t\tedgeLength = 3 * radius / Math.sqrt(3);\n\t\t\theight = edgeLength * Math.sqrt(3) / 2;\n\t\t\tctx.moveTo(x - edgeLength / 2, y + height / 3);\n\t\t\tctx.lineTo(x + edgeLength / 2, y + height / 3);\n\t\t\tctx.lineTo(x, y - 2 * height / 3);\n\t\t\tctx.closePath();\n\t\t\tctx.fill();\n\t\t\tbreak;\n\t\tcase 'rect':\n\t\t\tsize = 1 / Math.SQRT2 * radius;\n\t\t\tctx.beginPath();\n\t\t\tctx.fillRect(x - size, y - size, 2 * size, 2 * size);\n\t\t\tctx.strokeRect(x - size, y - size, 2 * size, 2 * size);\n\t\t\tbreak;\n\t\tcase 'rectRounded':\n\t\t\tvar offset = radius / Math.SQRT2;\n\t\t\tvar leftX = x - offset;\n\t\t\tvar topY = y - offset;\n\t\t\tvar sideSize = Math.SQRT2 * radius;\n\t\t\tChart.helpers.drawRoundedRectangle(ctx, leftX, topY, sideSize, sideSize, radius / 2);\n\t\t\tctx.fill();\n\t\t\tbreak;\n\t\tcase 'rectRot':\n\t\t\tsize = 1 / Math.SQRT2 * radius;\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x - size, y);\n\t\t\tctx.lineTo(x, y + size);\n\t\t\tctx.lineTo(x + size, y);\n\t\t\tctx.lineTo(x, y - size);\n\t\t\tctx.closePath();\n\t\t\tctx.fill();\n\t\t\tbreak;\n\t\tcase 'cross':\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x, y + radius);\n\t\t\tctx.lineTo(x, y - radius);\n\t\t\tctx.moveTo(x - radius, y);\n\t\t\tctx.lineTo(x + radius, y);\n\t\t\tctx.closePath();\n\t\t\tbreak;\n\t\tcase 'crossRot':\n\t\t\tctx.beginPath();\n\t\t\txOffset = Math.cos(Math.PI / 4) * radius;\n\t\t\tyOffset = Math.sin(Math.PI / 4) * radius;\n\t\t\tctx.moveTo(x - xOffset, y - yOffset);\n\t\t\tctx.lineTo(x + xOffset, y + yOffset);\n\t\t\tctx.moveTo(x - xOffset, y + yOffset);\n\t\t\tctx.lineTo(x + xOffset, y - yOffset);\n\t\t\tctx.closePath();\n\t\t\tbreak;\n\t\tcase 'star':\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x, y + radius);\n\t\t\tctx.lineTo(x, y - radius);\n\t\t\tctx.moveTo(x - radius, y);\n\t\t\tctx.lineTo(x + radius, y);\n\t\t\txOffset = Math.cos(Math.PI / 4) * radius;\n\t\t\tyOffset = Math.sin(Math.PI / 4) * radius;\n\t\t\tctx.moveTo(x - xOffset, y - yOffset);\n\t\t\tctx.lineTo(x + xOffset, y + yOffset);\n\t\t\tctx.moveTo(x - xOffset, y + yOffset);\n\t\t\tctx.lineTo(x + xOffset, y - yOffset);\n\t\t\tctx.closePath();\n\t\t\tbreak;\n\t\tcase 'line':\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x - radius, y);\n\t\t\tctx.lineTo(x + radius, y);\n\t\t\tctx.closePath();\n\t\t\tbreak;\n\t\tcase 'dash':\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x, y);\n\t\t\tctx.lineTo(x + radius, y);\n\t\t\tctx.closePath();\n\t\t\tbreak;\n\t\t}\n\n\t\tctx.stroke();\n\t};\n\n\thelpers.clipArea = function(ctx, clipArea) {\n\t\tctx.save();\n\t\tctx.beginPath();\n\t\tctx.rect(clipArea.left, clipArea.top, clipArea.right - clipArea.left, clipArea.bottom - clipArea.top);\n\t\tctx.clip();\n\t};\n\n\thelpers.unclipArea = function(ctx) {\n\t\tctx.restore();\n\t};\n\n};\n\n},{}],23:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\tvar plugins = Chart.plugins;\n\tvar platform = Chart.platform;\n\n\t// Create a dictionary of chart types, to allow for extension of existing types\n\tChart.types = {};\n\n\t// Store a reference to each instance - allowing us to globally resize chart instances on window resize.\n\t// Destroy method on the chart will remove the instance of the chart from this reference.\n\tChart.instances = {};\n\n\t// Controllers available for dataset visualization eg. bar, line, slice, etc.\n\tChart.controllers = {};\n\n\t/**\n\t * Initializes the given config with global and chart default values.\n\t */\n\tfunction initConfig(config) {\n\t\tconfig = config || {};\n\n\t\t// Do NOT use configMerge() for the data object because this method merges arrays\n\t\t// and so would change references to labels and datasets, preventing data updates.\n\t\tvar data = config.data = config.data || {};\n\t\tdata.datasets = data.datasets || [];\n\t\tdata.labels = data.labels || [];\n\n\t\tconfig.options = helpers.configMerge(\n\t\t\tChart.defaults.global,\n\t\t\tChart.defaults[config.type],\n\t\t\tconfig.options || {});\n\n\t\treturn config;\n\t}\n\n\t/**\n\t * Updates the config of the chart\n\t * @param chart {Chart.Controller} chart to update the options for\n\t */\n\tfunction updateConfig(chart) {\n\t\tvar newOptions = chart.options;\n\n\t\t// Update Scale(s) with options\n\t\tif (newOptions.scale) {\n\t\t\tchart.scale.options = newOptions.scale;\n\t\t} else if (newOptions.scales) {\n\t\t\tnewOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {\n\t\t\t\tchart.scales[scaleOptions.id].options = scaleOptions;\n\t\t\t});\n\t\t}\n\n\t\t// Tooltip\n\t\tchart.tooltip._options = newOptions.tooltips;\n\t}\n\n\t/**\n\t * @class Chart.Controller\n\t * The main controller of a chart.\n\t */\n\tChart.Controller = function(item, config, instance) {\n\t\tvar me = this;\n\n\t\tconfig = initConfig(config);\n\n\t\tvar context = platform.acquireContext(item, config);\n\t\tvar canvas = context && context.canvas;\n\t\tvar height = canvas && canvas.height;\n\t\tvar width = canvas && canvas.width;\n\n\t\tinstance.ctx = context;\n\t\tinstance.canvas = canvas;\n\t\tinstance.config = config;\n\t\tinstance.width = width;\n\t\tinstance.height = height;\n\t\tinstance.aspectRatio = height? width / height : null;\n\n\t\tme.id = helpers.uid();\n\t\tme.chart = instance;\n\t\tme.config = config;\n\t\tme.options = config.options;\n\t\tme._bufferedRender = false;\n\n\t\t// Add the chart instance to the global namespace\n\t\tChart.instances[me.id] = me;\n\n\t\tObject.defineProperty(me, 'data', {\n\t\t\tget: function() {\n\t\t\t\treturn me.config.data;\n\t\t\t}\n\t\t});\n\n\t\tif (!context || !canvas) {\n\t\t\t// The given item is not a compatible context2d element, let's return before finalizing\n\t\t\t// the chart initialization but after setting basic chart / controller properties that\n\t\t\t// can help to figure out that the chart is not valid (e.g chart.canvas !== null);\n\t\t\t// https://github.com/chartjs/Chart.js/issues/2807\n\t\t\tconsole.error(\"Failed to create chart: can't acquire context from the given item\");\n\t\t\treturn me;\n\t\t}\n\n\t\tme.initialize();\n\t\tme.update();\n\n\t\treturn me;\n\t};\n\n\thelpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller.prototype */ {\n\t\tinitialize: function() {\n\t\t\tvar me = this;\n\n\t\t\t// Before init plugin notification\n\t\t\tplugins.notify(me, 'beforeInit');\n\n\t\t\thelpers.retinaScale(me.chart);\n\n\t\t\tme.bindEvents();\n\n\t\t\tif (me.options.responsive) {\n\t\t\t\t// Initial resize before chart draws (must be silent to preserve initial animations).\n\t\t\t\tme.resize(true);\n\t\t\t}\n\n\t\t\t// Make sure scales have IDs and are built before we build any controllers.\n\t\t\tme.ensureScalesHaveIDs();\n\t\t\tme.buildScales();\n\t\t\tme.initToolTip();\n\n\t\t\t// After init plugin notification\n\t\t\tplugins.notify(me, 'afterInit');\n\n\t\t\treturn me;\n\t\t},\n\n\t\tclear: function() {\n\t\t\thelpers.clear(this.chart);\n\t\t\treturn this;\n\t\t},\n\n\t\tstop: function() {\n\t\t\t// Stops any current animation loop occurring\n\t\t\tChart.animationService.cancelAnimation(this);\n\t\t\treturn this;\n\t\t},\n\n\t\tresize: function(silent) {\n\t\t\tvar me = this;\n\t\t\tvar chart = me.chart;\n\t\t\tvar options = me.options;\n\t\t\tvar canvas = chart.canvas;\n\t\t\tvar aspectRatio = (options.maintainAspectRatio && chart.aspectRatio) || null;\n\n\t\t\t// the canvas render width and height will be casted to integers so make sure that\n\t\t\t// the canvas display style uses the same integer values to avoid blurring effect.\n\t\t\tvar newWidth = Math.floor(helpers.getMaximumWidth(canvas));\n\t\t\tvar newHeight = Math.floor(aspectRatio? newWidth / aspectRatio : helpers.getMaximumHeight(canvas));\n\n\t\t\tif (chart.width === newWidth && chart.height === newHeight) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcanvas.width = chart.width = newWidth;\n\t\t\tcanvas.height = chart.height = newHeight;\n\t\t\tcanvas.style.width = newWidth + 'px';\n\t\t\tcanvas.style.height = newHeight + 'px';\n\n\t\t\thelpers.retinaScale(chart);\n\n\t\t\tif (!silent) {\n\t\t\t\t// Notify any plugins about the resize\n\t\t\t\tvar newSize = {width: newWidth, height: newHeight};\n\t\t\t\tplugins.notify(me, 'resize', [newSize]);\n\n\t\t\t\t// Notify of resize\n\t\t\t\tif (me.options.onResize) {\n\t\t\t\t\tme.options.onResize(me, newSize);\n\t\t\t\t}\n\n\t\t\t\tme.stop();\n\t\t\t\tme.update(me.options.responsiveAnimationDuration);\n\t\t\t}\n\t\t},\n\n\t\tensureScalesHaveIDs: function() {\n\t\t\tvar options = this.options;\n\t\t\tvar scalesOptions = options.scales || {};\n\t\t\tvar scaleOptions = options.scale;\n\n\t\t\thelpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {\n\t\t\t\txAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);\n\t\t\t});\n\n\t\t\thelpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {\n\t\t\t\tyAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);\n\t\t\t});\n\n\t\t\tif (scaleOptions) {\n\t\t\t\tscaleOptions.id = scaleOptions.id || 'scale';\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Builds a map of scale ID to scale object for future lookup.\n\t\t */\n\t\tbuildScales: function() {\n\t\t\tvar me = this;\n\t\t\tvar options = me.options;\n\t\t\tvar scales = me.scales = {};\n\t\t\tvar items = [];\n\n\t\t\tif (options.scales) {\n\t\t\t\titems = items.concat(\n\t\t\t\t\t(options.scales.xAxes || []).map(function(xAxisOptions) {\n\t\t\t\t\t\treturn {options: xAxisOptions, dtype: 'category'};\n\t\t\t\t\t}),\n\t\t\t\t\t(options.scales.yAxes || []).map(function(yAxisOptions) {\n\t\t\t\t\t\treturn {options: yAxisOptions, dtype: 'linear'};\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (options.scale) {\n\t\t\t\titems.push({options: options.scale, dtype: 'radialLinear', isDefault: true});\n\t\t\t}\n\n\t\t\thelpers.each(items, function(item) {\n\t\t\t\tvar scaleOptions = item.options;\n\t\t\t\tvar scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);\n\t\t\t\tvar scaleClass = Chart.scaleService.getScaleConstructor(scaleType);\n\t\t\t\tif (!scaleClass) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar scale = new scaleClass({\n\t\t\t\t\tid: scaleOptions.id,\n\t\t\t\t\toptions: scaleOptions,\n\t\t\t\t\tctx: me.chart.ctx,\n\t\t\t\t\tchart: me\n\t\t\t\t});\n\n\t\t\t\tscales[scale.id] = scale;\n\n\t\t\t\t// TODO(SB): I think we should be able to remove this custom case (options.scale)\n\t\t\t\t// and consider it as a regular scale part of the \"scales\"\" map only! This would\n\t\t\t\t// make the logic easier and remove some useless? custom code.\n\t\t\t\tif (item.isDefault) {\n\t\t\t\t\tme.scale = scale;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tChart.scaleService.addScalesToLayout(this);\n\t\t},\n\n\t\tbuildOrUpdateControllers: function() {\n\t\t\tvar me = this;\n\t\t\tvar types = [];\n\t\t\tvar newControllers = [];\n\n\t\t\thelpers.each(me.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tvar meta = me.getDatasetMeta(datasetIndex);\n\t\t\t\tif (!meta.type) {\n\t\t\t\t\tmeta.type = dataset.type || me.config.type;\n\t\t\t\t}\n\n\t\t\t\ttypes.push(meta.type);\n\n\t\t\t\tif (meta.controller) {\n\t\t\t\t\tmeta.controller.updateIndex(datasetIndex);\n\t\t\t\t} else {\n\t\t\t\t\tmeta.controller = new Chart.controllers[meta.type](me, datasetIndex);\n\t\t\t\t\tnewControllers.push(meta.controller);\n\t\t\t\t}\n\t\t\t}, me);\n\n\t\t\tif (types.length > 1) {\n\t\t\t\tfor (var i = 1; i < types.length; i++) {\n\t\t\t\t\tif (types[i] !== types[i - 1]) {\n\t\t\t\t\t\tme.isCombo = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn newControllers;\n\t\t},\n\n\t\t/**\n\t\t * Reset the elements of all datasets\n\t\t * @private\n\t\t */\n\t\tresetElements: function() {\n\t\t\tvar me = this;\n\t\t\thelpers.each(me.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tme.getDatasetMeta(datasetIndex).controller.reset();\n\t\t\t}, me);\n\t\t},\n\n\t\t/**\n\t\t* Resets the chart back to it's state before the initial animation\n\t\t*/\n\t\treset: function() {\n\t\t\tthis.resetElements();\n\t\t\tthis.tooltip.initialize();\n\t\t},\n\n\t\tupdate: function(animationDuration, lazy) {\n\t\t\tvar me = this;\n\n\t\t\tupdateConfig(me);\n\n\t\t\tif (plugins.notify(me, 'beforeUpdate') === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// In case the entire data object changed\n\t\t\tme.tooltip._data = me.data;\n\n\t\t\t// Make sure dataset controllers are updated and new controllers are reset\n\t\t\tvar newControllers = me.buildOrUpdateControllers();\n\n\t\t\t// Make sure all dataset controllers have correct meta data counts\n\t\t\thelpers.each(me.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tme.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();\n\t\t\t}, me);\n\n\t\t\tme.updateLayout();\n\n\t\t\t// Can only reset the new controllers after the scales have been updated\n\t\t\thelpers.each(newControllers, function(controller) {\n\t\t\t\tcontroller.reset();\n\t\t\t});\n\n\t\t\tme.updateDatasets();\n\n\t\t\t// Do this before render so that any plugins that need final scale updates can use it\n\t\t\tplugins.notify(me, 'afterUpdate');\n\n\t\t\tif (me._bufferedRender) {\n\t\t\t\tme._bufferedRequest = {\n\t\t\t\t\tlazy: lazy,\n\t\t\t\t\tduration: animationDuration\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tme.render(animationDuration, lazy);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`\n\t\t * hook, in which case, plugins will not be called on `afterLayout`.\n\t\t * @private\n\t\t */\n\t\tupdateLayout: function() {\n\t\t\tvar me = this;\n\n\t\t\tif (plugins.notify(me, 'beforeLayout') === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tChart.layoutService.update(this, this.chart.width, this.chart.height);\n\n\t\t\t/**\n\t\t\t * Provided for backward compatibility, use `afterLayout` instead.\n\t\t\t * @method IPlugin#afterScaleUpdate\n\t\t\t * @deprecated since version 2.5.0\n\t\t\t * @todo remove at version 3\n\t\t\t */\n\t\t\tplugins.notify(me, 'afterScaleUpdate');\n\t\t\tplugins.notify(me, 'afterLayout');\n\t\t},\n\n\t\t/**\n\t\t * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`\n\t\t * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.\n\t\t * @private\n\t\t */\n\t\tupdateDatasets: function() {\n\t\t\tvar me = this;\n\n\t\t\tif (plugins.notify(me, 'beforeDatasetsUpdate') === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {\n\t\t\t\tme.getDatasetMeta(i).controller.update();\n\t\t\t}\n\n\t\t\tplugins.notify(me, 'afterDatasetsUpdate');\n\t\t},\n\n\t\trender: function(duration, lazy) {\n\t\t\tvar me = this;\n\n\t\t\tif (plugins.notify(me, 'beforeRender') === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar animationOptions = me.options.animation;\n\t\t\tvar onComplete = function() {\n\t\t\t\tplugins.notify(me, 'afterRender');\n\t\t\t\tvar callback = animationOptions && animationOptions.onComplete;\n\t\t\t\tif (callback && callback.call) {\n\t\t\t\t\tcallback.call(me);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {\n\t\t\t\tvar animation = new Chart.Animation();\n\t\t\t\tanimation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps\n\t\t\t\tanimation.easing = animationOptions.easing;\n\n\t\t\t\t// render function\n\t\t\t\tanimation.render = function(chartInstance, animationObject) {\n\t\t\t\t\tvar easingFunction = helpers.easingEffects[animationObject.easing];\n\t\t\t\t\tvar stepDecimal = animationObject.currentStep / animationObject.numSteps;\n\t\t\t\t\tvar easeDecimal = easingFunction(stepDecimal);\n\n\t\t\t\t\tchartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);\n\t\t\t\t};\n\n\t\t\t\t// user events\n\t\t\t\tanimation.onAnimationProgress = animationOptions.onProgress;\n\t\t\t\tanimation.onAnimationComplete = onComplete;\n\n\t\t\t\tChart.animationService.addAnimation(me, animation, duration, lazy);\n\t\t\t} else {\n\t\t\t\tme.draw();\n\t\t\t\tonComplete();\n\t\t\t}\n\n\t\t\treturn me;\n\t\t},\n\n\t\tdraw: function(easingValue) {\n\t\t\tvar me = this;\n\n\t\t\tme.clear();\n\n\t\t\tif (easingValue === undefined || easingValue === null) {\n\t\t\t\teasingValue = 1;\n\t\t\t}\n\n\t\t\tif (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Draw all the scales\n\t\t\thelpers.each(me.boxes, function(box) {\n\t\t\t\tbox.draw(me.chartArea);\n\t\t\t}, me);\n\n\t\t\tif (me.scale) {\n\t\t\t\tme.scale.draw();\n\t\t\t}\n\n\t\t\tme.drawDatasets(easingValue);\n\n\t\t\t// Finally draw the tooltip\n\t\t\tme.tooltip.transition(easingValue).draw();\n\n\t\t\tplugins.notify(me, 'afterDraw', [easingValue]);\n\t\t},\n\n\t\t/**\n\t\t * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`\n\t\t * hook, in which case, plugins will not be called on `afterDatasetsDraw`.\n\t\t * @private\n\t\t */\n\t\tdrawDatasets: function(easingValue) {\n\t\t\tvar me = this;\n\n\t\t\tif (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Draw each dataset via its respective controller (reversed to support proper line stacking)\n\t\t\thelpers.each(me.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tif (me.isDatasetVisible(datasetIndex)) {\n\t\t\t\t\tme.getDatasetMeta(datasetIndex).controller.draw(easingValue);\n\t\t\t\t}\n\t\t\t}, me, true);\n\n\t\t\tplugins.notify(me, 'afterDatasetsDraw', [easingValue]);\n\t\t},\n\n\t\t// Get the single element that was clicked on\n\t\t// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw\n\t\tgetElementAtEvent: function(e) {\n\t\t\treturn Chart.Interaction.modes.single(this, e);\n\t\t},\n\n\t\tgetElementsAtEvent: function(e) {\n\t\t\treturn Chart.Interaction.modes.label(this, e, {intersect: true});\n\t\t},\n\n\t\tgetElementsAtXAxis: function(e) {\n\t\t\treturn Chart.Interaction.modes['x-axis'](this, e, {intersect: true});\n\t\t},\n\n\t\tgetElementsAtEventForMode: function(e, mode, options) {\n\t\t\tvar method = Chart.Interaction.modes[mode];\n\t\t\tif (typeof method === 'function') {\n\t\t\t\treturn method(this, e, options);\n\t\t\t}\n\n\t\t\treturn [];\n\t\t},\n\n\t\tgetDatasetAtEvent: function(e) {\n\t\t\treturn Chart.Interaction.modes.dataset(this, e, {intersect: true});\n\t\t},\n\n\t\tgetDatasetMeta: function(datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar dataset = me.data.datasets[datasetIndex];\n\t\t\tif (!dataset._meta) {\n\t\t\t\tdataset._meta = {};\n\t\t\t}\n\n\t\t\tvar meta = dataset._meta[me.id];\n\t\t\tif (!meta) {\n\t\t\t\tmeta = dataset._meta[me.id] = {\n\t\t\t\t\ttype: null,\n\t\t\t\t\tdata: [],\n\t\t\t\t\tdataset: null,\n\t\t\t\t\tcontroller: null,\n\t\t\t\t\thidden: null,\t\t\t// See isDatasetVisible() comment\n\t\t\t\t\txAxisID: null,\n\t\t\t\t\tyAxisID: null\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn meta;\n\t\t},\n\n\t\tgetVisibleDatasetCount: function() {\n\t\t\tvar count = 0;\n\t\t\tfor (var i = 0, ilen = this.data.datasets.length; i 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tarrayEvents.forEach(function(key) {\n\t\t\tdelete array[key];\n\t\t});\n\n\t\tdelete array._chartjs;\n\t}\n\n\t// Base class for all dataset controllers (line, bar, etc)\n\tChart.DatasetController = function(chart, datasetIndex) {\n\t\tthis.initialize(chart, datasetIndex);\n\t};\n\n\thelpers.extend(Chart.DatasetController.prototype, {\n\n\t\t/**\n\t\t * Element type used to generate a meta dataset (e.g. Chart.element.Line).\n\t\t * @type {Chart.core.element}\n\t\t */\n\t\tdatasetElementType: null,\n\n\t\t/**\n\t\t * Element type used to generate a meta data (e.g. Chart.element.Point).\n\t\t * @type {Chart.core.element}\n\t\t */\n\t\tdataElementType: null,\n\n\t\tinitialize: function(chart, datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tme.chart = chart;\n\t\t\tme.index = datasetIndex;\n\t\t\tme.linkScales();\n\t\t\tme.addElements();\n\t\t},\n\n\t\tupdateIndex: function(datasetIndex) {\n\t\t\tthis.index = datasetIndex;\n\t\t},\n\n\t\tlinkScales: function() {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar dataset = me.getDataset();\n\n\t\t\tif (meta.xAxisID === null) {\n\t\t\t\tmeta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;\n\t\t\t}\n\t\t\tif (meta.yAxisID === null) {\n\t\t\t\tmeta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;\n\t\t\t}\n\t\t},\n\n\t\tgetDataset: function() {\n\t\t\treturn this.chart.data.datasets[this.index];\n\t\t},\n\n\t\tgetMeta: function() {\n\t\t\treturn this.chart.getDatasetMeta(this.index);\n\t\t},\n\n\t\tgetScaleForId: function(scaleID) {\n\t\t\treturn this.chart.scales[scaleID];\n\t\t},\n\n\t\treset: function() {\n\t\t\tthis.update(true);\n\t\t},\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\tdestroy: function() {\n\t\t\tif (this._data) {\n\t\t\t\tunlistenArrayEvents(this._data, this);\n\t\t\t}\n\t\t},\n\n\t\tcreateMetaDataset: function() {\n\t\t\tvar me = this;\n\t\t\tvar type = me.datasetElementType;\n\t\t\treturn type && new type({\n\t\t\t\t_chart: me.chart.chart,\n\t\t\t\t_datasetIndex: me.index\n\t\t\t});\n\t\t},\n\n\t\tcreateMetaData: function(index) {\n\t\t\tvar me = this;\n\t\t\tvar type = me.dataElementType;\n\t\t\treturn type && new type({\n\t\t\t\t_chart: me.chart.chart,\n\t\t\t\t_datasetIndex: me.index,\n\t\t\t\t_index: index\n\t\t\t});\n\t\t},\n\n\t\taddElements: function() {\n\t\t\tvar me = this;\n\t\t\tvar meta = me.getMeta();\n\t\t\tvar data = me.getDataset().data || [];\n\t\t\tvar metaData = meta.data;\n\t\t\tvar i, ilen;\n\n\t\t\tfor (i=0, ilen=data.length; i numMeta) {\n\t\t\t\tme.insertElements(numMeta, numData - numMeta);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\tinsertElements: function(start, count) {\n\t\t\tfor (var i=0; i No Transition\n\t\t\tif (ease === 1) {\n\t\t\t\tme._view = me._model;\n\t\t\t\tme._start = null;\n\t\t\t\treturn me;\n\t\t\t}\n\n\t\t\tif (!me._start) {\n\t\t\t\tme.pivot();\n\t\t\t}\n\n\t\t\thelpers.each(me._model, function(value, key) {\n\n\t\t\t\tif (key[0] === '_') {\n\t\t\t\t\t// Only non-underscored properties\n\t\t\t\t// Init if doesn't exist\n\t\t\t\t} else if (!me._view.hasOwnProperty(key)) {\n\t\t\t\t\tif (typeof value === 'number' && !isNaN(me._view[key])) {\n\t\t\t\t\t\tme._view[key] = value * ease;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tme._view[key] = value;\n\t\t\t\t\t}\n\t\t\t\t// No unnecessary computations\n\t\t\t\t} else if (value === me._view[key]) {\n\t\t\t\t\t// It's the same! Woohoo!\n\t\t\t\t// Color transitions if possible\n\t\t\t\t} else if (typeof value === 'string') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease);\n\t\t\t\t\t\tme._view[key] = color.rgbString();\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tme._view[key] = value;\n\t\t\t\t\t}\n\t\t\t\t// Number transitions\n\t\t\t\t} else if (typeof value === 'number') {\n\t\t\t\t\tvar startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0;\n\t\t\t\t\tme._view[key] = ((me._model[key] - startVal) * ease) + startVal;\n\t\t\t\t// Everything else\n\t\t\t\t} else {\n\t\t\t\t\tme._view[key] = value;\n\t\t\t\t}\n\t\t\t}, me);\n\n\t\t\treturn me;\n\t\t},\n\n\t\ttooltipPosition: function() {\n\t\t\treturn {\n\t\t\t\tx: this._model.x,\n\t\t\t\ty: this._model.y\n\t\t\t};\n\t\t},\n\n\t\thasValue: function() {\n\t\t\treturn helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);\n\t\t}\n\t});\n\n\tChart.Element.extend = helpers.inherits;\n\n};\n\n},{}],26:[function(require,module,exports){\n/* global window: false */\n/* global document: false */\n'use strict';\n\nvar color = require(3);\n\nmodule.exports = function(Chart) {\n\t// Global Chart helpers object for utility methods and classes\n\tvar helpers = Chart.helpers = {};\n\n\t// -- Basic js utility methods\n\thelpers.each = function(loopable, callback, self, reverse) {\n\t\t// Check to see if null or undefined firstly.\n\t\tvar i, len;\n\t\tif (helpers.isArray(loopable)) {\n\t\t\tlen = loopable.length;\n\t\t\tif (reverse) {\n\t\t\t\tfor (i = len - 1; i >= 0; i--) {\n\t\t\t\t\tcallback.call(self, loopable[i], i);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (i = 0; i < len; i++) {\n\t\t\t\t\tcallback.call(self, loopable[i], i);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (typeof loopable === 'object') {\n\t\t\tvar keys = Object.keys(loopable);\n\t\t\tlen = keys.length;\n\t\t\tfor (i = 0; i < len; i++) {\n\t\t\t\tcallback.call(self, loopable[keys[i]], keys[i]);\n\t\t\t}\n\t\t}\n\t};\n\thelpers.clone = function(obj) {\n\t\tvar objClone = {};\n\t\thelpers.each(obj, function(value, key) {\n\t\t\tif (helpers.isArray(value)) {\n\t\t\t\tobjClone[key] = value.slice(0);\n\t\t\t} else if (typeof value === 'object' && value !== null) {\n\t\t\t\tobjClone[key] = helpers.clone(value);\n\t\t\t} else {\n\t\t\t\tobjClone[key] = value;\n\t\t\t}\n\t\t});\n\t\treturn objClone;\n\t};\n\thelpers.extend = function(base) {\n\t\tvar setFn = function(value, key) {\n\t\t\tbase[key] = value;\n\t\t};\n\t\tfor (var i = 1, ilen = arguments.length; i < ilen; i++) {\n\t\t\thelpers.each(arguments[i], setFn);\n\t\t}\n\t\treturn base;\n\t};\n\t// Need a special merge function to chart configs since they are now grouped\n\thelpers.configMerge = function(_base) {\n\t\tvar base = helpers.clone(_base);\n\t\thelpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {\n\t\t\thelpers.each(extension, function(value, key) {\n\t\t\t\tvar baseHasProperty = base.hasOwnProperty(key);\n\t\t\t\tvar baseVal = baseHasProperty ? base[key] : {};\n\n\t\t\t\tif (key === 'scales') {\n\t\t\t\t\t// Scale config merging is complex. Add our own function here for that\n\t\t\t\t\tbase[key] = helpers.scaleMerge(baseVal, value);\n\t\t\t\t} else if (key === 'scale') {\n\t\t\t\t\t// Used in polar area & radar charts since there is only one scale\n\t\t\t\t\tbase[key] = helpers.configMerge(baseVal, Chart.scaleService.getScaleDefaults(value.type), value);\n\t\t\t\t} else if (baseHasProperty\n\t\t\t\t\t\t&& typeof baseVal === 'object'\n\t\t\t\t\t\t&& !helpers.isArray(baseVal)\n\t\t\t\t\t\t&& baseVal !== null\n\t\t\t\t\t\t&& typeof value === 'object'\n\t\t\t\t\t\t&& !helpers.isArray(value)) {\n\t\t\t\t\t// If we are overwriting an object with an object, do a merge of the properties.\n\t\t\t\t\tbase[key] = helpers.configMerge(baseVal, value);\n\t\t\t\t} else {\n\t\t\t\t\t// can just overwrite the value in this case\n\t\t\t\t\tbase[key] = value;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\treturn base;\n\t};\n\thelpers.scaleMerge = function(_base, extension) {\n\t\tvar base = helpers.clone(_base);\n\n\t\thelpers.each(extension, function(value, key) {\n\t\t\tif (key === 'xAxes' || key === 'yAxes') {\n\t\t\t\t// These properties are arrays of items\n\t\t\t\tif (base.hasOwnProperty(key)) {\n\t\t\t\t\thelpers.each(value, function(valueObj, index) {\n\t\t\t\t\t\tvar axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');\n\t\t\t\t\t\tvar axisDefaults = Chart.scaleService.getScaleDefaults(axisType);\n\t\t\t\t\t\tif (index >= base[key].length || !base[key][index].type) {\n\t\t\t\t\t\t\tbase[key].push(helpers.configMerge(axisDefaults, valueObj));\n\t\t\t\t\t\t} else if (valueObj.type && valueObj.type !== base[key][index].type) {\n\t\t\t\t\t\t\t// Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults\n\t\t\t\t\t\t\tbase[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Type is the same\n\t\t\t\t\t\t\tbase[key][index] = helpers.configMerge(base[key][index], valueObj);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tbase[key] = [];\n\t\t\t\t\thelpers.each(value, function(valueObj) {\n\t\t\t\t\t\tvar axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');\n\t\t\t\t\t\tbase[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') {\n\t\t\t\t// If we are overwriting an object with an object, do a merge of the properties.\n\t\t\t\tbase[key] = helpers.configMerge(base[key], value);\n\n\t\t\t} else {\n\t\t\t\t// can just overwrite the value in this case\n\t\t\t\tbase[key] = value;\n\t\t\t}\n\t\t});\n\n\t\treturn base;\n\t};\n\thelpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {\n\t\tif (value === undefined || value === null) {\n\t\t\treturn defaultValue;\n\t\t}\n\n\t\tif (helpers.isArray(value)) {\n\t\t\treturn index < value.length ? value[index] : defaultValue;\n\t\t}\n\n\t\treturn value;\n\t};\n\thelpers.getValueOrDefault = function(value, defaultValue) {\n\t\treturn value === undefined ? defaultValue : value;\n\t};\n\thelpers.indexOf = Array.prototype.indexOf?\n\t\tfunction(array, item) {\n\t\t\treturn array.indexOf(item);\n\t\t}:\n\t\tfunction(array, item) {\n\t\t\tfor (var i = 0, ilen = array.length; i < ilen; ++i) {\n\t\t\t\tif (array[i] === item) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn -1;\n\t\t};\n\thelpers.where = function(collection, filterCallback) {\n\t\tif (helpers.isArray(collection) && Array.prototype.filter) {\n\t\t\treturn collection.filter(filterCallback);\n\t\t}\n\t\tvar filtered = [];\n\n\t\thelpers.each(collection, function(item) {\n\t\t\tif (filterCallback(item)) {\n\t\t\t\tfiltered.push(item);\n\t\t\t}\n\t\t});\n\n\t\treturn filtered;\n\t};\n\thelpers.findIndex = Array.prototype.findIndex?\n\t\tfunction(array, callback, scope) {\n\t\t\treturn array.findIndex(callback, scope);\n\t\t} :\n\t\tfunction(array, callback, scope) {\n\t\t\tscope = scope === undefined? array : scope;\n\t\t\tfor (var i = 0, ilen = array.length; i < ilen; ++i) {\n\t\t\t\tif (callback.call(scope, array[i], i, array)) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn -1;\n\t\t};\n\thelpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {\n\t\t// Default to start of the array\n\t\tif (startIndex === undefined || startIndex === null) {\n\t\t\tstartIndex = -1;\n\t\t}\n\t\tfor (var i = startIndex + 1; i < arrayToSearch.length; i++) {\n\t\t\tvar currentItem = arrayToSearch[i];\n\t\t\tif (filterCallback(currentItem)) {\n\t\t\t\treturn currentItem;\n\t\t\t}\n\t\t}\n\t};\n\thelpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {\n\t\t// Default to end of the array\n\t\tif (startIndex === undefined || startIndex === null) {\n\t\t\tstartIndex = arrayToSearch.length;\n\t\t}\n\t\tfor (var i = startIndex - 1; i >= 0; i--) {\n\t\t\tvar currentItem = arrayToSearch[i];\n\t\t\tif (filterCallback(currentItem)) {\n\t\t\t\treturn currentItem;\n\t\t\t}\n\t\t}\n\t};\n\thelpers.inherits = function(extensions) {\n\t\t// Basic javascript inheritance based on the model created in Backbone.js\n\t\tvar me = this;\n\t\tvar ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {\n\t\t\treturn me.apply(this, arguments);\n\t\t};\n\n\t\tvar Surrogate = function() {\n\t\t\tthis.constructor = ChartElement;\n\t\t};\n\t\tSurrogate.prototype = me.prototype;\n\t\tChartElement.prototype = new Surrogate();\n\n\t\tChartElement.extend = helpers.inherits;\n\n\t\tif (extensions) {\n\t\t\thelpers.extend(ChartElement.prototype, extensions);\n\t\t}\n\n\t\tChartElement.__super__ = me.prototype;\n\n\t\treturn ChartElement;\n\t};\n\thelpers.noop = function() {};\n\thelpers.uid = (function() {\n\t\tvar id = 0;\n\t\treturn function() {\n\t\t\treturn id++;\n\t\t};\n\t}());\n\t// -- Math methods\n\thelpers.isNumber = function(n) {\n\t\treturn !isNaN(parseFloat(n)) && isFinite(n);\n\t};\n\thelpers.almostEquals = function(x, y, epsilon) {\n\t\treturn Math.abs(x - y) < epsilon;\n\t};\n\thelpers.almostWhole = function(x, epsilon) {\n\t\tvar rounded = Math.round(x);\n\t\treturn (((rounded - epsilon) < x) && ((rounded + epsilon) > x));\n\t};\n\thelpers.max = function(array) {\n\t\treturn array.reduce(function(max, value) {\n\t\t\tif (!isNaN(value)) {\n\t\t\t\treturn Math.max(max, value);\n\t\t\t}\n\t\t\treturn max;\n\t\t}, Number.NEGATIVE_INFINITY);\n\t};\n\thelpers.min = function(array) {\n\t\treturn array.reduce(function(min, value) {\n\t\t\tif (!isNaN(value)) {\n\t\t\t\treturn Math.min(min, value);\n\t\t\t}\n\t\t\treturn min;\n\t\t}, Number.POSITIVE_INFINITY);\n\t};\n\thelpers.sign = Math.sign?\n\t\tfunction(x) {\n\t\t\treturn Math.sign(x);\n\t\t} :\n\t\tfunction(x) {\n\t\t\tx = +x; // convert to a number\n\t\t\tif (x === 0 || isNaN(x)) {\n\t\t\t\treturn x;\n\t\t\t}\n\t\t\treturn x > 0 ? 1 : -1;\n\t\t};\n\thelpers.log10 = Math.log10?\n\t\tfunction(x) {\n\t\t\treturn Math.log10(x);\n\t\t} :\n\t\tfunction(x) {\n\t\t\treturn Math.log(x) / Math.LN10;\n\t\t};\n\thelpers.toRadians = function(degrees) {\n\t\treturn degrees * (Math.PI / 180);\n\t};\n\thelpers.toDegrees = function(radians) {\n\t\treturn radians * (180 / Math.PI);\n\t};\n\t// Gets the angle from vertical upright to the point about a centre.\n\thelpers.getAngleFromPoint = function(centrePoint, anglePoint) {\n\t\tvar distanceFromXCenter = anglePoint.x - centrePoint.x,\n\t\t\tdistanceFromYCenter = anglePoint.y - centrePoint.y,\n\t\t\tradialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);\n\n\t\tvar angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);\n\n\t\tif (angle < (-0.5 * Math.PI)) {\n\t\t\tangle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]\n\t\t}\n\n\t\treturn {\n\t\t\tangle: angle,\n\t\t\tdistance: radialDistanceFromCenter\n\t\t};\n\t};\n\thelpers.distanceBetweenPoints = function(pt1, pt2) {\n\t\treturn Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));\n\t};\n\thelpers.aliasPixel = function(pixelWidth) {\n\t\treturn (pixelWidth % 2 === 0) ? 0 : 0.5;\n\t};\n\thelpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {\n\t\t// Props to Rob Spencer at scaled innovation for his post on splining between points\n\t\t// http://scaledinnovation.com/analytics/splines/aboutSplines.html\n\n\t\t// This function must also respect \"skipped\" points\n\n\t\tvar previous = firstPoint.skip ? middlePoint : firstPoint,\n\t\t\tcurrent = middlePoint,\n\t\t\tnext = afterPoint.skip ? middlePoint : afterPoint;\n\n\t\tvar d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));\n\t\tvar d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));\n\n\t\tvar s01 = d01 / (d01 + d12);\n\t\tvar s12 = d12 / (d01 + d12);\n\n\t\t// If all points are the same, s01 & s02 will be inf\n\t\ts01 = isNaN(s01) ? 0 : s01;\n\t\ts12 = isNaN(s12) ? 0 : s12;\n\n\t\tvar fa = t * s01; // scaling factor for triangle Ta\n\t\tvar fb = t * s12;\n\n\t\treturn {\n\t\t\tprevious: {\n\t\t\t\tx: current.x - fa * (next.x - previous.x),\n\t\t\t\ty: current.y - fa * (next.y - previous.y)\n\t\t\t},\n\t\t\tnext: {\n\t\t\t\tx: current.x + fb * (next.x - previous.x),\n\t\t\t\ty: current.y + fb * (next.y - previous.y)\n\t\t\t}\n\t\t};\n\t};\n\thelpers.EPSILON = Number.EPSILON || 1e-14;\n\thelpers.splineCurveMonotone = function(points) {\n\t\t// This function calculates Bézier control points in a similar way than |splineCurve|,\n\t\t// but preserves monotonicity of the provided data and ensures no local extremums are added\n\t\t// between the dataset discrete points due to the interpolation.\n\t\t// See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation\n\n\t\tvar pointsWithTangents = (points || []).map(function(point) {\n\t\t\treturn {\n\t\t\t\tmodel: point._model,\n\t\t\t\tdeltaK: 0,\n\t\t\t\tmK: 0\n\t\t\t};\n\t\t});\n\n\t\t// Calculate slopes (deltaK) and initialize tangents (mK)\n\t\tvar pointsLen = pointsWithTangents.length;\n\t\tvar i, pointBefore, pointCurrent, pointAfter;\n\t\tfor (i = 0; i < pointsLen; ++i) {\n\t\t\tpointCurrent = pointsWithTangents[i];\n\t\t\tif (pointCurrent.model.skip) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpointBefore = i > 0 ? pointsWithTangents[i - 1] : null;\n\t\t\tpointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;\n\t\t\tif (pointAfter && !pointAfter.model.skip) {\n\t\t\t\tvar slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);\n\n\t\t\t\t// In the case of two points that appear at the same x pixel, slopeDeltaX is 0\n\t\t\t\tpointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;\n\t\t\t}\n\n\t\t\tif (!pointBefore || pointBefore.model.skip) {\n\t\t\t\tpointCurrent.mK = pointCurrent.deltaK;\n\t\t\t} else if (!pointAfter || pointAfter.model.skip) {\n\t\t\t\tpointCurrent.mK = pointBefore.deltaK;\n\t\t\t} else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {\n\t\t\t\tpointCurrent.mK = 0;\n\t\t\t} else {\n\t\t\t\tpointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;\n\t\t\t}\n\t\t}\n\n\t\t// Adjust tangents to ensure monotonic properties\n\t\tvar alphaK, betaK, tauK, squaredMagnitude;\n\t\tfor (i = 0; i < pointsLen - 1; ++i) {\n\t\t\tpointCurrent = pointsWithTangents[i];\n\t\t\tpointAfter = pointsWithTangents[i + 1];\n\t\t\tif (pointCurrent.model.skip || pointAfter.model.skip) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {\n\t\t\t\tpointCurrent.mK = pointAfter.mK = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\talphaK = pointCurrent.mK / pointCurrent.deltaK;\n\t\t\tbetaK = pointAfter.mK / pointCurrent.deltaK;\n\t\t\tsquaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);\n\t\t\tif (squaredMagnitude <= 9) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttauK = 3 / Math.sqrt(squaredMagnitude);\n\t\t\tpointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;\n\t\t\tpointAfter.mK = betaK * tauK * pointCurrent.deltaK;\n\t\t}\n\n\t\t// Compute control points\n\t\tvar deltaX;\n\t\tfor (i = 0; i < pointsLen; ++i) {\n\t\t\tpointCurrent = pointsWithTangents[i];\n\t\t\tif (pointCurrent.model.skip) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpointBefore = i > 0 ? pointsWithTangents[i - 1] : null;\n\t\t\tpointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;\n\t\t\tif (pointBefore && !pointBefore.model.skip) {\n\t\t\t\tdeltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;\n\t\t\t\tpointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;\n\t\t\t\tpointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;\n\t\t\t}\n\t\t\tif (pointAfter && !pointAfter.model.skip) {\n\t\t\t\tdeltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;\n\t\t\t\tpointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;\n\t\t\t\tpointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;\n\t\t\t}\n\t\t}\n\t};\n\thelpers.nextItem = function(collection, index, loop) {\n\t\tif (loop) {\n\t\t\treturn index >= collection.length - 1 ? collection[0] : collection[index + 1];\n\t\t}\n\t\treturn index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];\n\t};\n\thelpers.previousItem = function(collection, index, loop) {\n\t\tif (loop) {\n\t\t\treturn index <= 0 ? collection[collection.length - 1] : collection[index - 1];\n\t\t}\n\t\treturn index <= 0 ? collection[0] : collection[index - 1];\n\t};\n\t// Implementation of the nice number algorithm used in determining where axis labels will go\n\thelpers.niceNum = function(range, round) {\n\t\tvar exponent = Math.floor(helpers.log10(range));\n\t\tvar fraction = range / Math.pow(10, exponent);\n\t\tvar niceFraction;\n\n\t\tif (round) {\n\t\t\tif (fraction < 1.5) {\n\t\t\t\tniceFraction = 1;\n\t\t\t} else if (fraction < 3) {\n\t\t\t\tniceFraction = 2;\n\t\t\t} else if (fraction < 7) {\n\t\t\t\tniceFraction = 5;\n\t\t\t} else {\n\t\t\t\tniceFraction = 10;\n\t\t\t}\n\t\t} else if (fraction <= 1.0) {\n\t\t\tniceFraction = 1;\n\t\t} else if (fraction <= 2) {\n\t\t\tniceFraction = 2;\n\t\t} else if (fraction <= 5) {\n\t\t\tniceFraction = 5;\n\t\t} else {\n\t\t\tniceFraction = 10;\n\t\t}\n\n\t\treturn niceFraction * Math.pow(10, exponent);\n\t};\n\t// Easing functions adapted from Robert Penner's easing equations\n\t// http://www.robertpenner.com/easing/\n\tvar easingEffects = helpers.easingEffects = {\n\t\tlinear: function(t) {\n\t\t\treturn t;\n\t\t},\n\t\teaseInQuad: function(t) {\n\t\t\treturn t * t;\n\t\t},\n\t\teaseOutQuad: function(t) {\n\t\t\treturn -1 * t * (t - 2);\n\t\t},\n\t\teaseInOutQuad: function(t) {\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn 1 / 2 * t * t;\n\t\t\t}\n\t\t\treturn -1 / 2 * ((--t) * (t - 2) - 1);\n\t\t},\n\t\teaseInCubic: function(t) {\n\t\t\treturn t * t * t;\n\t\t},\n\t\teaseOutCubic: function(t) {\n\t\t\treturn 1 * ((t = t / 1 - 1) * t * t + 1);\n\t\t},\n\t\teaseInOutCubic: function(t) {\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn 1 / 2 * t * t * t;\n\t\t\t}\n\t\t\treturn 1 / 2 * ((t -= 2) * t * t + 2);\n\t\t},\n\t\teaseInQuart: function(t) {\n\t\t\treturn t * t * t * t;\n\t\t},\n\t\teaseOutQuart: function(t) {\n\t\t\treturn -1 * ((t = t / 1 - 1) * t * t * t - 1);\n\t\t},\n\t\teaseInOutQuart: function(t) {\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn 1 / 2 * t * t * t * t;\n\t\t\t}\n\t\t\treturn -1 / 2 * ((t -= 2) * t * t * t - 2);\n\t\t},\n\t\teaseInQuint: function(t) {\n\t\t\treturn 1 * (t /= 1) * t * t * t * t;\n\t\t},\n\t\teaseOutQuint: function(t) {\n\t\t\treturn 1 * ((t = t / 1 - 1) * t * t * t * t + 1);\n\t\t},\n\t\teaseInOutQuint: function(t) {\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn 1 / 2 * t * t * t * t * t;\n\t\t\t}\n\t\t\treturn 1 / 2 * ((t -= 2) * t * t * t * t + 2);\n\t\t},\n\t\teaseInSine: function(t) {\n\t\t\treturn -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;\n\t\t},\n\t\teaseOutSine: function(t) {\n\t\t\treturn 1 * Math.sin(t / 1 * (Math.PI / 2));\n\t\t},\n\t\teaseInOutSine: function(t) {\n\t\t\treturn -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);\n\t\t},\n\t\teaseInExpo: function(t) {\n\t\t\treturn (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));\n\t\t},\n\t\teaseOutExpo: function(t) {\n\t\t\treturn (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);\n\t\t},\n\t\teaseInOutExpo: function(t) {\n\t\t\tif (t === 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (t === 1) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn 1 / 2 * Math.pow(2, 10 * (t - 1));\n\t\t\t}\n\t\t\treturn 1 / 2 * (-Math.pow(2, -10 * --t) + 2);\n\t\t},\n\t\teaseInCirc: function(t) {\n\t\t\tif (t >= 1) {\n\t\t\t\treturn t;\n\t\t\t}\n\t\t\treturn -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);\n\t\t},\n\t\teaseOutCirc: function(t) {\n\t\t\treturn 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);\n\t\t},\n\t\teaseInOutCirc: function(t) {\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn -1 / 2 * (Math.sqrt(1 - t * t) - 1);\n\t\t\t}\n\t\t\treturn 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);\n\t\t},\n\t\teaseInElastic: function(t) {\n\t\t\tvar s = 1.70158;\n\t\t\tvar p = 0;\n\t\t\tvar a = 1;\n\t\t\tif (t === 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif ((t /= 1) === 1) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (!p) {\n\t\t\t\tp = 1 * 0.3;\n\t\t\t}\n\t\t\tif (a < Math.abs(1)) {\n\t\t\t\ta = 1;\n\t\t\t\ts = p / 4;\n\t\t\t} else {\n\t\t\t\ts = p / (2 * Math.PI) * Math.asin(1 / a);\n\t\t\t}\n\t\t\treturn -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));\n\t\t},\n\t\teaseOutElastic: function(t) {\n\t\t\tvar s = 1.70158;\n\t\t\tvar p = 0;\n\t\t\tvar a = 1;\n\t\t\tif (t === 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif ((t /= 1) === 1) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (!p) {\n\t\t\t\tp = 1 * 0.3;\n\t\t\t}\n\t\t\tif (a < Math.abs(1)) {\n\t\t\t\ta = 1;\n\t\t\t\ts = p / 4;\n\t\t\t} else {\n\t\t\t\ts = p / (2 * Math.PI) * Math.asin(1 / a);\n\t\t\t}\n\t\t\treturn a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;\n\t\t},\n\t\teaseInOutElastic: function(t) {\n\t\t\tvar s = 1.70158;\n\t\t\tvar p = 0;\n\t\t\tvar a = 1;\n\t\t\tif (t === 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif ((t /= 1 / 2) === 2) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (!p) {\n\t\t\t\tp = 1 * (0.3 * 1.5);\n\t\t\t}\n\t\t\tif (a < Math.abs(1)) {\n\t\t\t\ta = 1;\n\t\t\t\ts = p / 4;\n\t\t\t} else {\n\t\t\t\ts = p / (2 * Math.PI) * Math.asin(1 / a);\n\t\t\t}\n\t\t\tif (t < 1) {\n\t\t\t\treturn -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));\n\t\t\t}\n\t\t\treturn a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;\n\t\t},\n\t\teaseInBack: function(t) {\n\t\t\tvar s = 1.70158;\n\t\t\treturn 1 * (t /= 1) * t * ((s + 1) * t - s);\n\t\t},\n\t\teaseOutBack: function(t) {\n\t\t\tvar s = 1.70158;\n\t\t\treturn 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);\n\t\t},\n\t\teaseInOutBack: function(t) {\n\t\t\tvar s = 1.70158;\n\t\t\tif ((t /= 1 / 2) < 1) {\n\t\t\t\treturn 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));\n\t\t\t}\n\t\t\treturn 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);\n\t\t},\n\t\teaseInBounce: function(t) {\n\t\t\treturn 1 - easingEffects.easeOutBounce(1 - t);\n\t\t},\n\t\teaseOutBounce: function(t) {\n\t\t\tif ((t /= 1) < (1 / 2.75)) {\n\t\t\t\treturn 1 * (7.5625 * t * t);\n\t\t\t} else if (t < (2 / 2.75)) {\n\t\t\t\treturn 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);\n\t\t\t} else if (t < (2.5 / 2.75)) {\n\t\t\t\treturn 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);\n\t\t\t}\n\t\t\treturn 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);\n\t\t},\n\t\teaseInOutBounce: function(t) {\n\t\t\tif (t < 1 / 2) {\n\t\t\t\treturn easingEffects.easeInBounce(t * 2) * 0.5;\n\t\t\t}\n\t\t\treturn easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;\n\t\t}\n\t};\n\t// Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/\n\thelpers.requestAnimFrame = (function() {\n\t\treturn window.requestAnimationFrame ||\n\t\t\twindow.webkitRequestAnimationFrame ||\n\t\t\twindow.mozRequestAnimationFrame ||\n\t\t\twindow.oRequestAnimationFrame ||\n\t\t\twindow.msRequestAnimationFrame ||\n\t\t\tfunction(callback) {\n\t\t\t\treturn window.setTimeout(callback, 1000 / 60);\n\t\t\t};\n\t}());\n\t// -- DOM methods\n\thelpers.getRelativePosition = function(evt, chart) {\n\t\tvar mouseX, mouseY;\n\t\tvar e = evt.originalEvent || evt,\n\t\t\tcanvas = evt.currentTarget || evt.srcElement,\n\t\t\tboundingRect = canvas.getBoundingClientRect();\n\n\t\tvar touches = e.touches;\n\t\tif (touches && touches.length > 0) {\n\t\t\tmouseX = touches[0].clientX;\n\t\t\tmouseY = touches[0].clientY;\n\n\t\t} else {\n\t\t\tmouseX = e.clientX;\n\t\t\tmouseY = e.clientY;\n\t\t}\n\n\t\t// Scale mouse coordinates into canvas coordinates\n\t\t// by following the pattern laid out by 'jerryj' in the comments of\n\t\t// http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/\n\t\tvar paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));\n\t\tvar paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));\n\t\tvar paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));\n\t\tvar paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));\n\t\tvar width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;\n\t\tvar height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;\n\n\t\t// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However\n\t\t// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here\n\t\tmouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);\n\t\tmouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);\n\n\t\treturn {\n\t\t\tx: mouseX,\n\t\t\ty: mouseY\n\t\t};\n\n\t};\n\thelpers.addEvent = function(node, eventType, method) {\n\t\tif (node.addEventListener) {\n\t\t\tnode.addEventListener(eventType, method);\n\t\t} else if (node.attachEvent) {\n\t\t\tnode.attachEvent('on' + eventType, method);\n\t\t} else {\n\t\t\tnode['on' + eventType] = method;\n\t\t}\n\t};\n\thelpers.removeEvent = function(node, eventType, handler) {\n\t\tif (node.removeEventListener) {\n\t\t\tnode.removeEventListener(eventType, handler, false);\n\t\t} else if (node.detachEvent) {\n\t\t\tnode.detachEvent('on' + eventType, handler);\n\t\t} else {\n\t\t\tnode['on' + eventType] = helpers.noop;\n\t\t}\n\t};\n\n\t// Private helper function to convert max-width/max-height values that may be percentages into a number\n\tfunction parseMaxStyle(styleValue, node, parentProperty) {\n\t\tvar valueInPixels;\n\t\tif (typeof(styleValue) === 'string') {\n\t\t\tvalueInPixels = parseInt(styleValue, 10);\n\n\t\t\tif (styleValue.indexOf('%') !== -1) {\n\t\t\t\t// percentage * size in dimension\n\t\t\t\tvalueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];\n\t\t\t}\n\t\t} else {\n\t\t\tvalueInPixels = styleValue;\n\t\t}\n\n\t\treturn valueInPixels;\n\t}\n\n\t/**\n\t * Returns if the given value contains an effective constraint.\n\t * @private\n\t */\n\tfunction isConstrainedValue(value) {\n\t\treturn value !== undefined && value !== null && value !== 'none';\n\t}\n\n\t// Private helper to get a constraint dimension\n\t// @param domNode : the node to check the constraint on\n\t// @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)\n\t// @param percentageProperty : property of parent to use when calculating width as a percentage\n\t// @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser\n\tfunction getConstraintDimension(domNode, maxStyle, percentageProperty) {\n\t\tvar view = document.defaultView;\n\t\tvar parentNode = domNode.parentNode;\n\t\tvar constrainedNode = view.getComputedStyle(domNode)[maxStyle];\n\t\tvar constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];\n\t\tvar hasCNode = isConstrainedValue(constrainedNode);\n\t\tvar hasCContainer = isConstrainedValue(constrainedContainer);\n\t\tvar infinity = Number.POSITIVE_INFINITY;\n\n\t\tif (hasCNode || hasCContainer) {\n\t\t\treturn Math.min(\n\t\t\t\thasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,\n\t\t\t\thasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);\n\t\t}\n\n\t\treturn 'none';\n\t}\n\t// returns Number or undefined if no constraint\n\thelpers.getConstraintWidth = function(domNode) {\n\t\treturn getConstraintDimension(domNode, 'max-width', 'clientWidth');\n\t};\n\t// returns Number or undefined if no constraint\n\thelpers.getConstraintHeight = function(domNode) {\n\t\treturn getConstraintDimension(domNode, 'max-height', 'clientHeight');\n\t};\n\thelpers.getMaximumWidth = function(domNode) {\n\t\tvar container = domNode.parentNode;\n\t\tvar paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);\n\t\tvar paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);\n\t\tvar w = container.clientWidth - paddingLeft - paddingRight;\n\t\tvar cw = helpers.getConstraintWidth(domNode);\n\t\treturn isNaN(cw)? w : Math.min(w, cw);\n\t};\n\thelpers.getMaximumHeight = function(domNode) {\n\t\tvar container = domNode.parentNode;\n\t\tvar paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);\n\t\tvar paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);\n\t\tvar h = container.clientHeight - paddingTop - paddingBottom;\n\t\tvar ch = helpers.getConstraintHeight(domNode);\n\t\treturn isNaN(ch)? h : Math.min(h, ch);\n\t};\n\thelpers.getStyle = function(el, property) {\n\t\treturn el.currentStyle ?\n\t\t\tel.currentStyle[property] :\n\t\t\tdocument.defaultView.getComputedStyle(el, null).getPropertyValue(property);\n\t};\n\thelpers.retinaScale = function(chart) {\n\t\tvar pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;\n\t\tif (pixelRatio === 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar canvas = chart.canvas;\n\t\tvar height = chart.height;\n\t\tvar width = chart.width;\n\n\t\tcanvas.height = height * pixelRatio;\n\t\tcanvas.width = width * pixelRatio;\n\t\tchart.ctx.scale(pixelRatio, pixelRatio);\n\n\t\t// If no style has been set on the canvas, the render size is used as display size,\n\t\t// making the chart visually bigger, so let's enforce it to the \"correct\" values.\n\t\t// See https://github.com/chartjs/Chart.js/issues/3575\n\t\tcanvas.style.height = height + 'px';\n\t\tcanvas.style.width = width + 'px';\n\t};\n\t// -- Canvas methods\n\thelpers.clear = function(chart) {\n\t\tchart.ctx.clearRect(0, 0, chart.width, chart.height);\n\t};\n\thelpers.fontString = function(pixelSize, fontStyle, fontFamily) {\n\t\treturn fontStyle + ' ' + pixelSize + 'px ' + fontFamily;\n\t};\n\thelpers.longestText = function(ctx, font, arrayOfThings, cache) {\n\t\tcache = cache || {};\n\t\tvar data = cache.data = cache.data || {};\n\t\tvar gc = cache.garbageCollect = cache.garbageCollect || [];\n\n\t\tif (cache.font !== font) {\n\t\t\tdata = cache.data = {};\n\t\t\tgc = cache.garbageCollect = [];\n\t\t\tcache.font = font;\n\t\t}\n\n\t\tctx.font = font;\n\t\tvar longest = 0;\n\t\thelpers.each(arrayOfThings, function(thing) {\n\t\t\t// Undefined strings and arrays should not be measured\n\t\t\tif (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {\n\t\t\t\tlongest = helpers.measureText(ctx, data, gc, longest, thing);\n\t\t\t} else if (helpers.isArray(thing)) {\n\t\t\t\t// if it is an array lets measure each element\n\t\t\t\t// to do maybe simplify this function a bit so we can do this more recursively?\n\t\t\t\thelpers.each(thing, function(nestedThing) {\n\t\t\t\t\t// Undefined strings and arrays should not be measured\n\t\t\t\t\tif (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {\n\t\t\t\t\t\tlongest = helpers.measureText(ctx, data, gc, longest, nestedThing);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tvar gcLen = gc.length / 2;\n\t\tif (gcLen > arrayOfThings.length) {\n\t\t\tfor (var i = 0; i < gcLen; i++) {\n\t\t\t\tdelete data[gc[i]];\n\t\t\t}\n\t\t\tgc.splice(0, gcLen);\n\t\t}\n\t\treturn longest;\n\t};\n\thelpers.measureText = function(ctx, data, gc, longest, string) {\n\t\tvar textWidth = data[string];\n\t\tif (!textWidth) {\n\t\t\ttextWidth = data[string] = ctx.measureText(string).width;\n\t\t\tgc.push(string);\n\t\t}\n\t\tif (textWidth > longest) {\n\t\t\tlongest = textWidth;\n\t\t}\n\t\treturn longest;\n\t};\n\thelpers.numberOfLabelLines = function(arrayOfThings) {\n\t\tvar numberOfLines = 1;\n\t\thelpers.each(arrayOfThings, function(thing) {\n\t\t\tif (helpers.isArray(thing)) {\n\t\t\t\tif (thing.length > numberOfLines) {\n\t\t\t\t\tnumberOfLines = thing.length;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn numberOfLines;\n\t};\n\thelpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {\n\t\tctx.beginPath();\n\t\tctx.moveTo(x + radius, y);\n\t\tctx.lineTo(x + width - radius, y);\n\t\tctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n\t\tctx.lineTo(x + width, y + height - radius);\n\t\tctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);\n\t\tctx.lineTo(x + radius, y + height);\n\t\tctx.quadraticCurveTo(x, y + height, x, y + height - radius);\n\t\tctx.lineTo(x, y + radius);\n\t\tctx.quadraticCurveTo(x, y, x + radius, y);\n\t\tctx.closePath();\n\t};\n\thelpers.color = function(c) {\n\t\tif (!color) {\n\t\t\tconsole.error('Color.js not found!');\n\t\t\treturn c;\n\t\t}\n\n\t\t/* global CanvasGradient */\n\t\tif (c instanceof CanvasGradient) {\n\t\t\treturn color(Chart.defaults.global.defaultColor);\n\t\t}\n\n\t\treturn color(c);\n\t};\n\thelpers.isArray = Array.isArray?\n\t\tfunction(obj) {\n\t\t\treturn Array.isArray(obj);\n\t\t} :\n\t\tfunction(obj) {\n\t\t\treturn Object.prototype.toString.call(obj) === '[object Array]';\n\t\t};\n\t// ! @see http://stackoverflow.com/a/14853974\n\thelpers.arrayEquals = function(a0, a1) {\n\t\tvar i, ilen, v0, v1;\n\n\t\tif (!a0 || !a1 || a0.length !== a1.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (i = 0, ilen=a0.length; i < ilen; ++i) {\n\t\t\tv0 = a0[i];\n\t\t\tv1 = a1[i];\n\n\t\t\tif (v0 instanceof Array && v1 instanceof Array) {\n\t\t\t\tif (!helpers.arrayEquals(v0, v1)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} else if (v0 !== v1) {\n\t\t\t\t// NOTE: two different object instances will never be equal: {x:20} != {x:20}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t};\n\thelpers.callCallback = function(fn, args, _tArg) {\n\t\tif (fn && typeof fn.call === 'function') {\n\t\t\tfn.apply(_tArg, args);\n\t\t}\n\t};\n\thelpers.getHoverColor = function(colorValue) {\n\t\t/* global CanvasPattern */\n\t\treturn (colorValue instanceof CanvasPattern) ?\n\t\t\tcolorValue :\n\t\t\thelpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();\n\t};\n};\n\n},{\"3\":3}],27:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\tvar helpers = Chart.helpers;\n\n\t/**\n\t * Helper function to get relative position for an event\n\t * @param {Event|IEvent} event - The event to get the position for\n\t * @param {Chart} chart - The chart\n\t * @returns {Point} the event position\n\t */\n\tfunction getRelativePosition(e, chart) {\n\t\tif (e.native) {\n\t\t\treturn {\n\t\t\t\tx: e.x,\n\t\t\t\ty: e.y\n\t\t\t};\n\t\t}\n\n\t\treturn helpers.getRelativePosition(e, chart);\n\t}\n\n\t/**\n\t * Helper function to traverse all of the visible elements in the chart\n\t * @param chart {chart} the chart\n\t * @param handler {Function} the callback to execute for each visible item\n\t */\n\tfunction parseVisibleItems(chart, handler) {\n\t\tvar datasets = chart.data.datasets;\n\t\tvar meta, i, j, ilen, jlen;\n\n\t\tfor (i = 0, ilen = datasets.length; i < ilen; ++i) {\n\t\t\tif (!chart.isDatasetVisible(i)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmeta = chart.getDatasetMeta(i);\n\t\t\tfor (j = 0, jlen = meta.data.length; j < jlen; ++j) {\n\t\t\t\tvar element = meta.data[j];\n\t\t\t\tif (!element._view.skip) {\n\t\t\t\t\thandler(element);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the items that intersect the event position\n\t * @param items {ChartElement[]} elements to filter\n\t * @param position {Point} the point to be nearest to\n\t * @return {ChartElement[]} the nearest items\n\t */\n\tfunction getIntersectItems(chart, position) {\n\t\tvar elements = [];\n\n\t\tparseVisibleItems(chart, function(element) {\n\t\t\tif (element.inRange(position.x, position.y)) {\n\t\t\t\telements.push(element);\n\t\t\t}\n\t\t});\n\n\t\treturn elements;\n\t}\n\n\t/**\n\t * Helper function to get the items nearest to the event position considering all visible items in teh chart\n\t * @param chart {Chart} the chart to look at elements from\n\t * @param position {Point} the point to be nearest to\n\t * @param intersect {Boolean} if true, only consider items that intersect the position\n\t * @param distanceMetric {Function} Optional function to provide the distance between\n\t * @return {ChartElement[]} the nearest items\n\t */\n\tfunction getNearestItems(chart, position, intersect, distanceMetric) {\n\t\tvar minDistance = Number.POSITIVE_INFINITY;\n\t\tvar nearestItems = [];\n\n\t\tif (!distanceMetric) {\n\t\t\tdistanceMetric = helpers.distanceBetweenPoints;\n\t\t}\n\n\t\tparseVisibleItems(chart, function(element) {\n\t\t\tif (intersect && !element.inRange(position.x, position.y)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar center = element.getCenterPoint();\n\t\t\tvar distance = distanceMetric(position, center);\n\n\t\t\tif (distance < minDistance) {\n\t\t\t\tnearestItems = [element];\n\t\t\t\tminDistance = distance;\n\t\t\t} else if (distance === minDistance) {\n\t\t\t\t// Can have multiple items at the same distance in which case we sort by size\n\t\t\t\tnearestItems.push(element);\n\t\t\t}\n\t\t});\n\n\t\treturn nearestItems;\n\t}\n\n\tfunction indexMode(chart, e, options) {\n\t\tvar position = getRelativePosition(e, chart.chart);\n\t\tvar distanceMetric = function(pt1, pt2) {\n\t\t\treturn Math.abs(pt1.x - pt2.x);\n\t\t};\n\t\tvar items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);\n\t\tvar elements = [];\n\n\t\tif (!items.length) {\n\t\t\treturn [];\n\t\t}\n\n\t\tchart.data.datasets.forEach(function(dataset, datasetIndex) {\n\t\t\tif (chart.isDatasetVisible(datasetIndex)) {\n\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex),\n\t\t\t\t\telement = meta.data[items[0]._index];\n\n\t\t\t\t// don't count items that are skipped (null data)\n\t\t\t\tif (element && !element._view.skip) {\n\t\t\t\t\telements.push(element);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn elements;\n\t}\n\n\t/**\n\t * @interface IInteractionOptions\n\t */\n\t/**\n\t * If true, only consider items that intersect the point\n\t * @name IInterfaceOptions#boolean\n\t * @type Boolean\n\t */\n\n\t/**\n\t * Contains interaction related functions\n\t * @namespace Chart.Interaction\n\t */\n\tChart.Interaction = {\n\t\t// Helper function for different modes\n\t\tmodes: {\n\t\t\tsingle: function(chart, e) {\n\t\t\t\tvar position = getRelativePosition(e, chart.chart);\n\t\t\t\tvar elements = [];\n\n\t\t\t\tparseVisibleItems(chart, function(element) {\n\t\t\t\t\tif (element.inRange(position.x, position.y)) {\n\t\t\t\t\t\telements.push(element);\n\t\t\t\t\t\treturn elements;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn elements.slice(0, 1);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * @function Chart.Interaction.modes.label\n\t\t\t * @deprecated since version 2.4.0\n\t\t\t */\n\t\t\tlabel: indexMode,\n\n\t\t\t/**\n\t\t\t * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something\n\t\t\t * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item\n\t\t\t * @function Chart.Interaction.modes.index\n\t\t\t * @since v2.4.0\n\t\t\t * @param chart {chart} the chart we are returning items from\n\t\t\t * @param e {Event} the event we are find things at\n\t\t\t * @param options {IInteractionOptions} options to use during interaction\n\t\t\t * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n\t\t\t */\n\t\t\tindex: indexMode,\n\n\t\t\t/**\n\t\t\t * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something\n\t\t\t * If the options.intersect is false, we find the nearest item and return the items in that dataset\n\t\t\t * @function Chart.Interaction.modes.dataset\n\t\t\t * @param chart {chart} the chart we are returning items from\n\t\t\t * @param e {Event} the event we are find things at\n\t\t\t * @param options {IInteractionOptions} options to use during interaction\n\t\t\t * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n\t\t\t */\n\t\t\tdataset: function(chart, e, options) {\n\t\t\t\tvar position = getRelativePosition(e, chart.chart);\n\t\t\t\tvar items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);\n\n\t\t\t\tif (items.length > 0) {\n\t\t\t\t\titems = chart.getDatasetMeta(items[0]._datasetIndex).data;\n\t\t\t\t}\n\n\t\t\t\treturn items;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * @function Chart.Interaction.modes.x-axis\n\t\t\t * @deprecated since version 2.4.0. Use index mode and intersect == true\n\t\t\t */\n\t\t\t'x-axis': function(chart, e) {\n\t\t\t\treturn indexMode(chart, e, true);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Point mode returns all elements that hit test based on the event position\n\t\t\t * of the event\n\t\t\t * @function Chart.Interaction.modes.intersect\n\t\t\t * @param chart {chart} the chart we are returning items from\n\t\t\t * @param e {Event} the event we are find things at\n\t\t\t * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n\t\t\t */\n\t\t\tpoint: function(chart, e) {\n\t\t\t\tvar position = getRelativePosition(e, chart.chart);\n\t\t\t\treturn getIntersectItems(chart, position);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * nearest mode returns the element closest to the point\n\t\t\t * @function Chart.Interaction.modes.intersect\n\t\t\t * @param chart {chart} the chart we are returning items from\n\t\t\t * @param e {Event} the event we are find things at\n\t\t\t * @param options {IInteractionOptions} options to use\n\t\t\t * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n\t\t\t */\n\t\t\tnearest: function(chart, e, options) {\n\t\t\t\tvar position = getRelativePosition(e, chart.chart);\n\t\t\t\tvar nearestItems = getNearestItems(chart, position, options.intersect);\n\n\t\t\t\t// We have multiple items at the same distance from the event. Now sort by smallest\n\t\t\t\tif (nearestItems.length > 1) {\n\t\t\t\t\tnearestItems.sort(function(a, b) {\n\t\t\t\t\t\tvar sizeA = a.getArea();\n\t\t\t\t\t\tvar sizeB = b.getArea();\n\t\t\t\t\t\tvar ret = sizeA - sizeB;\n\n\t\t\t\t\t\tif (ret === 0) {\n\t\t\t\t\t\t\t// if equal sort by dataset index\n\t\t\t\t\t\t\tret = a._datasetIndex - b._datasetIndex;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn ret;\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Return only 1 item\n\t\t\t\treturn nearestItems.slice(0, 1);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * x mode returns the elements that hit-test at the current x coordinate\n\t\t\t * @function Chart.Interaction.modes.x\n\t\t\t * @param chart {chart} the chart we are returning items from\n\t\t\t * @param e {Event} the event we are find things at\n\t\t\t * @param options {IInteractionOptions} options to use\n\t\t\t * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n\t\t\t */\n\t\t\tx: function(chart, e, options) {\n\t\t\t\tvar position = getRelativePosition(e, chart.chart);\n\t\t\t\tvar items = [];\n\t\t\t\tvar intersectsItem = false;\n\n\t\t\t\tparseVisibleItems(chart, function(element) {\n\t\t\t\t\tif (element.inXRange(position.x)) {\n\t\t\t\t\t\titems.push(element);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (element.inRange(position.x, position.y)) {\n\t\t\t\t\t\tintersectsItem = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// If we want to trigger on an intersect and we don't have any items\n\t\t\t\t// that intersect the position, return nothing\n\t\t\t\tif (options.intersect && !intersectsItem) {\n\t\t\t\t\titems = [];\n\t\t\t\t}\n\t\t\t\treturn items;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * y mode returns the elements that hit-test at the current y coordinate\n\t\t\t * @function Chart.Interaction.modes.y\n\t\t\t * @param chart {chart} the chart we are returning items from\n\t\t\t * @param e {Event} the event we are find things at\n\t\t\t * @param options {IInteractionOptions} options to use\n\t\t\t * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n\t\t\t */\n\t\t\ty: function(chart, e, options) {\n\t\t\t\tvar position = getRelativePosition(e, chart.chart);\n\t\t\t\tvar items = [];\n\t\t\t\tvar intersectsItem = false;\n\n\t\t\t\tparseVisibleItems(chart, function(element) {\n\t\t\t\t\tif (element.inYRange(position.y)) {\n\t\t\t\t\t\titems.push(element);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (element.inRange(position.x, position.y)) {\n\t\t\t\t\t\tintersectsItem = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// If we want to trigger on an intersect and we don't have any items\n\t\t\t\t// that intersect the position, return nothing\n\t\t\t\tif (options.intersect && !intersectsItem) {\n\t\t\t\t\titems = [];\n\t\t\t\t}\n\t\t\t\treturn items;\n\t\t\t}\n\t\t}\n\t};\n};\n\n},{}],28:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function() {\n\n\t// Occupy the global variable of Chart, and create a simple base class\n\tvar Chart = function(item, config) {\n\t\tthis.controller = new Chart.Controller(item, config, this);\n\t\treturn this.controller;\n\t};\n\n\t// Globally expose the defaults to allow for user updating/changing\n\tChart.defaults = {\n\t\tglobal: {\n\t\t\tresponsive: true,\n\t\t\tresponsiveAnimationDuration: 0,\n\t\t\tmaintainAspectRatio: true,\n\t\t\tevents: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],\n\t\t\thover: {\n\t\t\t\tonHover: null,\n\t\t\t\tmode: 'nearest',\n\t\t\t\tintersect: true,\n\t\t\t\tanimationDuration: 400\n\t\t\t},\n\t\t\tonClick: null,\n\t\t\tdefaultColor: 'rgba(0,0,0,0.1)',\n\t\t\tdefaultFontColor: '#666',\n\t\t\tdefaultFontFamily: \"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n\t\t\tdefaultFontSize: 12,\n\t\t\tdefaultFontStyle: 'normal',\n\t\t\tshowLines: true,\n\n\t\t\t// Element defaults defined in element extensions\n\t\t\telements: {},\n\n\t\t\t// Legend callback string\n\t\t\tlegendCallback: function(chart) {\n\t\t\t\tvar text = [];\n\t\t\t\ttext.push('');\n\t\t\t\tfor (var i = 0; i < chart.data.datasets.length; i++) {\n\t\t\t\t\ttext.push(' ');\n\t\t\t\t\tif (chart.data.datasets[i].label) {\n\t\t\t\t\t\ttext.push(chart.data.datasets[i].label);\n\t\t\t\t\t}\n\t\t\t\t\ttext.push(' ');\n\t\t\t\t}\n\t\t\t\ttext.push(' ');\n\n\t\t\t\treturn text.join('');\n\t\t\t}\n\t\t}\n\t};\n\n\tChart.Chart = Chart;\n\n\treturn Chart;\n};\n\n},{}],29:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\t// The layout service is very self explanatory. It's responsible for the layout within a chart.\n\t// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need\n\t// It is this service's responsibility of carrying out that layout.\n\tChart.layoutService = {\n\t\tdefaults: {},\n\n\t\t// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.\n\t\taddBox: function(chartInstance, box) {\n\t\t\tif (!chartInstance.boxes) {\n\t\t\t\tchartInstance.boxes = [];\n\t\t\t}\n\t\t\tchartInstance.boxes.push(box);\n\t\t},\n\n\t\tremoveBox: function(chartInstance, box) {\n\t\t\tif (!chartInstance.boxes) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tchartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);\n\t\t},\n\n\t\t// The most important function\n\t\tupdate: function(chartInstance, width, height) {\n\n\t\t\tif (!chartInstance) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar layoutOptions = chartInstance.options.layout;\n\t\t\tvar padding = layoutOptions ? layoutOptions.padding : null;\n\n\t\t\tvar leftPadding = 0;\n\t\t\tvar rightPadding = 0;\n\t\t\tvar topPadding = 0;\n\t\t\tvar bottomPadding = 0;\n\n\t\t\tif (!isNaN(padding)) {\n\t\t\t\t// options.layout.padding is a number. assign to all\n\t\t\t\tleftPadding = padding;\n\t\t\t\trightPadding = padding;\n\t\t\t\ttopPadding = padding;\n\t\t\t\tbottomPadding = padding;\n\t\t\t} else {\n\t\t\t\tleftPadding = padding.left || 0;\n\t\t\t\trightPadding = padding.right || 0;\n\t\t\t\ttopPadding = padding.top || 0;\n\t\t\t\tbottomPadding = padding.bottom || 0;\n\t\t\t}\n\n\t\t\tvar leftBoxes = helpers.where(chartInstance.boxes, function(box) {\n\t\t\t\treturn box.options.position === 'left';\n\t\t\t});\n\t\t\tvar rightBoxes = helpers.where(chartInstance.boxes, function(box) {\n\t\t\t\treturn box.options.position === 'right';\n\t\t\t});\n\t\t\tvar topBoxes = helpers.where(chartInstance.boxes, function(box) {\n\t\t\t\treturn box.options.position === 'top';\n\t\t\t});\n\t\t\tvar bottomBoxes = helpers.where(chartInstance.boxes, function(box) {\n\t\t\t\treturn box.options.position === 'bottom';\n\t\t\t});\n\n\t\t\t// Boxes that overlay the chartarea such as the radialLinear scale\n\t\t\tvar chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {\n\t\t\t\treturn box.options.position === 'chartArea';\n\t\t\t});\n\n\t\t\t// Ensure that full width boxes are at the very top / bottom\n\t\t\ttopBoxes.sort(function(a, b) {\n\t\t\t\treturn (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);\n\t\t\t});\n\t\t\tbottomBoxes.sort(function(a, b) {\n\t\t\t\treturn (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);\n\t\t\t});\n\n\t\t\t// Essentially we now have any number of boxes on each of the 4 sides.\n\t\t\t// Our canvas looks like the following.\n\t\t\t// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and\n\t\t\t// B1 is the bottom axis\n\t\t\t// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays\n\t\t\t// These locations are single-box locations only, when trying to register a chartArea location that is already taken,\n\t\t\t// an error will be thrown.\n\t\t\t//\n\t\t\t// |----------------------------------------------------|\n\t\t\t// | T1 (Full Width) |\n\t\t\t// |----------------------------------------------------|\n\t\t\t// | | | T2 | |\n\t\t\t// | |----|-------------------------------------|----|\n\t\t\t// | | | C1 | | C2 | |\n\t\t\t// | | |----| |----| |\n\t\t\t// | | | | |\n\t\t\t// | L1 | L2 | ChartArea (C0) | R1 |\n\t\t\t// | | | | |\n\t\t\t// | | |----| |----| |\n\t\t\t// | | | C3 | | C4 | |\n\t\t\t// | |----|-------------------------------------|----|\n\t\t\t// | | | B1 | |\n\t\t\t// |----------------------------------------------------|\n\t\t\t// | B2 (Full Width) |\n\t\t\t// |----------------------------------------------------|\n\t\t\t//\n\t\t\t// What we do to find the best sizing, we do the following\n\t\t\t// 1. Determine the minimum size of the chart area.\n\t\t\t// 2. Split the remaining width equally between each vertical axis\n\t\t\t// 3. Split the remaining height equally between each horizontal axis\n\t\t\t// 4. Give each layout the maximum size it can be. The layout will return it's minimum size\n\t\t\t// 5. Adjust the sizes of each axis based on it's minimum reported size.\n\t\t\t// 6. Refit each axis\n\t\t\t// 7. Position each axis in the final location\n\t\t\t// 8. Tell the chart the final location of the chart area\n\t\t\t// 9. Tell any axes that overlay the chart area the positions of the chart area\n\n\t\t\t// Step 1\n\t\t\tvar chartWidth = width - leftPadding - rightPadding;\n\t\t\tvar chartHeight = height - topPadding - bottomPadding;\n\t\t\tvar chartAreaWidth = chartWidth / 2; // min 50%\n\t\t\tvar chartAreaHeight = chartHeight / 2; // min 50%\n\n\t\t\t// Step 2\n\t\t\tvar verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);\n\n\t\t\t// Step 3\n\t\t\tvar horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);\n\n\t\t\t// Step 4\n\t\t\tvar maxChartAreaWidth = chartWidth;\n\t\t\tvar maxChartAreaHeight = chartHeight;\n\t\t\tvar minBoxSizes = [];\n\n\t\t\tfunction getMinimumBoxSize(box) {\n\t\t\t\tvar minSize;\n\t\t\t\tvar isHorizontal = box.isHorizontal();\n\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\tminSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);\n\t\t\t\t\tmaxChartAreaHeight -= minSize.height;\n\t\t\t\t} else {\n\t\t\t\t\tminSize = box.update(verticalBoxWidth, chartAreaHeight);\n\t\t\t\t\tmaxChartAreaWidth -= minSize.width;\n\t\t\t\t}\n\n\t\t\t\tminBoxSizes.push({\n\t\t\t\t\thorizontal: isHorizontal,\n\t\t\t\t\tminSize: minSize,\n\t\t\t\t\tbox: box,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thelpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);\n\n\t\t\t// If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)\n\t\t\tvar maxHorizontalLeftPadding = 0;\n\t\t\tvar maxHorizontalRightPadding = 0;\n\t\t\tvar maxVerticalTopPadding = 0;\n\t\t\tvar maxVerticalBottomPadding = 0;\n\n\t\t\thelpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {\n\t\t\t\tif (horizontalBox.getPadding) {\n\t\t\t\t\tvar boxPadding = horizontalBox.getPadding();\n\t\t\t\t\tmaxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);\n\t\t\t\t\tmaxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\thelpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {\n\t\t\t\tif (verticalBox.getPadding) {\n\t\t\t\t\tvar boxPadding = verticalBox.getPadding();\n\t\t\t\t\tmaxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);\n\t\t\t\t\tmaxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could\n\t\t\t// be if the axes are drawn at their minimum sizes.\n\t\t\t// Steps 5 & 6\n\t\t\tvar totalLeftBoxesWidth = leftPadding;\n\t\t\tvar totalRightBoxesWidth = rightPadding;\n\t\t\tvar totalTopBoxesHeight = topPadding;\n\t\t\tvar totalBottomBoxesHeight = bottomPadding;\n\n\t\t\t// Function to fit a box\n\t\t\tfunction fitBox(box) {\n\t\t\t\tvar minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {\n\t\t\t\t\treturn minBox.box === box;\n\t\t\t\t});\n\n\t\t\t\tif (minBoxSize) {\n\t\t\t\t\tif (box.isHorizontal()) {\n\t\t\t\t\t\tvar scaleMargin = {\n\t\t\t\t\t\t\tleft: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),\n\t\t\t\t\t\t\tright: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),\n\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\tbottom: 0\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends\n\t\t\t\t\t\t// on the margin. Sometimes they need to increase in size slightly\n\t\t\t\t\t\tbox.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbox.update(minBoxSize.minSize.width, maxChartAreaHeight);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update, and calculate the left and right margins for the horizontal boxes\n\t\t\thelpers.each(leftBoxes.concat(rightBoxes), fitBox);\n\n\t\t\thelpers.each(leftBoxes, function(box) {\n\t\t\t\ttotalLeftBoxesWidth += box.width;\n\t\t\t});\n\n\t\t\thelpers.each(rightBoxes, function(box) {\n\t\t\t\ttotalRightBoxesWidth += box.width;\n\t\t\t});\n\n\t\t\t// Set the Left and Right margins for the horizontal boxes\n\t\t\thelpers.each(topBoxes.concat(bottomBoxes), fitBox);\n\n\t\t\t// Figure out how much margin is on the top and bottom of the vertical boxes\n\t\t\thelpers.each(topBoxes, function(box) {\n\t\t\t\ttotalTopBoxesHeight += box.height;\n\t\t\t});\n\n\t\t\thelpers.each(bottomBoxes, function(box) {\n\t\t\t\ttotalBottomBoxesHeight += box.height;\n\t\t\t});\n\n\t\t\tfunction finalFitVerticalBox(box) {\n\t\t\t\tvar minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {\n\t\t\t\t\treturn minSize.box === box;\n\t\t\t\t});\n\n\t\t\t\tvar scaleMargin = {\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: 0,\n\t\t\t\t\ttop: totalTopBoxesHeight,\n\t\t\t\t\tbottom: totalBottomBoxesHeight\n\t\t\t\t};\n\n\t\t\t\tif (minBoxSize) {\n\t\t\t\t\tbox.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Let the left layout know the final margin\n\t\t\thelpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);\n\n\t\t\t// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)\n\t\t\ttotalLeftBoxesWidth = leftPadding;\n\t\t\ttotalRightBoxesWidth = rightPadding;\n\t\t\ttotalTopBoxesHeight = topPadding;\n\t\t\ttotalBottomBoxesHeight = bottomPadding;\n\n\t\t\thelpers.each(leftBoxes, function(box) {\n\t\t\t\ttotalLeftBoxesWidth += box.width;\n\t\t\t});\n\n\t\t\thelpers.each(rightBoxes, function(box) {\n\t\t\t\ttotalRightBoxesWidth += box.width;\n\t\t\t});\n\n\t\t\thelpers.each(topBoxes, function(box) {\n\t\t\t\ttotalTopBoxesHeight += box.height;\n\t\t\t});\n\t\t\thelpers.each(bottomBoxes, function(box) {\n\t\t\t\ttotalBottomBoxesHeight += box.height;\n\t\t\t});\n\n\t\t\t// We may be adding some padding to account for rotated x axis labels\n\t\t\tvar leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);\n\t\t\ttotalLeftBoxesWidth += leftPaddingAddition;\n\t\t\ttotalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);\n\n\t\t\tvar topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);\n\t\t\ttotalTopBoxesHeight += topPaddingAddition;\n\t\t\ttotalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);\n\n\t\t\t// Figure out if our chart area changed. This would occur if the dataset layout label rotation\n\t\t\t// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do\n\t\t\t// without calling `fit` again\n\t\t\tvar newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;\n\t\t\tvar newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;\n\n\t\t\tif (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {\n\t\t\t\thelpers.each(leftBoxes, function(box) {\n\t\t\t\t\tbox.height = newMaxChartAreaHeight;\n\t\t\t\t});\n\n\t\t\t\thelpers.each(rightBoxes, function(box) {\n\t\t\t\t\tbox.height = newMaxChartAreaHeight;\n\t\t\t\t});\n\n\t\t\t\thelpers.each(topBoxes, function(box) {\n\t\t\t\t\tif (!box.options.fullWidth) {\n\t\t\t\t\t\tbox.width = newMaxChartAreaWidth;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\thelpers.each(bottomBoxes, function(box) {\n\t\t\t\t\tif (!box.options.fullWidth) {\n\t\t\t\t\t\tbox.width = newMaxChartAreaWidth;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tmaxChartAreaHeight = newMaxChartAreaHeight;\n\t\t\t\tmaxChartAreaWidth = newMaxChartAreaWidth;\n\t\t\t}\n\n\t\t\t// Step 7 - Position the boxes\n\t\t\tvar left = leftPadding + leftPaddingAddition;\n\t\t\tvar top = topPadding + topPaddingAddition;\n\n\t\t\tfunction placeBox(box) {\n\t\t\t\tif (box.isHorizontal()) {\n\t\t\t\t\tbox.left = box.options.fullWidth ? leftPadding : totalLeftBoxesWidth;\n\t\t\t\t\tbox.right = box.options.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;\n\t\t\t\t\tbox.top = top;\n\t\t\t\t\tbox.bottom = top + box.height;\n\n\t\t\t\t\t// Move to next point\n\t\t\t\t\ttop = box.bottom;\n\n\t\t\t\t} else {\n\n\t\t\t\t\tbox.left = left;\n\t\t\t\t\tbox.right = left + box.width;\n\t\t\t\t\tbox.top = totalTopBoxesHeight;\n\t\t\t\t\tbox.bottom = totalTopBoxesHeight + maxChartAreaHeight;\n\n\t\t\t\t\t// Move to next point\n\t\t\t\t\tleft = box.right;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\thelpers.each(leftBoxes.concat(topBoxes), placeBox);\n\n\t\t\t// Account for chart width and height\n\t\t\tleft += maxChartAreaWidth;\n\t\t\ttop += maxChartAreaHeight;\n\n\t\t\thelpers.each(rightBoxes, placeBox);\n\t\t\thelpers.each(bottomBoxes, placeBox);\n\n\t\t\t// Step 8\n\t\t\tchartInstance.chartArea = {\n\t\t\t\tleft: totalLeftBoxesWidth,\n\t\t\t\ttop: totalTopBoxesHeight,\n\t\t\t\tright: totalLeftBoxesWidth + maxChartAreaWidth,\n\t\t\t\tbottom: totalTopBoxesHeight + maxChartAreaHeight\n\t\t\t};\n\n\t\t\t// Step 9\n\t\t\thelpers.each(chartAreaBoxes, function(box) {\n\t\t\t\tbox.left = chartInstance.chartArea.left;\n\t\t\t\tbox.top = chartInstance.chartArea.top;\n\t\t\t\tbox.right = chartInstance.chartArea.right;\n\t\t\t\tbox.bottom = chartInstance.chartArea.bottom;\n\n\t\t\t\tbox.update(maxChartAreaWidth, maxChartAreaHeight);\n\t\t\t});\n\t\t}\n\t};\n};\n\n},{}],30:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\tvar noop = helpers.noop;\n\n\tChart.defaults.global.legend = {\n\n\t\tdisplay: true,\n\t\tposition: 'top',\n\t\tfullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)\n\t\treverse: false,\n\n\t\t// a callback that will handle\n\t\tonClick: function(e, legendItem) {\n\t\t\tvar index = legendItem.datasetIndex;\n\t\t\tvar ci = this.chart;\n\t\t\tvar meta = ci.getDatasetMeta(index);\n\n\t\t\t// See controller.isDatasetVisible comment\n\t\t\tmeta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;\n\n\t\t\t// We hid a dataset ... rerender the chart\n\t\t\tci.update();\n\t\t},\n\n\t\tonHover: null,\n\n\t\tlabels: {\n\t\t\tboxWidth: 40,\n\t\t\tpadding: 10,\n\t\t\t// Generates labels shown in the legend\n\t\t\t// Valid properties to return:\n\t\t\t// text : text to display\n\t\t\t// fillStyle : fill of coloured box\n\t\t\t// strokeStyle: stroke of coloured box\n\t\t\t// hidden : if this legend item refers to a hidden item\n\t\t\t// lineCap : cap style for line\n\t\t\t// lineDash\n\t\t\t// lineDashOffset :\n\t\t\t// lineJoin :\n\t\t\t// lineWidth :\n\t\t\tgenerateLabels: function(chart) {\n\t\t\t\tvar data = chart.data;\n\t\t\t\treturn helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttext: dataset.label,\n\t\t\t\t\t\tfillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),\n\t\t\t\t\t\thidden: !chart.isDatasetVisible(i),\n\t\t\t\t\t\tlineCap: dataset.borderCapStyle,\n\t\t\t\t\t\tlineDash: dataset.borderDash,\n\t\t\t\t\t\tlineDashOffset: dataset.borderDashOffset,\n\t\t\t\t\t\tlineJoin: dataset.borderJoinStyle,\n\t\t\t\t\t\tlineWidth: dataset.borderWidth,\n\t\t\t\t\t\tstrokeStyle: dataset.borderColor,\n\t\t\t\t\t\tpointStyle: dataset.pointStyle,\n\n\t\t\t\t\t\t// Below is extra data used for toggling the datasets\n\t\t\t\t\t\tdatasetIndex: i\n\t\t\t\t\t};\n\t\t\t\t}, this) : [];\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Helper function to get the box width based on the usePointStyle option\n\t * @param labelopts {Object} the label options on the legend\n\t * @param fontSize {Number} the label font size\n\t * @return {Number} width of the color box area\n\t */\n\tfunction getBoxWidth(labelOpts, fontSize) {\n\t\treturn labelOpts.usePointStyle ?\n\t\t\tfontSize * Math.SQRT2 :\n\t\t\tlabelOpts.boxWidth;\n\t}\n\n\tChart.Legend = Chart.Element.extend({\n\n\t\tinitialize: function(config) {\n\t\t\thelpers.extend(this, config);\n\n\t\t\t// Contains hit boxes for each dataset (in dataset order)\n\t\t\tthis.legendHitBoxes = [];\n\n\t\t\t// Are we in doughnut mode which has a different data type\n\t\t\tthis.doughnutMode = false;\n\t\t},\n\n\t\t// These methods are ordered by lifecycle. Utilities then follow.\n\t\t// Any function defined here is inherited by all legend types.\n\t\t// Any function can be extended by the legend type\n\n\t\tbeforeUpdate: noop,\n\t\tupdate: function(maxWidth, maxHeight, margins) {\n\t\t\tvar me = this;\n\n\t\t\t// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)\n\t\t\tme.beforeUpdate();\n\n\t\t\t// Absorb the master measurements\n\t\t\tme.maxWidth = maxWidth;\n\t\t\tme.maxHeight = maxHeight;\n\t\t\tme.margins = margins;\n\n\t\t\t// Dimensions\n\t\t\tme.beforeSetDimensions();\n\t\t\tme.setDimensions();\n\t\t\tme.afterSetDimensions();\n\t\t\t// Labels\n\t\t\tme.beforeBuildLabels();\n\t\t\tme.buildLabels();\n\t\t\tme.afterBuildLabels();\n\n\t\t\t// Fit\n\t\t\tme.beforeFit();\n\t\t\tme.fit();\n\t\t\tme.afterFit();\n\t\t\t//\n\t\t\tme.afterUpdate();\n\n\t\t\treturn me.minSize;\n\t\t},\n\t\tafterUpdate: noop,\n\n\t\t//\n\n\t\tbeforeSetDimensions: noop,\n\t\tsetDimensions: function() {\n\t\t\tvar me = this;\n\t\t\t// Set the unconstrained dimension before label rotation\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\t// Reset position before calculating rotation\n\t\t\t\tme.width = me.maxWidth;\n\t\t\t\tme.left = 0;\n\t\t\t\tme.right = me.width;\n\t\t\t} else {\n\t\t\t\tme.height = me.maxHeight;\n\n\t\t\t\t// Reset position before calculating rotation\n\t\t\t\tme.top = 0;\n\t\t\t\tme.bottom = me.height;\n\t\t\t}\n\n\t\t\t// Reset padding\n\t\t\tme.paddingLeft = 0;\n\t\t\tme.paddingTop = 0;\n\t\t\tme.paddingRight = 0;\n\t\t\tme.paddingBottom = 0;\n\n\t\t\t// Reset minSize\n\t\t\tme.minSize = {\n\t\t\t\twidth: 0,\n\t\t\t\theight: 0\n\t\t\t};\n\t\t},\n\t\tafterSetDimensions: noop,\n\n\t\t//\n\n\t\tbeforeBuildLabels: noop,\n\t\tbuildLabels: function() {\n\t\t\tvar me = this;\n\t\t\tvar labelOpts = me.options.labels;\n\t\t\tvar legendItems = labelOpts.generateLabels.call(me, me.chart);\n\n\t\t\tif (labelOpts.filter) {\n\t\t\t\tlegendItems = legendItems.filter(function(item) {\n\t\t\t\t\treturn labelOpts.filter(item, me.chart.data);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (me.options.reverse) {\n\t\t\t\tlegendItems.reverse();\n\t\t\t}\n\n\t\t\tme.legendItems = legendItems;\n\t\t},\n\t\tafterBuildLabels: noop,\n\n\t\t//\n\n\t\tbeforeFit: noop,\n\t\tfit: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar labelOpts = opts.labels;\n\t\t\tvar display = opts.display;\n\n\t\t\tvar ctx = me.ctx;\n\n\t\t\tvar globalDefault = Chart.defaults.global,\n\t\t\t\titemOrDefault = helpers.getValueOrDefault,\n\t\t\t\tfontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),\n\t\t\t\tfontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),\n\t\t\t\tfontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),\n\t\t\t\tlabelFont = helpers.fontString(fontSize, fontStyle, fontFamily);\n\n\t\t\t// Reset hit boxes\n\t\t\tvar hitboxes = me.legendHitBoxes = [];\n\n\t\t\tvar minSize = me.minSize;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\n\t\t\tif (isHorizontal) {\n\t\t\t\tminSize.width = me.maxWidth; // fill all the width\n\t\t\t\tminSize.height = display ? 10 : 0;\n\t\t\t} else {\n\t\t\t\tminSize.width = display ? 10 : 0;\n\t\t\t\tminSize.height = me.maxHeight; // fill all the height\n\t\t\t}\n\n\t\t\t// Increase sizes here\n\t\t\tif (display) {\n\t\t\t\tctx.font = labelFont;\n\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\t// Labels\n\n\t\t\t\t\t// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one\n\t\t\t\t\tvar lineWidths = me.lineWidths = [0];\n\t\t\t\t\tvar totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;\n\n\t\t\t\t\tctx.textAlign = 'left';\n\t\t\t\t\tctx.textBaseline = 'top';\n\n\t\t\t\t\thelpers.each(me.legendItems, function(legendItem, i) {\n\t\t\t\t\t\tvar boxWidth = getBoxWidth(labelOpts, fontSize);\n\t\t\t\t\t\tvar width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;\n\n\t\t\t\t\t\tif (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {\n\t\t\t\t\t\t\ttotalHeight += fontSize + (labelOpts.padding);\n\t\t\t\t\t\t\tlineWidths[lineWidths.length] = me.left;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Store the hitbox width and height here. Final position will be updated in `draw`\n\t\t\t\t\t\thitboxes[i] = {\n\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\twidth: width,\n\t\t\t\t\t\t\theight: fontSize\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tlineWidths[lineWidths.length - 1] += width + labelOpts.padding;\n\t\t\t\t\t});\n\n\t\t\t\t\tminSize.height += totalHeight;\n\n\t\t\t\t} else {\n\t\t\t\t\tvar vPadding = labelOpts.padding;\n\t\t\t\t\tvar columnWidths = me.columnWidths = [];\n\t\t\t\t\tvar totalWidth = labelOpts.padding;\n\t\t\t\t\tvar currentColWidth = 0;\n\t\t\t\t\tvar currentColHeight = 0;\n\t\t\t\t\tvar itemHeight = fontSize + vPadding;\n\n\t\t\t\t\thelpers.each(me.legendItems, function(legendItem, i) {\n\t\t\t\t\t\tvar boxWidth = getBoxWidth(labelOpts, fontSize);\n\t\t\t\t\t\tvar itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;\n\n\t\t\t\t\t\t// If too tall, go to new column\n\t\t\t\t\t\tif (currentColHeight + itemHeight > minSize.height) {\n\t\t\t\t\t\t\ttotalWidth += currentColWidth + labelOpts.padding;\n\t\t\t\t\t\t\tcolumnWidths.push(currentColWidth); // previous column width\n\n\t\t\t\t\t\t\tcurrentColWidth = 0;\n\t\t\t\t\t\t\tcurrentColHeight = 0;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Get max width\n\t\t\t\t\t\tcurrentColWidth = Math.max(currentColWidth, itemWidth);\n\t\t\t\t\t\tcurrentColHeight += itemHeight;\n\n\t\t\t\t\t\t// Store the hitbox width and height here. Final position will be updated in `draw`\n\t\t\t\t\t\thitboxes[i] = {\n\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\twidth: itemWidth,\n\t\t\t\t\t\t\theight: fontSize\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\n\t\t\t\t\ttotalWidth += currentColWidth;\n\t\t\t\t\tcolumnWidths.push(currentColWidth);\n\t\t\t\t\tminSize.width += totalWidth;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tme.width = minSize.width;\n\t\t\tme.height = minSize.height;\n\t\t},\n\t\tafterFit: noop,\n\n\t\t// Shared Methods\n\t\tisHorizontal: function() {\n\t\t\treturn this.options.position === 'top' || this.options.position === 'bottom';\n\t\t},\n\n\t\t// Actually draw the legend on the canvas\n\t\tdraw: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar labelOpts = opts.labels;\n\t\t\tvar globalDefault = Chart.defaults.global,\n\t\t\t\tlineDefault = globalDefault.elements.line,\n\t\t\t\tlegendWidth = me.width,\n\t\t\t\tlineWidths = me.lineWidths;\n\n\t\t\tif (opts.display) {\n\t\t\t\tvar ctx = me.ctx,\n\t\t\t\t\tcursor,\n\t\t\t\t\titemOrDefault = helpers.getValueOrDefault,\n\t\t\t\t\tfontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor),\n\t\t\t\t\tfontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),\n\t\t\t\t\tfontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),\n\t\t\t\t\tfontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),\n\t\t\t\t\tlabelFont = helpers.fontString(fontSize, fontStyle, fontFamily);\n\n\t\t\t\t// Canvas setup\n\t\t\t\tctx.textAlign = 'left';\n\t\t\t\tctx.textBaseline = 'top';\n\t\t\t\tctx.lineWidth = 0.5;\n\t\t\t\tctx.strokeStyle = fontColor; // for strikethrough effect\n\t\t\t\tctx.fillStyle = fontColor; // render in correct colour\n\t\t\t\tctx.font = labelFont;\n\n\t\t\t\tvar boxWidth = getBoxWidth(labelOpts, fontSize),\n\t\t\t\t\thitboxes = me.legendHitBoxes;\n\n\t\t\t\t// current position\n\t\t\t\tvar drawLegendBox = function(x, y, legendItem) {\n\t\t\t\t\tif (isNaN(boxWidth) || boxWidth <= 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Set the ctx for the box\n\t\t\t\t\tctx.save();\n\n\t\t\t\t\tctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor);\n\t\t\t\t\tctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);\n\t\t\t\t\tctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);\n\t\t\t\t\tctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);\n\t\t\t\t\tctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth);\n\t\t\t\t\tctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);\n\t\t\t\t\tvar isLineWidthZero = (itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);\n\n\t\t\t\t\tif (ctx.setLineDash) {\n\t\t\t\t\t\t// IE 9 and 10 do not support line dash\n\t\t\t\t\t\tctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));\n\t\t\t\t\t}\n\n\t\t\t\t\tif (opts.labels && opts.labels.usePointStyle) {\n\t\t\t\t\t\t// Recalculate x and y for drawPoint() because its expecting\n\t\t\t\t\t\t// x and y to be center of figure (instead of top left)\n\t\t\t\t\t\tvar radius = fontSize * Math.SQRT2 / 2;\n\t\t\t\t\t\tvar offSet = radius / Math.SQRT2;\n\t\t\t\t\t\tvar centerX = x + offSet;\n\t\t\t\t\t\tvar centerY = y + offSet;\n\n\t\t\t\t\t\t// Draw pointStyle as legend symbol\n\t\t\t\t\t\tChart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Draw box as legend symbol\n\t\t\t\t\t\tif (!isLineWidthZero) {\n\t\t\t\t\t\t\tctx.strokeRect(x, y, boxWidth, fontSize);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctx.fillRect(x, y, boxWidth, fontSize);\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.restore();\n\t\t\t\t};\n\t\t\t\tvar fillText = function(x, y, legendItem, textWidth) {\n\t\t\t\t\tctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y);\n\n\t\t\t\t\tif (legendItem.hidden) {\n\t\t\t\t\t\t// Strikethrough the text if hidden\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.lineWidth = 2;\n\t\t\t\t\t\tctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2));\n\t\t\t\t\t\tctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2));\n\t\t\t\t\t\tctx.stroke();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Horizontal\n\t\t\t\tvar isHorizontal = me.isHorizontal();\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\tcursor = {\n\t\t\t\t\t\tx: me.left + ((legendWidth - lineWidths[0]) / 2),\n\t\t\t\t\t\ty: me.top + labelOpts.padding,\n\t\t\t\t\t\tline: 0\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\tcursor = {\n\t\t\t\t\t\tx: me.left + labelOpts.padding,\n\t\t\t\t\t\ty: me.top + labelOpts.padding,\n\t\t\t\t\t\tline: 0\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tvar itemHeight = fontSize + labelOpts.padding;\n\t\t\t\thelpers.each(me.legendItems, function(legendItem, i) {\n\t\t\t\t\tvar textWidth = ctx.measureText(legendItem.text).width,\n\t\t\t\t\t\twidth = boxWidth + (fontSize / 2) + textWidth,\n\t\t\t\t\t\tx = cursor.x,\n\t\t\t\t\t\ty = cursor.y;\n\n\t\t\t\t\tif (isHorizontal) {\n\t\t\t\t\t\tif (x + width >= legendWidth) {\n\t\t\t\t\t\t\ty = cursor.y += itemHeight;\n\t\t\t\t\t\t\tcursor.line++;\n\t\t\t\t\t\t\tx = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (y + itemHeight > me.bottom) {\n\t\t\t\t\t\tx = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;\n\t\t\t\t\t\ty = cursor.y = me.top + labelOpts.padding;\n\t\t\t\t\t\tcursor.line++;\n\t\t\t\t\t}\n\n\t\t\t\t\tdrawLegendBox(x, y, legendItem);\n\n\t\t\t\t\thitboxes[i].left = x;\n\t\t\t\t\thitboxes[i].top = y;\n\n\t\t\t\t\t// Fill the actual label\n\t\t\t\t\tfillText(x, y, legendItem, textWidth);\n\n\t\t\t\t\tif (isHorizontal) {\n\t\t\t\t\t\tcursor.x += width + (labelOpts.padding);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcursor.y += itemHeight;\n\t\t\t\t\t}\n\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle an event\n\t\t * @private\n\t\t * @param {IEvent} event - The event to handle\n\t\t * @return {Boolean} true if a change occured\n\t\t */\n\t\thandleEvent: function(e) {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar type = e.type === 'mouseup' ? 'click' : e.type;\n\t\t\tvar changed = false;\n\n\t\t\tif (type === 'mousemove') {\n\t\t\t\tif (!opts.onHover) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else if (type === 'click') {\n\t\t\t\tif (!opts.onClick) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Chart event already has relative position in it\n\t\t\tvar x = e.x,\n\t\t\t\ty = e.y;\n\n\t\t\tif (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {\n\t\t\t\t// See if we are touching one of the dataset boxes\n\t\t\t\tvar lh = me.legendHitBoxes;\n\t\t\t\tfor (var i = 0; i < lh.length; ++i) {\n\t\t\t\t\tvar hitBox = lh[i];\n\n\t\t\t\t\tif (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {\n\t\t\t\t\t\t// Touching an element\n\t\t\t\t\t\tif (type === 'click') {\n\t\t\t\t\t\t\t// use e.native for backwards compatibility\n\t\t\t\t\t\t\topts.onClick.call(me, e.native, me.legendItems[i]);\n\t\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else if (type === 'mousemove') {\n\t\t\t\t\t\t\t// use e.native for backwards compatibility\n\t\t\t\t\t\t\topts.onHover.call(me, e.native, me.legendItems[i]);\n\t\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn changed;\n\t\t}\n\t});\n\n\tfunction createNewLegendAndAttach(chartInstance, legendOpts) {\n\t\tvar legend = new Chart.Legend({\n\t\t\tctx: chartInstance.chart.ctx,\n\t\t\toptions: legendOpts,\n\t\t\tchart: chartInstance\n\t\t});\n\t\tchartInstance.legend = legend;\n\t\tChart.layoutService.addBox(chartInstance, legend);\n\t}\n\n\t// Register the legend plugin\n\tChart.plugins.register({\n\t\tbeforeInit: function(chartInstance) {\n\t\t\tvar legendOpts = chartInstance.options.legend;\n\n\t\t\tif (legendOpts) {\n\t\t\t\tcreateNewLegendAndAttach(chartInstance, legendOpts);\n\t\t\t}\n\t\t},\n\t\tbeforeUpdate: function(chartInstance) {\n\t\t\tvar legendOpts = chartInstance.options.legend;\n\n\t\t\tif (legendOpts) {\n\t\t\t\tlegendOpts = helpers.configMerge(Chart.defaults.global.legend, legendOpts);\n\n\t\t\t\tif (chartInstance.legend) {\n\t\t\t\t\tchartInstance.legend.options = legendOpts;\n\t\t\t\t} else {\n\t\t\t\t\tcreateNewLegendAndAttach(chartInstance, legendOpts);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tChart.layoutService.removeBox(chartInstance, chartInstance.legend);\n\t\t\t\tdelete chartInstance.legend;\n\t\t\t}\n\t\t},\n\t\tafterEvent: function(chartInstance, e) {\n\t\t\tvar legend = chartInstance.legend;\n\t\t\tif (legend) {\n\t\t\t\tlegend.handleEvent(e);\n\t\t\t}\n\t\t}\n\t});\n};\n\n},{}],31:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.global.plugins = {};\n\n\t/**\n\t * The plugin service singleton\n\t * @namespace Chart.plugins\n\t * @since 2.1.0\n\t */\n\tChart.plugins = {\n\t\t/**\n\t\t * Globally registered plugins.\n\t\t * @private\n\t\t */\n\t\t_plugins: [],\n\n\t\t/**\n\t\t * This identifier is used to invalidate the descriptors cache attached to each chart\n\t\t * when a global plugin is registered or unregistered. In this case, the cache ID is\n\t\t * incremented and descriptors are regenerated during following API calls.\n\t\t * @private\n\t\t */\n\t\t_cacheId: 0,\n\n\t\t/**\n\t\t * Registers the given plugin(s) if not already registered.\n\t\t * @param {Array|Object} plugins plugin instance(s).\n\t\t */\n\t\tregister: function(plugins) {\n\t\t\tvar p = this._plugins;\n\t\t\t([]).concat(plugins).forEach(function(plugin) {\n\t\t\t\tif (p.indexOf(plugin) === -1) {\n\t\t\t\t\tp.push(plugin);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis._cacheId++;\n\t\t},\n\n\t\t/**\n\t\t * Unregisters the given plugin(s) only if registered.\n\t\t * @param {Array|Object} plugins plugin instance(s).\n\t\t */\n\t\tunregister: function(plugins) {\n\t\t\tvar p = this._plugins;\n\t\t\t([]).concat(plugins).forEach(function(plugin) {\n\t\t\t\tvar idx = p.indexOf(plugin);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\tp.splice(idx, 1);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis._cacheId++;\n\t\t},\n\n\t\t/**\n\t\t * Remove all registered plugins.\n\t\t * @since 2.1.5\n\t\t */\n\t\tclear: function() {\n\t\t\tthis._plugins = [];\n\t\t\tthis._cacheId++;\n\t\t},\n\n\t\t/**\n\t\t * Returns the number of registered plugins?\n\t\t * @returns {Number}\n\t\t * @since 2.1.5\n\t\t */\n\t\tcount: function() {\n\t\t\treturn this._plugins.length;\n\t\t},\n\n\t\t/**\n\t\t * Returns all registered plugin instances.\n\t\t * @returns {Array} array of plugin objects.\n\t\t * @since 2.1.5\n\t\t */\n\t\tgetAll: function() {\n\t\t\treturn this._plugins;\n\t\t},\n\n\t\t/**\n\t\t * Calls enabled plugins for `chart` on the specified hook and with the given args.\n\t\t * This method immediately returns as soon as a plugin explicitly returns false. The\n\t\t * returned value can be used, for instance, to interrupt the current action.\n\t\t * @param {Object} chart - The chart instance for which plugins should be called.\n\t\t * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').\n\t\t * @param {Array} [args] - Extra arguments to apply to the hook call.\n\t\t * @returns {Boolean} false if any of the plugins return false, else returns true.\n\t\t */\n\t\tnotify: function(chart, hook, args) {\n\t\t\tvar descriptors = this.descriptors(chart);\n\t\t\tvar ilen = descriptors.length;\n\t\t\tvar i, descriptor, plugin, params, method;\n\n\t\t\tfor (i=0; i tickWidth && labelRotation < tickOpts.maxRotation) {\n\t\t\t\t\tvar angleRadians = helpers.toRadians(labelRotation);\n\t\t\t\t\tcosRotation = Math.cos(angleRadians);\n\t\t\t\t\tsinRotation = Math.sin(angleRadians);\n\n\t\t\t\t\tif (sinRotation * originalLabelWidth > me.maxHeight) {\n\t\t\t\t\t\t// go back one step\n\t\t\t\t\t\tlabelRotation--;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tlabelRotation++;\n\t\t\t\t\tlabelWidth = cosRotation * originalLabelWidth;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tme.labelRotation = labelRotation;\n\t\t},\n\t\tafterCalculateTickRotation: function() {\n\t\t\thelpers.callCallback(this.options.afterCalculateTickRotation, [this]);\n\t\t},\n\n\t\t//\n\n\t\tbeforeFit: function() {\n\t\t\thelpers.callCallback(this.options.beforeFit, [this]);\n\t\t},\n\t\tfit: function() {\n\t\t\tvar me = this;\n\t\t\t// Reset\n\t\t\tvar minSize = me.minSize = {\n\t\t\t\twidth: 0,\n\t\t\t\theight: 0\n\t\t\t};\n\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\t\t\tvar scaleLabelOpts = opts.scaleLabel;\n\t\t\tvar gridLineOpts = opts.gridLines;\n\t\t\tvar display = opts.display;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\n\t\t\tvar tickFont = parseFontOptions(tickOpts);\n\t\t\tvar scaleLabelFontSize = parseFontOptions(scaleLabelOpts).size * 1.5;\n\t\t\tvar tickMarkLength = opts.gridLines.tickMarkLength;\n\n\t\t\t// Width\n\t\t\tif (isHorizontal) {\n\t\t\t\t// subtract the margins to line up with the chartArea if we are a full width scale\n\t\t\t\tminSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;\n\t\t\t} else {\n\t\t\t\tminSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;\n\t\t\t}\n\n\t\t\t// height\n\t\t\tif (isHorizontal) {\n\t\t\t\tminSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;\n\t\t\t} else {\n\t\t\t\tminSize.height = me.maxHeight; // fill all the height\n\t\t\t}\n\n\t\t\t// Are we showing a title for the scale?\n\t\t\tif (scaleLabelOpts.display && display) {\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\tminSize.height += scaleLabelFontSize;\n\t\t\t\t} else {\n\t\t\t\t\tminSize.width += scaleLabelFontSize;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Don't bother fitting the ticks if we are not showing them\n\t\t\tif (tickOpts.display && display) {\n\t\t\t\tvar largestTextWidth = helpers.longestText(me.ctx, tickFont.font, me.ticks, me.longestTextCache);\n\t\t\t\tvar tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks);\n\t\t\t\tvar lineSpace = tickFont.size * 0.5;\n\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\t// A horizontal axis is more constrained by the height.\n\t\t\t\t\tme.longestLabelWidth = largestTextWidth;\n\n\t\t\t\t\tvar angleRadians = helpers.toRadians(me.labelRotation);\n\t\t\t\t\tvar cosRotation = Math.cos(angleRadians);\n\t\t\t\t\tvar sinRotation = Math.sin(angleRadians);\n\n\t\t\t\t\t// TODO - improve this calculation\n\t\t\t\t\tvar labelHeight = (sinRotation * largestTextWidth)\n\t\t\t\t\t\t+ (tickFont.size * tallestLabelHeightInLines)\n\t\t\t\t\t\t+ (lineSpace * tallestLabelHeightInLines);\n\n\t\t\t\t\tminSize.height = Math.min(me.maxHeight, minSize.height + labelHeight);\n\t\t\t\t\tme.ctx.font = tickFont.font;\n\n\t\t\t\t\tvar firstTick = me.ticks[0];\n\t\t\t\t\tvar firstLabelWidth = computeTextSize(me.ctx, firstTick, tickFont.font);\n\n\t\t\t\t\tvar lastTick = me.ticks[me.ticks.length - 1];\n\t\t\t\t\tvar lastLabelWidth = computeTextSize(me.ctx, lastTick, tickFont.font);\n\n\t\t\t\t\t// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated\n\t\t\t\t\t// by the font height\n\t\t\t\t\tif (me.labelRotation !== 0) {\n\t\t\t\t\t\tme.paddingLeft = opts.position === 'bottom'? (cosRotation * firstLabelWidth) + 3: (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges\n\t\t\t\t\t\tme.paddingRight = opts.position === 'bottom'? (cosRotation * lineSpace) + 3: (cosRotation * lastLabelWidth) + 3;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tme.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges\n\t\t\t\t\t\tme.paddingRight = lastLabelWidth / 2 + 3;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first\n\t\t\t\t\t// Account for padding\n\n\t\t\t\t\tif (tickOpts.mirror) {\n\t\t\t\t\t\tlargestTextWidth = 0;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlargestTextWidth += me.options.ticks.padding;\n\t\t\t\t\t}\n\t\t\t\t\tminSize.width += largestTextWidth;\n\t\t\t\t\tme.paddingTop = tickFont.size / 2;\n\t\t\t\t\tme.paddingBottom = tickFont.size / 2;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tme.handleMargins();\n\n\t\t\tme.width = minSize.width;\n\t\t\tme.height = minSize.height;\n\t\t},\n\n\t\t/**\n\t\t * Handle margins and padding interactions\n\t\t * @private\n\t\t */\n\t\thandleMargins: function() {\n\t\t\tvar me = this;\n\t\t\tif (me.margins) {\n\t\t\t\tme.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);\n\t\t\t\tme.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);\n\t\t\t\tme.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);\n\t\t\t\tme.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);\n\t\t\t}\n\t\t},\n\n\t\tafterFit: function() {\n\t\t\thelpers.callCallback(this.options.afterFit, [this]);\n\t\t},\n\n\t\t// Shared Methods\n\t\tisHorizontal: function() {\n\t\t\treturn this.options.position === 'top' || this.options.position === 'bottom';\n\t\t},\n\t\tisFullWidth: function() {\n\t\t\treturn (this.options.fullWidth);\n\t\t},\n\n\t\t// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not\n\t\tgetRightValue: function(rawValue) {\n\t\t\t// Null and undefined values first\n\t\t\tif (rawValue === null || typeof(rawValue) === 'undefined') {\n\t\t\t\treturn NaN;\n\t\t\t}\n\t\t\t// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values\n\t\t\tif (typeof(rawValue) === 'number' && !isFinite(rawValue)) {\n\t\t\t\treturn NaN;\n\t\t\t}\n\t\t\t// If it is in fact an object, dive in one more level\n\t\t\tif (typeof(rawValue) === 'object') {\n\t\t\t\tif ((rawValue instanceof Date) || (rawValue.isValid)) {\n\t\t\t\t\treturn rawValue;\n\t\t\t\t}\n\t\t\t\treturn this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);\n\t\t\t}\n\n\t\t\t// Value is good, return it\n\t\t\treturn rawValue;\n\t\t},\n\n\t\t// Used to get the value to display in the tooltip for the data at the given index\n\t\t// function getLabelForIndex(index, datasetIndex)\n\t\tgetLabelForIndex: helpers.noop,\n\n\t\t// Used to get data value locations. Value can either be an index or a numerical value\n\t\tgetPixelForValue: helpers.noop,\n\n\t\t// Used to get the data value from a given pixel. This is the inverse of getPixelForValue\n\t\tgetValueForPixel: helpers.noop,\n\n\t\t// Used for tick location, should\n\t\tgetPixelForTick: function(index, includeOffset) {\n\t\t\tvar me = this;\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tvar innerWidth = me.width - (me.paddingLeft + me.paddingRight);\n\t\t\t\tvar tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);\n\t\t\t\tvar pixel = (tickWidth * index) + me.paddingLeft;\n\n\t\t\t\tif (includeOffset) {\n\t\t\t\t\tpixel += tickWidth / 2;\n\t\t\t\t}\n\n\t\t\t\tvar finalVal = me.left + Math.round(pixel);\n\t\t\t\tfinalVal += me.isFullWidth() ? me.margins.left : 0;\n\t\t\t\treturn finalVal;\n\t\t\t}\n\t\t\tvar innerHeight = me.height - (me.paddingTop + me.paddingBottom);\n\t\t\treturn me.top + (index * (innerHeight / (me.ticks.length - 1)));\n\t\t},\n\n\t\t// Utility for getting the pixel location of a percentage of scale\n\t\tgetPixelForDecimal: function(decimal /* , includeOffset*/) {\n\t\t\tvar me = this;\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tvar innerWidth = me.width - (me.paddingLeft + me.paddingRight);\n\t\t\t\tvar valueOffset = (innerWidth * decimal) + me.paddingLeft;\n\n\t\t\t\tvar finalVal = me.left + Math.round(valueOffset);\n\t\t\t\tfinalVal += me.isFullWidth() ? me.margins.left : 0;\n\t\t\t\treturn finalVal;\n\t\t\t}\n\t\t\treturn me.top + (decimal * me.height);\n\t\t},\n\n\t\tgetBasePixel: function() {\n\t\t\treturn this.getPixelForValue(this.getBaseValue());\n\t\t},\n\n\t\tgetBaseValue: function() {\n\t\t\tvar me = this;\n\t\t\tvar min = me.min;\n\t\t\tvar max = me.max;\n\n\t\t\treturn me.beginAtZero ? 0:\n\t\t\t\tmin < 0 && max < 0? max :\n\t\t\t\tmin > 0 && max > 0? min :\n\t\t\t\t0;\n\t\t},\n\n\t\t// Actually draw the scale on the canvas\n\t\t// @param {rectangle} chartArea : the area of the chart to draw full grid lines on\n\t\tdraw: function(chartArea) {\n\t\t\tvar me = this;\n\t\t\tvar options = me.options;\n\t\t\tif (!options.display) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar context = me.ctx;\n\t\t\tvar globalDefaults = Chart.defaults.global;\n\t\t\tvar optionTicks = options.ticks;\n\t\t\tvar gridLines = options.gridLines;\n\t\t\tvar scaleLabel = options.scaleLabel;\n\n\t\t\tvar isRotated = me.labelRotation !== 0;\n\t\t\tvar skipRatio;\n\t\t\tvar useAutoskipper = optionTicks.autoSkip;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\n\t\t\t// figure out the maximum number of gridlines to show\n\t\t\tvar maxTicks;\n\t\t\tif (optionTicks.maxTicksLimit) {\n\t\t\t\tmaxTicks = optionTicks.maxTicksLimit;\n\t\t\t}\n\n\t\t\tvar tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);\n\t\t\tvar tickFont = parseFontOptions(optionTicks);\n\n\t\t\tvar tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;\n\t\t\tvar borderDash = helpers.getValueOrDefault(gridLines.borderDash, globalDefaults.borderDash);\n\t\t\tvar borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);\n\n\t\t\tvar scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);\n\t\t\tvar scaleLabelFont = parseFontOptions(scaleLabel);\n\n\t\t\tvar labelRotationRadians = helpers.toRadians(me.labelRotation);\n\t\t\tvar cosRotation = Math.cos(labelRotationRadians);\n\t\t\tvar longestRotatedLabel = me.longestLabelWidth * cosRotation;\n\n\t\t\t// Make sure we draw text in the correct color and font\n\t\t\tcontext.fillStyle = tickFontColor;\n\n\t\t\tvar itemsToDraw = [];\n\n\t\t\tif (isHorizontal) {\n\t\t\t\tskipRatio = false;\n\n\t\t\t\t// Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation\n\t\t\t\t// See #2584\n\t\t\t\tif (isRotated) {\n\t\t\t\t\tlongestRotatedLabel /= 2;\n\t\t\t\t}\n\n\t\t\t\tif ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) {\n\t\t\t\t\tskipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight)));\n\t\t\t\t}\n\n\t\t\t\t// if they defined a max number of optionTicks,\n\t\t\t\t// increase skipRatio until that number is met\n\t\t\t\tif (maxTicks && me.ticks.length > maxTicks) {\n\t\t\t\t\twhile (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) {\n\t\t\t\t\t\tif (!skipRatio) {\n\t\t\t\t\t\t\tskipRatio = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskipRatio += 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!useAutoskipper) {\n\t\t\t\t\tskipRatio = false;\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tvar xTickStart = options.position === 'right' ? me.left : me.right - tl;\n\t\t\tvar xTickEnd = options.position === 'right' ? me.left + tl : me.right;\n\t\t\tvar yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;\n\t\t\tvar yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;\n\n\t\t\thelpers.each(me.ticks, function(label, index) {\n\t\t\t\t// If the callback returned a null or undefined value, do not draw this line\n\t\t\t\tif (label === undefined || label === null) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar isLastTick = me.ticks.length === index + 1;\n\n\t\t\t\t// Since we always show the last tick,we need may need to hide the last shown one before\n\t\t\t\tvar shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length);\n\t\t\t\tif (shouldSkip && !isLastTick || (label === undefined || label === null)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar lineWidth, lineColor;\n\t\t\t\tif (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) {\n\t\t\t\t\t// Draw the first index specially\n\t\t\t\t\tlineWidth = gridLines.zeroLineWidth;\n\t\t\t\t\tlineColor = gridLines.zeroLineColor;\n\t\t\t\t} else {\n\t\t\t\t\tlineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index);\n\t\t\t\t\tlineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index);\n\t\t\t\t}\n\n\t\t\t\t// Common properties\n\t\t\t\tvar tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;\n\t\t\t\tvar textAlign = 'middle';\n\t\t\t\tvar textBaseline = 'middle';\n\n\t\t\t\tif (isHorizontal) {\n\n\t\t\t\t\tif (options.position === 'bottom') {\n\t\t\t\t\t\t// bottom\n\t\t\t\t\t\ttextBaseline = !isRotated? 'top':'middle';\n\t\t\t\t\t\ttextAlign = !isRotated? 'center': 'right';\n\t\t\t\t\t\tlabelY = me.top + tl;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// top\n\t\t\t\t\t\ttextBaseline = !isRotated? 'bottom':'middle';\n\t\t\t\t\t\ttextAlign = !isRotated? 'center': 'left';\n\t\t\t\t\t\tlabelY = me.bottom - tl;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines\n\t\t\t\t\tlabelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)\n\n\t\t\t\t\ttx1 = tx2 = x1 = x2 = xLineValue;\n\t\t\t\t\tty1 = yTickStart;\n\t\t\t\t\tty2 = yTickEnd;\n\t\t\t\t\ty1 = chartArea.top;\n\t\t\t\t\ty2 = chartArea.bottom;\n\t\t\t\t} else {\n\t\t\t\t\tvar isLeft = options.position === 'left';\n\t\t\t\t\tvar tickPadding = optionTicks.padding;\n\t\t\t\t\tvar labelXOffset;\n\n\t\t\t\t\tif (optionTicks.mirror) {\n\t\t\t\t\t\ttextAlign = isLeft ? 'left' : 'right';\n\t\t\t\t\t\tlabelXOffset = tickPadding;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttextAlign = isLeft ? 'right' : 'left';\n\t\t\t\t\t\tlabelXOffset = tl + tickPadding;\n\t\t\t\t\t}\n\n\t\t\t\t\tlabelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;\n\n\t\t\t\t\tvar yLineValue = me.getPixelForTick(index); // xvalues for grid lines\n\t\t\t\t\tyLineValue += helpers.aliasPixel(lineWidth);\n\t\t\t\t\tlabelY = me.getPixelForTick(index, gridLines.offsetGridLines);\n\n\t\t\t\t\ttx1 = xTickStart;\n\t\t\t\t\ttx2 = xTickEnd;\n\t\t\t\t\tx1 = chartArea.left;\n\t\t\t\t\tx2 = chartArea.right;\n\t\t\t\t\tty1 = ty2 = y1 = y2 = yLineValue;\n\t\t\t\t}\n\n\t\t\t\titemsToDraw.push({\n\t\t\t\t\ttx1: tx1,\n\t\t\t\t\tty1: ty1,\n\t\t\t\t\ttx2: tx2,\n\t\t\t\t\tty2: ty2,\n\t\t\t\t\tx1: x1,\n\t\t\t\t\ty1: y1,\n\t\t\t\t\tx2: x2,\n\t\t\t\t\ty2: y2,\n\t\t\t\t\tlabelX: labelX,\n\t\t\t\t\tlabelY: labelY,\n\t\t\t\t\tglWidth: lineWidth,\n\t\t\t\t\tglColor: lineColor,\n\t\t\t\t\tglBorderDash: borderDash,\n\t\t\t\t\tglBorderDashOffset: borderDashOffset,\n\t\t\t\t\trotation: -1 * labelRotationRadians,\n\t\t\t\t\tlabel: label,\n\t\t\t\t\ttextBaseline: textBaseline,\n\t\t\t\t\ttextAlign: textAlign\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// Draw all of the tick labels, tick marks, and grid lines at the correct places\n\t\t\thelpers.each(itemsToDraw, function(itemToDraw) {\n\t\t\t\tif (gridLines.display) {\n\t\t\t\t\tcontext.save();\n\t\t\t\t\tcontext.lineWidth = itemToDraw.glWidth;\n\t\t\t\t\tcontext.strokeStyle = itemToDraw.glColor;\n\t\t\t\t\tif (context.setLineDash) {\n\t\t\t\t\t\tcontext.setLineDash(itemToDraw.glBorderDash);\n\t\t\t\t\t\tcontext.lineDashOffset = itemToDraw.glBorderDashOffset;\n\t\t\t\t\t}\n\n\t\t\t\t\tcontext.beginPath();\n\n\t\t\t\t\tif (gridLines.drawTicks) {\n\t\t\t\t\t\tcontext.moveTo(itemToDraw.tx1, itemToDraw.ty1);\n\t\t\t\t\t\tcontext.lineTo(itemToDraw.tx2, itemToDraw.ty2);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (gridLines.drawOnChartArea) {\n\t\t\t\t\t\tcontext.moveTo(itemToDraw.x1, itemToDraw.y1);\n\t\t\t\t\t\tcontext.lineTo(itemToDraw.x2, itemToDraw.y2);\n\t\t\t\t\t}\n\n\t\t\t\t\tcontext.stroke();\n\t\t\t\t\tcontext.restore();\n\t\t\t\t}\n\n\t\t\t\tif (optionTicks.display) {\n\t\t\t\t\tcontext.save();\n\t\t\t\t\tcontext.translate(itemToDraw.labelX, itemToDraw.labelY);\n\t\t\t\t\tcontext.rotate(itemToDraw.rotation);\n\t\t\t\t\tcontext.font = tickFont.font;\n\t\t\t\t\tcontext.textBaseline = itemToDraw.textBaseline;\n\t\t\t\t\tcontext.textAlign = itemToDraw.textAlign;\n\n\t\t\t\t\tvar label = itemToDraw.label;\n\t\t\t\t\tif (helpers.isArray(label)) {\n\t\t\t\t\t\tfor (var i = 0, y = 0; i < label.length; ++i) {\n\t\t\t\t\t\t\t// We just make sure the multiline element is a string here..\n\t\t\t\t\t\t\tcontext.fillText('' + label[i], 0, y);\n\t\t\t\t\t\t\t// apply same lineSpacing as calculated @ L#320\n\t\t\t\t\t\t\ty += (tickFont.size * 1.5);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontext.fillText(label, 0, 0);\n\t\t\t\t\t}\n\t\t\t\t\tcontext.restore();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (scaleLabel.display) {\n\t\t\t\t// Draw the scale label\n\t\t\t\tvar scaleLabelX;\n\t\t\t\tvar scaleLabelY;\n\t\t\t\tvar rotation = 0;\n\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\tscaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width\n\t\t\t\t\tscaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFont.size / 2) : me.top + (scaleLabelFont.size / 2);\n\t\t\t\t} else {\n\t\t\t\t\tvar isLeft = options.position === 'left';\n\t\t\t\t\tscaleLabelX = isLeft ? me.left + (scaleLabelFont.size / 2) : me.right - (scaleLabelFont.size / 2);\n\t\t\t\t\tscaleLabelY = me.top + ((me.bottom - me.top) / 2);\n\t\t\t\t\trotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;\n\t\t\t\t}\n\n\t\t\t\tcontext.save();\n\t\t\t\tcontext.translate(scaleLabelX, scaleLabelY);\n\t\t\t\tcontext.rotate(rotation);\n\t\t\t\tcontext.textAlign = 'center';\n\t\t\t\tcontext.textBaseline = 'middle';\n\t\t\t\tcontext.fillStyle = scaleLabelFontColor; // render in correct colour\n\t\t\t\tcontext.font = scaleLabelFont.font;\n\t\t\t\tcontext.fillText(scaleLabel.labelString, 0, 0);\n\t\t\t\tcontext.restore();\n\t\t\t}\n\n\t\t\tif (gridLines.drawBorder) {\n\t\t\t\t// Draw the line at the edge of the axis\n\t\t\t\tcontext.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0);\n\t\t\t\tcontext.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0);\n\t\t\t\tvar x1 = me.left,\n\t\t\t\t\tx2 = me.right,\n\t\t\t\t\ty1 = me.top,\n\t\t\t\t\ty2 = me.bottom;\n\n\t\t\t\tvar aliasPixel = helpers.aliasPixel(context.lineWidth);\n\t\t\t\tif (isHorizontal) {\n\t\t\t\t\ty1 = y2 = options.position === 'top' ? me.bottom : me.top;\n\t\t\t\t\ty1 += aliasPixel;\n\t\t\t\t\ty2 += aliasPixel;\n\t\t\t\t} else {\n\t\t\t\t\tx1 = x2 = options.position === 'left' ? me.right : me.left;\n\t\t\t\t\tx1 += aliasPixel;\n\t\t\t\t\tx2 += aliasPixel;\n\t\t\t\t}\n\n\t\t\t\tcontext.beginPath();\n\t\t\t\tcontext.moveTo(x1, y1);\n\t\t\t\tcontext.lineTo(x2, y2);\n\t\t\t\tcontext.stroke();\n\t\t\t}\n\t\t}\n\t});\n};\n\n},{}],33:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.scaleService = {\n\t\t// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then\n\t\t// use the new chart options to grab the correct scale\n\t\tconstructors: {},\n\t\t// Use a registration function so that we can move to an ES6 map when we no longer need to support\n\t\t// old browsers\n\n\t\t// Scale config defaults\n\t\tdefaults: {},\n\t\tregisterScaleType: function(type, scaleConstructor, defaults) {\n\t\t\tthis.constructors[type] = scaleConstructor;\n\t\t\tthis.defaults[type] = helpers.clone(defaults);\n\t\t},\n\t\tgetScaleConstructor: function(type) {\n\t\t\treturn this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;\n\t\t},\n\t\tgetScaleDefaults: function(type) {\n\t\t\t// Return the scale defaults merged with the global settings so that we always use the latest ones\n\t\t\treturn this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};\n\t\t},\n\t\tupdateScaleDefaults: function(type, additions) {\n\t\t\tvar defaults = this.defaults;\n\t\t\tif (defaults.hasOwnProperty(type)) {\n\t\t\t\tdefaults[type] = helpers.extend(defaults[type], additions);\n\t\t\t}\n\t\t},\n\t\taddScalesToLayout: function(chartInstance) {\n\t\t\t// Adds each scale to the chart.boxes array to be sized accordingly\n\t\t\thelpers.each(chartInstance.scales, function(scale) {\n\t\t\t\tChart.layoutService.addBox(chartInstance, scale);\n\t\t\t});\n\t\t}\n\t};\n};\n\n},{}],34:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\t/**\n\t * Namespace to hold static tick generation functions\n\t * @namespace Chart.Ticks\n\t */\n\tChart.Ticks = {\n\t\t/**\n\t\t * Namespace to hold generators for different types of ticks\n\t\t * @namespace Chart.Ticks.generators\n\t\t */\n\t\tgenerators: {\n\t\t\t/**\n\t\t\t * Interface for the options provided to the numeric tick generator\n\t\t\t * @interface INumericTickGenerationOptions\n\t\t\t */\n\t\t\t/**\n\t\t\t * The maximum number of ticks to display\n\t\t\t * @name INumericTickGenerationOptions#maxTicks\n\t\t\t * @type Number\n\t\t\t */\n\t\t\t/**\n\t\t\t * The distance between each tick.\n\t\t\t * @name INumericTickGenerationOptions#stepSize\n\t\t\t * @type Number\n\t\t\t * @optional\n\t\t\t */\n\t\t\t/**\n\t\t\t * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum\n\t\t\t * @name INumericTickGenerationOptions#min\n\t\t\t * @type Number\n\t\t\t * @optional\n\t\t\t */\n\t\t\t/**\n\t\t\t * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum\n\t\t\t * @name INumericTickGenerationOptions#max\n\t\t\t * @type Number\n\t\t\t * @optional\n\t\t\t */\n\n\t\t\t/**\n\t\t\t * Generate a set of linear ticks\n\t\t\t * @method Chart.Ticks.generators.linear\n\t\t\t * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks\n\t\t\t * @param dataRange {IRange} the range of the data\n\t\t\t * @returns {Array} array of tick values\n\t\t\t */\n\t\t\tlinear: function(generationOptions, dataRange) {\n\t\t\t\tvar ticks = [];\n\t\t\t\t// To get a \"nice\" value for the tick spacing, we will use the appropriately named\n\t\t\t\t// \"nice number\" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks\n\t\t\t\t// for details.\n\n\t\t\t\tvar spacing;\n\t\t\t\tif (generationOptions.stepSize && generationOptions.stepSize > 0) {\n\t\t\t\t\tspacing = generationOptions.stepSize;\n\t\t\t\t} else {\n\t\t\t\t\tvar niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);\n\t\t\t\t\tspacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);\n\t\t\t\t}\n\t\t\t\tvar niceMin = Math.floor(dataRange.min / spacing) * spacing;\n\t\t\t\tvar niceMax = Math.ceil(dataRange.max / spacing) * spacing;\n\n\t\t\t\t// If min, max and stepSize is set and they make an evenly spaced scale use it.\n\t\t\t\tif (generationOptions.min && generationOptions.max && generationOptions.stepSize) {\n\t\t\t\t\t// If very close to our whole number, use it.\n\t\t\t\t\tif (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {\n\t\t\t\t\t\tniceMin = generationOptions.min;\n\t\t\t\t\t\tniceMax = generationOptions.max;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar numSpaces = (niceMax - niceMin) / spacing;\n\t\t\t\t// If very close to our rounded value, use it.\n\t\t\t\tif (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {\n\t\t\t\t\tnumSpaces = Math.round(numSpaces);\n\t\t\t\t} else {\n\t\t\t\t\tnumSpaces = Math.ceil(numSpaces);\n\t\t\t\t}\n\n\t\t\t\t// Put the values into the ticks array\n\t\t\t\tticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);\n\t\t\t\tfor (var j = 1; j < numSpaces; ++j) {\n\t\t\t\t\tticks.push(niceMin + (j * spacing));\n\t\t\t\t}\n\t\t\t\tticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);\n\n\t\t\t\treturn ticks;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Generate a set of logarithmic ticks\n\t\t\t * @method Chart.Ticks.generators.logarithmic\n\t\t\t * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks\n\t\t\t * @param dataRange {IRange} the range of the data\n\t\t\t * @returns {Array} array of tick values\n\t\t\t */\n\t\t\tlogarithmic: function(generationOptions, dataRange) {\n\t\t\t\tvar ticks = [];\n\t\t\t\tvar getValueOrDefault = helpers.getValueOrDefault;\n\n\t\t\t\t// Figure out what the max number of ticks we can support it is based on the size of\n\t\t\t\t// the axis area. For now, we say that the minimum tick spacing in pixels must be 50\n\t\t\t\t// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on\n\t\t\t\t// the graph\n\t\t\t\tvar tickVal = getValueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));\n\n\t\t\t\tvar endExp = Math.floor(helpers.log10(dataRange.max));\n\t\t\t\tvar endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));\n\t\t\t\tvar exp;\n\t\t\t\tvar significand;\n\n\t\t\t\tif (tickVal === 0) {\n\t\t\t\t\texp = Math.floor(helpers.log10(dataRange.minNotZero));\n\t\t\t\t\tsignificand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));\n\n\t\t\t\t\tticks.push(tickVal);\n\t\t\t\t\ttickVal = significand * Math.pow(10, exp);\n\t\t\t\t} else {\n\t\t\t\t\texp = Math.floor(helpers.log10(tickVal));\n\t\t\t\t\tsignificand = Math.floor(tickVal / Math.pow(10, exp));\n\t\t\t\t}\n\n\t\t\t\tdo {\n\t\t\t\t\tticks.push(tickVal);\n\n\t\t\t\t\t++significand;\n\t\t\t\t\tif (significand === 10) {\n\t\t\t\t\t\tsignificand = 1;\n\t\t\t\t\t\t++exp;\n\t\t\t\t\t}\n\n\t\t\t\t\ttickVal = significand * Math.pow(10, exp);\n\t\t\t\t} while (exp < endExp || (exp === endExp && significand < endSignificand));\n\n\t\t\t\tvar lastTick = getValueOrDefault(generationOptions.max, tickVal);\n\t\t\t\tticks.push(lastTick);\n\n\t\t\t\treturn ticks;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Namespace to hold formatters for different types of ticks\n\t\t * @namespace Chart.Ticks.formatters\n\t\t */\n\t\tformatters: {\n\t\t\t/**\n\t\t\t * Formatter for value labels\n\t\t\t * @method Chart.Ticks.formatters.values\n\t\t\t * @param value the value to display\n\t\t\t * @return {String|Array} the label to display\n\t\t\t */\n\t\t\tvalues: function(value) {\n\t\t\t\treturn helpers.isArray(value) ? value : '' + value;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Formatter for linear numeric ticks\n\t\t\t * @method Chart.Ticks.formatters.linear\n\t\t\t * @param tickValue {Number} the value to be formatted\n\t\t\t * @param index {Number} the position of the tickValue parameter in the ticks array\n\t\t\t * @param ticks {Array} the list of ticks being converted\n\t\t\t * @return {String} string representation of the tickValue parameter\n\t\t\t */\n\t\t\tlinear: function(tickValue, index, ticks) {\n\t\t\t\t// If we have lots of ticks, don't use the ones\n\t\t\t\tvar delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];\n\n\t\t\t\t// If we have a number like 2.5 as the delta, figure out how many decimal places we need\n\t\t\t\tif (Math.abs(delta) > 1) {\n\t\t\t\t\tif (tickValue !== Math.floor(tickValue)) {\n\t\t\t\t\t\t// not an integer\n\t\t\t\t\t\tdelta = tickValue - Math.floor(tickValue);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar logDelta = helpers.log10(Math.abs(delta));\n\t\t\t\tvar tickString = '';\n\n\t\t\t\tif (tickValue !== 0) {\n\t\t\t\t\tvar numDecimal = -1 * Math.floor(logDelta);\n\t\t\t\t\tnumDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places\n\t\t\t\t\ttickString = tickValue.toFixed(numDecimal);\n\t\t\t\t} else {\n\t\t\t\t\ttickString = '0'; // never show decimal places for 0\n\t\t\t\t}\n\n\t\t\t\treturn tickString;\n\t\t\t},\n\n\t\t\tlogarithmic: function(tickValue, index, ticks) {\n\t\t\t\tvar remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));\n\n\t\t\t\tif (tickValue === 0) {\n\t\t\t\t\treturn '0';\n\t\t\t\t} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {\n\t\t\t\t\treturn tickValue.toExponential();\n\t\t\t\t}\n\t\t\t\treturn '';\n\t\t\t}\n\t\t}\n\t};\n};\n\n},{}],35:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tChart.defaults.global.title = {\n\t\tdisplay: false,\n\t\tposition: 'top',\n\t\tfullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)\n\n\t\tfontStyle: 'bold',\n\t\tpadding: 10,\n\n\t\t// actual title\n\t\ttext: ''\n\t};\n\n\tvar noop = helpers.noop;\n\tChart.Title = Chart.Element.extend({\n\n\t\tinitialize: function(config) {\n\t\t\tvar me = this;\n\t\t\thelpers.extend(me, config);\n\n\t\t\t// Contains hit boxes for each dataset (in dataset order)\n\t\t\tme.legendHitBoxes = [];\n\t\t},\n\n\t\t// These methods are ordered by lifecycle. Utilities then follow.\n\n\t\tbeforeUpdate: noop,\n\t\tupdate: function(maxWidth, maxHeight, margins) {\n\t\t\tvar me = this;\n\n\t\t\t// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)\n\t\t\tme.beforeUpdate();\n\n\t\t\t// Absorb the master measurements\n\t\t\tme.maxWidth = maxWidth;\n\t\t\tme.maxHeight = maxHeight;\n\t\t\tme.margins = margins;\n\n\t\t\t// Dimensions\n\t\t\tme.beforeSetDimensions();\n\t\t\tme.setDimensions();\n\t\t\tme.afterSetDimensions();\n\t\t\t// Labels\n\t\t\tme.beforeBuildLabels();\n\t\t\tme.buildLabels();\n\t\t\tme.afterBuildLabels();\n\n\t\t\t// Fit\n\t\t\tme.beforeFit();\n\t\t\tme.fit();\n\t\t\tme.afterFit();\n\t\t\t//\n\t\t\tme.afterUpdate();\n\n\t\t\treturn me.minSize;\n\n\t\t},\n\t\tafterUpdate: noop,\n\n\t\t//\n\n\t\tbeforeSetDimensions: noop,\n\t\tsetDimensions: function() {\n\t\t\tvar me = this;\n\t\t\t// Set the unconstrained dimension before label rotation\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\t// Reset position before calculating rotation\n\t\t\t\tme.width = me.maxWidth;\n\t\t\t\tme.left = 0;\n\t\t\t\tme.right = me.width;\n\t\t\t} else {\n\t\t\t\tme.height = me.maxHeight;\n\n\t\t\t\t// Reset position before calculating rotation\n\t\t\t\tme.top = 0;\n\t\t\t\tme.bottom = me.height;\n\t\t\t}\n\n\t\t\t// Reset padding\n\t\t\tme.paddingLeft = 0;\n\t\t\tme.paddingTop = 0;\n\t\t\tme.paddingRight = 0;\n\t\t\tme.paddingBottom = 0;\n\n\t\t\t// Reset minSize\n\t\t\tme.minSize = {\n\t\t\t\twidth: 0,\n\t\t\t\theight: 0\n\t\t\t};\n\t\t},\n\t\tafterSetDimensions: noop,\n\n\t\t//\n\n\t\tbeforeBuildLabels: noop,\n\t\tbuildLabels: noop,\n\t\tafterBuildLabels: noop,\n\n\t\t//\n\n\t\tbeforeFit: noop,\n\t\tfit: function() {\n\t\t\tvar me = this,\n\t\t\t\tvalueOrDefault = helpers.getValueOrDefault,\n\t\t\t\topts = me.options,\n\t\t\t\tglobalDefaults = Chart.defaults.global,\n\t\t\t\tdisplay = opts.display,\n\t\t\t\tfontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),\n\t\t\t\tminSize = me.minSize;\n\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tminSize.width = me.maxWidth; // fill all the width\n\t\t\t\tminSize.height = display ? fontSize + (opts.padding * 2) : 0;\n\t\t\t} else {\n\t\t\t\tminSize.width = display ? fontSize + (opts.padding * 2) : 0;\n\t\t\t\tminSize.height = me.maxHeight; // fill all the height\n\t\t\t}\n\n\t\t\tme.width = minSize.width;\n\t\t\tme.height = minSize.height;\n\n\t\t},\n\t\tafterFit: noop,\n\n\t\t// Shared Methods\n\t\tisHorizontal: function() {\n\t\t\tvar pos = this.options.position;\n\t\t\treturn pos === 'top' || pos === 'bottom';\n\t\t},\n\n\t\t// Actually draw the title block on the canvas\n\t\tdraw: function() {\n\t\t\tvar me = this,\n\t\t\t\tctx = me.ctx,\n\t\t\t\tvalueOrDefault = helpers.getValueOrDefault,\n\t\t\t\topts = me.options,\n\t\t\t\tglobalDefaults = Chart.defaults.global;\n\n\t\t\tif (opts.display) {\n\t\t\t\tvar fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),\n\t\t\t\t\tfontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle),\n\t\t\t\t\tfontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily),\n\t\t\t\t\ttitleFont = helpers.fontString(fontSize, fontStyle, fontFamily),\n\t\t\t\t\trotation = 0,\n\t\t\t\t\ttitleX,\n\t\t\t\t\ttitleY,\n\t\t\t\t\ttop = me.top,\n\t\t\t\t\tleft = me.left,\n\t\t\t\t\tbottom = me.bottom,\n\t\t\t\t\tright = me.right,\n\t\t\t\t\tmaxWidth;\n\n\t\t\t\tctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour\n\t\t\t\tctx.font = titleFont;\n\n\t\t\t\t// Horizontal\n\t\t\t\tif (me.isHorizontal()) {\n\t\t\t\t\ttitleX = left + ((right - left) / 2); // midpoint of the width\n\t\t\t\t\ttitleY = top + ((bottom - top) / 2); // midpoint of the height\n\t\t\t\t\tmaxWidth = right - left;\n\t\t\t\t} else {\n\t\t\t\t\ttitleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2);\n\t\t\t\t\ttitleY = top + ((bottom - top) / 2);\n\t\t\t\t\tmaxWidth = bottom - top;\n\t\t\t\t\trotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);\n\t\t\t\t}\n\n\t\t\t\tctx.save();\n\t\t\t\tctx.translate(titleX, titleY);\n\t\t\t\tctx.rotate(rotation);\n\t\t\t\tctx.textAlign = 'center';\n\t\t\t\tctx.textBaseline = 'middle';\n\t\t\t\tctx.fillText(opts.text, 0, 0, maxWidth);\n\t\t\t\tctx.restore();\n\t\t\t}\n\t\t}\n\t});\n\n\tfunction createNewTitleBlockAndAttach(chartInstance, titleOpts) {\n\t\tvar title = new Chart.Title({\n\t\t\tctx: chartInstance.chart.ctx,\n\t\t\toptions: titleOpts,\n\t\t\tchart: chartInstance\n\t\t});\n\t\tchartInstance.titleBlock = title;\n\t\tChart.layoutService.addBox(chartInstance, title);\n\t}\n\n\t// Register the title plugin\n\tChart.plugins.register({\n\t\tbeforeInit: function(chartInstance) {\n\t\t\tvar titleOpts = chartInstance.options.title;\n\n\t\t\tif (titleOpts) {\n\t\t\t\tcreateNewTitleBlockAndAttach(chartInstance, titleOpts);\n\t\t\t}\n\t\t},\n\t\tbeforeUpdate: function(chartInstance) {\n\t\t\tvar titleOpts = chartInstance.options.title;\n\n\t\t\tif (titleOpts) {\n\t\t\t\ttitleOpts = helpers.configMerge(Chart.defaults.global.title, titleOpts);\n\n\t\t\t\tif (chartInstance.titleBlock) {\n\t\t\t\t\tchartInstance.titleBlock.options = titleOpts;\n\t\t\t\t} else {\n\t\t\t\t\tcreateNewTitleBlockAndAttach(chartInstance, titleOpts);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tChart.layoutService.removeBox(chartInstance, chartInstance.titleBlock);\n\t\t\t\tdelete chartInstance.titleBlock;\n\t\t\t}\n\t\t}\n\t});\n};\n\n},{}],36:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\t/**\n \t * Helper method to merge the opacity into a color\n \t */\n\tfunction mergeOpacity(colorString, opacity) {\n\t\tvar color = helpers.color(colorString);\n\t\treturn color.alpha(opacity * color.alpha()).rgbaString();\n\t}\n\n\tChart.defaults.global.tooltips = {\n\t\tenabled: true,\n\t\tcustom: null,\n\t\tmode: 'nearest',\n\t\tposition: 'average',\n\t\tintersect: true,\n\t\tbackgroundColor: 'rgba(0,0,0,0.8)',\n\t\ttitleFontStyle: 'bold',\n\t\ttitleSpacing: 2,\n\t\ttitleMarginBottom: 6,\n\t\ttitleFontColor: '#fff',\n\t\ttitleAlign: 'left',\n\t\tbodySpacing: 2,\n\t\tbodyFontColor: '#fff',\n\t\tbodyAlign: 'left',\n\t\tfooterFontStyle: 'bold',\n\t\tfooterSpacing: 2,\n\t\tfooterMarginTop: 6,\n\t\tfooterFontColor: '#fff',\n\t\tfooterAlign: 'left',\n\t\tyPadding: 6,\n\t\txPadding: 6,\n\t\tcaretSize: 5,\n\t\tcornerRadius: 6,\n\t\tmultiKeyBackground: '#fff',\n\t\tdisplayColors: true,\n\t\tcallbacks: {\n\t\t\t// Args are: (tooltipItems, data)\n\t\t\tbeforeTitle: helpers.noop,\n\t\t\ttitle: function(tooltipItems, data) {\n\t\t\t\t// Pick first xLabel for now\n\t\t\t\tvar title = '';\n\t\t\t\tvar labels = data.labels;\n\t\t\t\tvar labelCount = labels ? labels.length : 0;\n\n\t\t\t\tif (tooltipItems.length > 0) {\n\t\t\t\t\tvar item = tooltipItems[0];\n\n\t\t\t\t\tif (item.xLabel) {\n\t\t\t\t\t\ttitle = item.xLabel;\n\t\t\t\t\t} else if (labelCount > 0 && item.index < labelCount) {\n\t\t\t\t\t\ttitle = labels[item.index];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn title;\n\t\t\t},\n\t\t\tafterTitle: helpers.noop,\n\n\t\t\t// Args are: (tooltipItems, data)\n\t\t\tbeforeBody: helpers.noop,\n\n\t\t\t// Args are: (tooltipItem, data)\n\t\t\tbeforeLabel: helpers.noop,\n\t\t\tlabel: function(tooltipItem, data) {\n\t\t\t\tvar datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';\n\t\t\t\treturn datasetLabel + ': ' + tooltipItem.yLabel;\n\t\t\t},\n\t\t\tlabelColor: function(tooltipItem, chartInstance) {\n\t\t\t\tvar meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex);\n\t\t\t\tvar activeElement = meta.data[tooltipItem.index];\n\t\t\t\tvar view = activeElement._view;\n\t\t\t\treturn {\n\t\t\t\t\tborderColor: view.borderColor,\n\t\t\t\t\tbackgroundColor: view.backgroundColor\n\t\t\t\t};\n\t\t\t},\n\t\t\tafterLabel: helpers.noop,\n\n\t\t\t// Args are: (tooltipItems, data)\n\t\t\tafterBody: helpers.noop,\n\n\t\t\t// Args are: (tooltipItems, data)\n\t\t\tbeforeFooter: helpers.noop,\n\t\t\tfooter: helpers.noop,\n\t\t\tafterFooter: helpers.noop\n\t\t}\n\t};\n\n\t// Helper to push or concat based on if the 2nd parameter is an array or not\n\tfunction pushOrConcat(base, toPush) {\n\t\tif (toPush) {\n\t\t\tif (helpers.isArray(toPush)) {\n\t\t\t\t// base = base.concat(toPush);\n\t\t\t\tArray.prototype.push.apply(base, toPush);\n\t\t\t} else {\n\t\t\t\tbase.push(toPush);\n\t\t\t}\n\t\t}\n\n\t\treturn base;\n\t}\n\n\t// Private helper to create a tooltip item model\n\t// @param element : the chart element (point, arc, bar) to create the tooltip item for\n\t// @return : new tooltip item\n\tfunction createTooltipItem(element) {\n\t\tvar xScale = element._xScale;\n\t\tvar yScale = element._yScale || element._scale; // handle radar || polarArea charts\n\t\tvar index = element._index,\n\t\t\tdatasetIndex = element._datasetIndex;\n\n\t\treturn {\n\t\t\txLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',\n\t\t\tyLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',\n\t\t\tindex: index,\n\t\t\tdatasetIndex: datasetIndex,\n\t\t\tx: element._model.x,\n\t\t\ty: element._model.y\n\t\t};\n\t}\n\n\t/**\n\t * Helper to get the reset model for the tooltip\n\t * @param tooltipOpts {Object} the tooltip options\n\t */\n\tfunction getBaseModel(tooltipOpts) {\n\t\tvar globalDefaults = Chart.defaults.global;\n\t\tvar getValueOrDefault = helpers.getValueOrDefault;\n\n\t\treturn {\n\t\t\t// Positioning\n\t\t\txPadding: tooltipOpts.xPadding,\n\t\t\tyPadding: tooltipOpts.yPadding,\n\t\t\txAlign: tooltipOpts.xAlign,\n\t\t\tyAlign: tooltipOpts.yAlign,\n\n\t\t\t// Body\n\t\t\tbodyFontColor: tooltipOpts.bodyFontColor,\n\t\t\t_bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),\n\t\t\t_bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),\n\t\t\t_bodyAlign: tooltipOpts.bodyAlign,\n\t\t\tbodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),\n\t\t\tbodySpacing: tooltipOpts.bodySpacing,\n\n\t\t\t// Title\n\t\t\ttitleFontColor: tooltipOpts.titleFontColor,\n\t\t\t_titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),\n\t\t\t_titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),\n\t\t\ttitleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),\n\t\t\t_titleAlign: tooltipOpts.titleAlign,\n\t\t\ttitleSpacing: tooltipOpts.titleSpacing,\n\t\t\ttitleMarginBottom: tooltipOpts.titleMarginBottom,\n\n\t\t\t// Footer\n\t\t\tfooterFontColor: tooltipOpts.footerFontColor,\n\t\t\t_footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),\n\t\t\t_footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),\n\t\t\tfooterFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),\n\t\t\t_footerAlign: tooltipOpts.footerAlign,\n\t\t\tfooterSpacing: tooltipOpts.footerSpacing,\n\t\t\tfooterMarginTop: tooltipOpts.footerMarginTop,\n\n\t\t\t// Appearance\n\t\t\tcaretSize: tooltipOpts.caretSize,\n\t\t\tcornerRadius: tooltipOpts.cornerRadius,\n\t\t\tbackgroundColor: tooltipOpts.backgroundColor,\n\t\t\topacity: 0,\n\t\t\tlegendColorBackground: tooltipOpts.multiKeyBackground,\n\t\t\tdisplayColors: tooltipOpts.displayColors\n\t\t};\n\t}\n\n\t/**\n\t * Get the size of the tooltip\n\t */\n\tfunction getTooltipSize(tooltip, model) {\n\t\tvar ctx = tooltip._chart.ctx;\n\n\t\tvar height = model.yPadding * 2; // Tooltip Padding\n\t\tvar width = 0;\n\n\t\t// Count of all lines in the body\n\t\tvar body = model.body;\n\t\tvar combinedBodyLength = body.reduce(function(count, bodyItem) {\n\t\t\treturn count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;\n\t\t}, 0);\n\t\tcombinedBodyLength += model.beforeBody.length + model.afterBody.length;\n\n\t\tvar titleLineCount = model.title.length;\n\t\tvar footerLineCount = model.footer.length;\n\t\tvar titleFontSize = model.titleFontSize,\n\t\t\tbodyFontSize = model.bodyFontSize,\n\t\t\tfooterFontSize = model.footerFontSize;\n\n\t\theight += titleLineCount * titleFontSize; // Title Lines\n\t\theight += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing\n\t\theight += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin\n\t\theight += combinedBodyLength * bodyFontSize; // Body Lines\n\t\theight += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing\n\t\theight += footerLineCount ? model.footerMarginTop : 0; // Footer Margin\n\t\theight += footerLineCount * (footerFontSize); // Footer Lines\n\t\theight += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing\n\n\t\t// Title width\n\t\tvar widthPadding = 0;\n\t\tvar maxLineWidth = function(line) {\n\t\t\twidth = Math.max(width, ctx.measureText(line).width + widthPadding);\n\t\t};\n\n\t\tctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);\n\t\thelpers.each(model.title, maxLineWidth);\n\n\t\t// Body width\n\t\tctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);\n\t\thelpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);\n\n\t\t// Body lines may include some extra width due to the color box\n\t\twidthPadding = model.displayColors ? (bodyFontSize + 2) : 0;\n\t\thelpers.each(body, function(bodyItem) {\n\t\t\thelpers.each(bodyItem.before, maxLineWidth);\n\t\t\thelpers.each(bodyItem.lines, maxLineWidth);\n\t\t\thelpers.each(bodyItem.after, maxLineWidth);\n\t\t});\n\n\t\t// Reset back to 0\n\t\twidthPadding = 0;\n\n\t\t// Footer width\n\t\tctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);\n\t\thelpers.each(model.footer, maxLineWidth);\n\n\t\t// Add padding\n\t\twidth += 2 * model.xPadding;\n\n\t\treturn {\n\t\t\twidth: width,\n\t\t\theight: height\n\t\t};\n\t}\n\n\t/**\n\t * Helper to get the alignment of a tooltip given the size\n\t */\n\tfunction determineAlignment(tooltip, size) {\n\t\tvar model = tooltip._model;\n\t\tvar chart = tooltip._chart;\n\t\tvar chartArea = tooltip._chartInstance.chartArea;\n\t\tvar xAlign = 'center';\n\t\tvar yAlign = 'center';\n\n\t\tif (model.y < size.height) {\n\t\t\tyAlign = 'top';\n\t\t} else if (model.y > (chart.height - size.height)) {\n\t\t\tyAlign = 'bottom';\n\t\t}\n\n\t\tvar lf, rf; // functions to determine left, right alignment\n\t\tvar olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart\n\t\tvar yf; // function to get the y alignment if the tooltip goes outside of the left or right edges\n\t\tvar midX = (chartArea.left + chartArea.right) / 2;\n\t\tvar midY = (chartArea.top + chartArea.bottom) / 2;\n\n\t\tif (yAlign === 'center') {\n\t\t\tlf = function(x) {\n\t\t\t\treturn x <= midX;\n\t\t\t};\n\t\t\trf = function(x) {\n\t\t\t\treturn x > midX;\n\t\t\t};\n\t\t} else {\n\t\t\tlf = function(x) {\n\t\t\t\treturn x <= (size.width / 2);\n\t\t\t};\n\t\t\trf = function(x) {\n\t\t\t\treturn x >= (chart.width - (size.width / 2));\n\t\t\t};\n\t\t}\n\n\t\tolf = function(x) {\n\t\t\treturn x + size.width > chart.width;\n\t\t};\n\t\torf = function(x) {\n\t\t\treturn x - size.width < 0;\n\t\t};\n\t\tyf = function(y) {\n\t\t\treturn y <= midY ? 'top' : 'bottom';\n\t\t};\n\n\t\tif (lf(model.x)) {\n\t\t\txAlign = 'left';\n\n\t\t\t// Is tooltip too wide and goes over the right side of the chart.?\n\t\t\tif (olf(model.x)) {\n\t\t\t\txAlign = 'center';\n\t\t\t\tyAlign = yf(model.y);\n\t\t\t}\n\t\t} else if (rf(model.x)) {\n\t\t\txAlign = 'right';\n\n\t\t\t// Is tooltip too wide and goes outside left edge of canvas?\n\t\t\tif (orf(model.x)) {\n\t\t\t\txAlign = 'center';\n\t\t\t\tyAlign = yf(model.y);\n\t\t\t}\n\t\t}\n\n\t\tvar opts = tooltip._options;\n\t\treturn {\n\t\t\txAlign: opts.xAlign ? opts.xAlign : xAlign,\n\t\t\tyAlign: opts.yAlign ? opts.yAlign : yAlign\n\t\t};\n\t}\n\n\t/**\n\t * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment\n\t */\n\tfunction getBackgroundPoint(vm, size, alignment) {\n\t\t// Background Position\n\t\tvar x = vm.x;\n\t\tvar y = vm.y;\n\n\t\tvar caretSize = vm.caretSize,\n\t\t\tcaretPadding = vm.caretPadding,\n\t\t\tcornerRadius = vm.cornerRadius,\n\t\t\txAlign = alignment.xAlign,\n\t\t\tyAlign = alignment.yAlign,\n\t\t\tpaddingAndSize = caretSize + caretPadding,\n\t\t\tradiusAndPadding = cornerRadius + caretPadding;\n\n\t\tif (xAlign === 'right') {\n\t\t\tx -= size.width;\n\t\t} else if (xAlign === 'center') {\n\t\t\tx -= (size.width / 2);\n\t\t}\n\n\t\tif (yAlign === 'top') {\n\t\t\ty += paddingAndSize;\n\t\t} else if (yAlign === 'bottom') {\n\t\t\ty -= size.height + paddingAndSize;\n\t\t} else {\n\t\t\ty -= (size.height / 2);\n\t\t}\n\n\t\tif (yAlign === 'center') {\n\t\t\tif (xAlign === 'left') {\n\t\t\t\tx += paddingAndSize;\n\t\t\t} else if (xAlign === 'right') {\n\t\t\t\tx -= paddingAndSize;\n\t\t\t}\n\t\t} else if (xAlign === 'left') {\n\t\t\tx -= radiusAndPadding;\n\t\t} else if (xAlign === 'right') {\n\t\t\tx += radiusAndPadding;\n\t\t}\n\n\t\treturn {\n\t\t\tx: x,\n\t\t\ty: y\n\t\t};\n\t}\n\n\tChart.Tooltip = Chart.Element.extend({\n\t\tinitialize: function() {\n\t\t\tthis._model = getBaseModel(this._options);\n\t\t},\n\n\t\t// Get the title\n\t\t// Args are: (tooltipItem, data)\n\t\tgetTitle: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me._options;\n\t\t\tvar callbacks = opts.callbacks;\n\n\t\t\tvar beforeTitle = callbacks.beforeTitle.apply(me, arguments),\n\t\t\t\ttitle = callbacks.title.apply(me, arguments),\n\t\t\t\tafterTitle = callbacks.afterTitle.apply(me, arguments);\n\n\t\t\tvar lines = [];\n\t\t\tlines = pushOrConcat(lines, beforeTitle);\n\t\t\tlines = pushOrConcat(lines, title);\n\t\t\tlines = pushOrConcat(lines, afterTitle);\n\n\t\t\treturn lines;\n\t\t},\n\n\t\t// Args are: (tooltipItem, data)\n\t\tgetBeforeBody: function() {\n\t\t\tvar lines = this._options.callbacks.beforeBody.apply(this, arguments);\n\t\t\treturn helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];\n\t\t},\n\n\t\t// Args are: (tooltipItem, data)\n\t\tgetBody: function(tooltipItems, data) {\n\t\t\tvar me = this;\n\t\t\tvar callbacks = me._options.callbacks;\n\t\t\tvar bodyItems = [];\n\n\t\t\thelpers.each(tooltipItems, function(tooltipItem) {\n\t\t\t\tvar bodyItem = {\n\t\t\t\t\tbefore: [],\n\t\t\t\t\tlines: [],\n\t\t\t\t\tafter: []\n\t\t\t\t};\n\t\t\t\tpushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));\n\t\t\t\tpushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));\n\t\t\t\tpushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));\n\n\t\t\t\tbodyItems.push(bodyItem);\n\t\t\t});\n\n\t\t\treturn bodyItems;\n\t\t},\n\n\t\t// Args are: (tooltipItem, data)\n\t\tgetAfterBody: function() {\n\t\t\tvar lines = this._options.callbacks.afterBody.apply(this, arguments);\n\t\t\treturn helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];\n\t\t},\n\n\t\t// Get the footer and beforeFooter and afterFooter lines\n\t\t// Args are: (tooltipItem, data)\n\t\tgetFooter: function() {\n\t\t\tvar me = this;\n\t\t\tvar callbacks = me._options.callbacks;\n\n\t\t\tvar beforeFooter = callbacks.beforeFooter.apply(me, arguments);\n\t\t\tvar footer = callbacks.footer.apply(me, arguments);\n\t\t\tvar afterFooter = callbacks.afterFooter.apply(me, arguments);\n\n\t\t\tvar lines = [];\n\t\t\tlines = pushOrConcat(lines, beforeFooter);\n\t\t\tlines = pushOrConcat(lines, footer);\n\t\t\tlines = pushOrConcat(lines, afterFooter);\n\n\t\t\treturn lines;\n\t\t},\n\n\t\tupdate: function(changed) {\n\t\t\tvar me = this;\n\t\t\tvar opts = me._options;\n\n\t\t\t// Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition\n\t\t\t// that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time\n\t\t\t// which breaks any animations.\n\t\t\tvar existingModel = me._model;\n\t\t\tvar model = me._model = getBaseModel(opts);\n\t\t\tvar active = me._active;\n\n\t\t\tvar data = me._data;\n\t\t\tvar chartInstance = me._chartInstance;\n\n\t\t\t// In the case where active.length === 0 we need to keep these at existing values for good animations\n\t\t\tvar alignment = {\n\t\t\t\txAlign: existingModel.xAlign,\n\t\t\t\tyAlign: existingModel.yAlign\n\t\t\t};\n\t\t\tvar backgroundPoint = {\n\t\t\t\tx: existingModel.x,\n\t\t\t\ty: existingModel.y\n\t\t\t};\n\t\t\tvar tooltipSize = {\n\t\t\t\twidth: existingModel.width,\n\t\t\t\theight: existingModel.height\n\t\t\t};\n\t\t\tvar tooltipPosition = {\n\t\t\t\tx: existingModel.caretX,\n\t\t\t\ty: existingModel.caretY\n\t\t\t};\n\n\t\t\tvar i, len;\n\n\t\t\tif (active.length) {\n\t\t\t\tmodel.opacity = 1;\n\n\t\t\t\tvar labelColors = [];\n\t\t\t\ttooltipPosition = Chart.Tooltip.positioners[opts.position](active, me._eventPosition);\n\n\t\t\t\tvar tooltipItems = [];\n\t\t\t\tfor (i = 0, len = active.length; i < len; ++i) {\n\t\t\t\t\ttooltipItems.push(createTooltipItem(active[i]));\n\t\t\t\t}\n\n\t\t\t\t// If the user provided a filter function, use it to modify the tooltip items\n\t\t\t\tif (opts.filter) {\n\t\t\t\t\ttooltipItems = tooltipItems.filter(function(a) {\n\t\t\t\t\t\treturn opts.filter(a, data);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// If the user provided a sorting function, use it to modify the tooltip items\n\t\t\t\tif (opts.itemSort) {\n\t\t\t\t\ttooltipItems = tooltipItems.sort(function(a, b) {\n\t\t\t\t\t\treturn opts.itemSort(a, b, data);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Determine colors for boxes\n\t\t\t\thelpers.each(tooltipItems, function(tooltipItem) {\n\t\t\t\t\tlabelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance));\n\t\t\t\t});\n\n\t\t\t\t// Build the Text Lines\n\t\t\t\tmodel.title = me.getTitle(tooltipItems, data);\n\t\t\t\tmodel.beforeBody = me.getBeforeBody(tooltipItems, data);\n\t\t\t\tmodel.body = me.getBody(tooltipItems, data);\n\t\t\t\tmodel.afterBody = me.getAfterBody(tooltipItems, data);\n\t\t\t\tmodel.footer = me.getFooter(tooltipItems, data);\n\n\t\t\t\t// Initial positioning and colors\n\t\t\t\tmodel.x = Math.round(tooltipPosition.x);\n\t\t\t\tmodel.y = Math.round(tooltipPosition.y);\n\t\t\t\tmodel.caretPadding = helpers.getValueOrDefault(tooltipPosition.padding, 2);\n\t\t\t\tmodel.labelColors = labelColors;\n\n\t\t\t\t// data points\n\t\t\t\tmodel.dataPoints = tooltipItems;\n\n\t\t\t\t// We need to determine alignment of the tooltip\n\t\t\t\ttooltipSize = getTooltipSize(this, model);\n\t\t\t\talignment = determineAlignment(this, tooltipSize);\n\t\t\t\t// Final Size and Position\n\t\t\t\tbackgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);\n\t\t\t} else {\n\t\t\t\tmodel.opacity = 0;\n\t\t\t}\n\n\t\t\tmodel.xAlign = alignment.xAlign;\n\t\t\tmodel.yAlign = alignment.yAlign;\n\t\t\tmodel.x = backgroundPoint.x;\n\t\t\tmodel.y = backgroundPoint.y;\n\t\t\tmodel.width = tooltipSize.width;\n\t\t\tmodel.height = tooltipSize.height;\n\n\t\t\t// Point where the caret on the tooltip points to\n\t\t\tmodel.caretX = tooltipPosition.x;\n\t\t\tmodel.caretY = tooltipPosition.y;\n\n\t\t\tme._model = model;\n\n\t\t\tif (changed && opts.custom) {\n\t\t\t\topts.custom.call(me, model);\n\t\t\t}\n\n\t\t\treturn me;\n\t\t},\n\t\tdrawCaret: function(tooltipPoint, size, opacity) {\n\t\t\tvar vm = this._view;\n\t\t\tvar ctx = this._chart.ctx;\n\t\t\tvar x1, x2, x3;\n\t\t\tvar y1, y2, y3;\n\t\t\tvar caretSize = vm.caretSize;\n\t\t\tvar cornerRadius = vm.cornerRadius;\n\t\t\tvar xAlign = vm.xAlign,\n\t\t\t\tyAlign = vm.yAlign;\n\t\t\tvar ptX = tooltipPoint.x,\n\t\t\t\tptY = tooltipPoint.y;\n\t\t\tvar width = size.width,\n\t\t\t\theight = size.height;\n\n\t\t\tif (yAlign === 'center') {\n\t\t\t\t// Left or right side\n\t\t\t\tif (xAlign === 'left') {\n\t\t\t\t\tx1 = ptX;\n\t\t\t\t\tx2 = x1 - caretSize;\n\t\t\t\t\tx3 = x1;\n\t\t\t\t} else {\n\t\t\t\t\tx1 = ptX + width;\n\t\t\t\t\tx2 = x1 + caretSize;\n\t\t\t\t\tx3 = x1;\n\t\t\t\t}\n\n\t\t\t\ty2 = ptY + (height / 2);\n\t\t\t\ty1 = y2 - caretSize;\n\t\t\t\ty3 = y2 + caretSize;\n\t\t\t} else {\n\t\t\t\tif (xAlign === 'left') {\n\t\t\t\t\tx1 = ptX + cornerRadius;\n\t\t\t\t\tx2 = x1 + caretSize;\n\t\t\t\t\tx3 = x2 + caretSize;\n\t\t\t\t} else if (xAlign === 'right') {\n\t\t\t\t\tx1 = ptX + width - cornerRadius;\n\t\t\t\t\tx2 = x1 - caretSize;\n\t\t\t\t\tx3 = x2 - caretSize;\n\t\t\t\t} else {\n\t\t\t\t\tx2 = ptX + (width / 2);\n\t\t\t\t\tx1 = x2 - caretSize;\n\t\t\t\t\tx3 = x2 + caretSize;\n\t\t\t\t}\n\n\t\t\t\tif (yAlign === 'top') {\n\t\t\t\t\ty1 = ptY;\n\t\t\t\t\ty2 = y1 - caretSize;\n\t\t\t\t\ty3 = y1;\n\t\t\t\t} else {\n\t\t\t\t\ty1 = ptY + height;\n\t\t\t\t\ty2 = y1 + caretSize;\n\t\t\t\t\ty3 = y1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x1, y1);\n\t\t\tctx.lineTo(x2, y2);\n\t\t\tctx.lineTo(x3, y3);\n\t\t\tctx.closePath();\n\t\t\tctx.fill();\n\t\t},\n\t\tdrawTitle: function(pt, vm, ctx, opacity) {\n\t\t\tvar title = vm.title;\n\n\t\t\tif (title.length) {\n\t\t\t\tctx.textAlign = vm._titleAlign;\n\t\t\t\tctx.textBaseline = 'top';\n\n\t\t\t\tvar titleFontSize = vm.titleFontSize,\n\t\t\t\t\ttitleSpacing = vm.titleSpacing;\n\n\t\t\t\tctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);\n\t\t\t\tctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);\n\n\t\t\t\tvar i, len;\n\t\t\t\tfor (i = 0, len = title.length; i < len; ++i) {\n\t\t\t\t\tctx.fillText(title[i], pt.x, pt.y);\n\t\t\t\t\tpt.y += titleFontSize + titleSpacing; // Line Height and spacing\n\n\t\t\t\t\tif (i + 1 === title.length) {\n\t\t\t\t\t\tpt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdrawBody: function(pt, vm, ctx, opacity) {\n\t\t\tvar bodyFontSize = vm.bodyFontSize;\n\t\t\tvar bodySpacing = vm.bodySpacing;\n\t\t\tvar body = vm.body;\n\n\t\t\tctx.textAlign = vm._bodyAlign;\n\t\t\tctx.textBaseline = 'top';\n\n\t\t\tvar textColor = mergeOpacity(vm.bodyFontColor, opacity);\n\t\t\tctx.fillStyle = textColor;\n\t\t\tctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);\n\n\t\t\t// Before Body\n\t\t\tvar xLinePadding = 0;\n\t\t\tvar fillLineOfText = function(line) {\n\t\t\t\tctx.fillText(line, pt.x + xLinePadding, pt.y);\n\t\t\t\tpt.y += bodyFontSize + bodySpacing;\n\t\t\t};\n\n\t\t\t// Before body lines\n\t\t\thelpers.each(vm.beforeBody, fillLineOfText);\n\n\t\t\tvar drawColorBoxes = vm.displayColors;\n\t\t\txLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;\n\n\t\t\t// Draw body lines now\n\t\t\thelpers.each(body, function(bodyItem, i) {\n\t\t\t\thelpers.each(bodyItem.before, fillLineOfText);\n\n\t\t\t\thelpers.each(bodyItem.lines, function(line) {\n\t\t\t\t\t// Draw Legend-like boxes if needed\n\t\t\t\t\tif (drawColorBoxes) {\n\t\t\t\t\t\t// Fill a white rect so that colours merge nicely if the opacity is < 1\n\t\t\t\t\t\tctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);\n\t\t\t\t\t\tctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);\n\n\t\t\t\t\t\t// Border\n\t\t\t\t\t\tctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);\n\t\t\t\t\t\tctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);\n\n\t\t\t\t\t\t// Inner square\n\t\t\t\t\t\tctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);\n\t\t\t\t\t\tctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);\n\n\t\t\t\t\t\tctx.fillStyle = textColor;\n\t\t\t\t\t}\n\n\t\t\t\t\tfillLineOfText(line);\n\t\t\t\t});\n\n\t\t\t\thelpers.each(bodyItem.after, fillLineOfText);\n\t\t\t});\n\n\t\t\t// Reset back to 0 for after body\n\t\t\txLinePadding = 0;\n\n\t\t\t// After body lines\n\t\t\thelpers.each(vm.afterBody, fillLineOfText);\n\t\t\tpt.y -= bodySpacing; // Remove last body spacing\n\t\t},\n\t\tdrawFooter: function(pt, vm, ctx, opacity) {\n\t\t\tvar footer = vm.footer;\n\n\t\t\tif (footer.length) {\n\t\t\t\tpt.y += vm.footerMarginTop;\n\n\t\t\t\tctx.textAlign = vm._footerAlign;\n\t\t\t\tctx.textBaseline = 'top';\n\n\t\t\t\tctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);\n\t\t\t\tctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);\n\n\t\t\t\thelpers.each(footer, function(line) {\n\t\t\t\t\tctx.fillText(line, pt.x, pt.y);\n\t\t\t\t\tpt.y += vm.footerFontSize + vm.footerSpacing;\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tdrawBackground: function(pt, vm, ctx, tooltipSize, opacity) {\n\t\t\tctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);\n\t\t\thelpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);\n\t\t\tctx.fill();\n\t\t},\n\t\tdraw: function() {\n\t\t\tvar ctx = this._chart.ctx;\n\t\t\tvar vm = this._view;\n\n\t\t\tif (vm.opacity === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar tooltipSize = {\n\t\t\t\twidth: vm.width,\n\t\t\t\theight: vm.height\n\t\t\t};\n\t\t\tvar pt = {\n\t\t\t\tx: vm.x,\n\t\t\t\ty: vm.y\n\t\t\t};\n\n\t\t\t// IE11/Edge does not like very small opacities, so snap to 0\n\t\t\tvar opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;\n\n\t\t\tif (this._options.enabled) {\n\t\t\t\t// Draw Background\n\t\t\t\tthis.drawBackground(pt, vm, ctx, tooltipSize, opacity);\n\n\t\t\t\t// Draw Caret\n\t\t\t\tthis.drawCaret(pt, tooltipSize, opacity);\n\n\t\t\t\t// Draw Title, Body, and Footer\n\t\t\t\tpt.x += vm.xPadding;\n\t\t\t\tpt.y += vm.yPadding;\n\n\t\t\t\t// Titles\n\t\t\t\tthis.drawTitle(pt, vm, ctx, opacity);\n\n\t\t\t\t// Body\n\t\t\t\tthis.drawBody(pt, vm, ctx, opacity);\n\n\t\t\t\t// Footer\n\t\t\t\tthis.drawFooter(pt, vm, ctx, opacity);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle an event\n\t\t * @private\n\t\t * @param {IEvent} event - The event to handle\n\t\t * @returns {Boolean} true if the tooltip changed\n\t\t */\n\t\thandleEvent: function(e) {\n\t\t\tvar me = this;\n\t\t\tvar options = me._options;\n\t\t\tvar changed = false;\n\n\t\t\tme._lastActive = me._lastActive || [];\n\n\t\t\t// Find Active Elements for tooltips\n\t\t\tif (e.type === 'mouseout') {\n\t\t\t\tme._active = [];\n\t\t\t} else {\n\t\t\t\tme._active = me._chartInstance.getElementsAtEventForMode(e, options.mode, options);\n\t\t\t}\n\n\t\t\t// Remember Last Actives\n\t\t\tchanged = !helpers.arrayEquals(me._active, me._lastActive);\n\t\t\tme._lastActive = me._active;\n\n\t\t\tif (options.enabled || options.custom) {\n\t\t\t\tme._eventPosition = {\n\t\t\t\t\tx: e.x,\n\t\t\t\t\ty: e.y\n\t\t\t\t};\n\n\t\t\t\tvar model = me._model;\n\t\t\t\tme.update(true);\n\t\t\t\tme.pivot();\n\n\t\t\t\t// See if our tooltip position changed\n\t\t\t\tchanged |= (model.x !== me._model.x) || (model.y !== me._model.y);\n\t\t\t}\n\n\t\t\treturn changed;\n\t\t}\n\t});\n\n\t/**\n\t * @namespace Chart.Tooltip.positioners\n\t */\n\tChart.Tooltip.positioners = {\n\t\t/**\n\t\t * Average mode places the tooltip at the average position of the elements shown\n\t\t * @function Chart.Tooltip.positioners.average\n\t\t * @param elements {ChartElement[]} the elements being displayed in the tooltip\n\t\t * @returns {Point} tooltip position\n\t\t */\n\t\taverage: function(elements) {\n\t\t\tif (!elements.length) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvar i, len;\n\t\t\tvar x = 0;\n\t\t\tvar y = 0;\n\t\t\tvar count = 0;\n\n\t\t\tfor (i = 0, len = elements.length; i < len; ++i) {\n\t\t\t\tvar el = elements[i];\n\t\t\t\tif (el && el.hasValue()) {\n\t\t\t\t\tvar pos = el.tooltipPosition();\n\t\t\t\t\tx += pos.x;\n\t\t\t\t\ty += pos.y;\n\t\t\t\t\t++count;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tx: Math.round(x / count),\n\t\t\t\ty: Math.round(y / count)\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Gets the tooltip position nearest of the item nearest to the event position\n\t\t * @function Chart.Tooltip.positioners.nearest\n\t\t * @param elements {Chart.Element[]} the tooltip elements\n\t\t * @param eventPosition {Point} the position of the event in canvas coordinates\n\t\t * @returns {Point} the tooltip position\n\t\t */\n\t\tnearest: function(elements, eventPosition) {\n\t\t\tvar x = eventPosition.x;\n\t\t\tvar y = eventPosition.y;\n\n\t\t\tvar nearestElement;\n\t\t\tvar minDistance = Number.POSITIVE_INFINITY;\n\t\t\tvar i, len;\n\t\t\tfor (i = 0, len = elements.length; i < len; ++i) {\n\t\t\t\tvar el = elements[i];\n\t\t\t\tif (el && el.hasValue()) {\n\t\t\t\t\tvar center = el.getCenterPoint();\n\t\t\t\t\tvar d = helpers.distanceBetweenPoints(eventPosition, center);\n\n\t\t\t\t\tif (d < minDistance) {\n\t\t\t\t\t\tminDistance = d;\n\t\t\t\t\t\tnearestElement = el;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (nearestElement) {\n\t\t\t\tvar tp = nearestElement.tooltipPosition();\n\t\t\t\tx = tp.x;\n\t\t\t\ty = tp.y;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tx: x,\n\t\t\t\ty: y\n\t\t\t};\n\t\t}\n\t};\n};\n\n},{}],37:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers,\n\t\tglobalOpts = Chart.defaults.global;\n\n\tglobalOpts.elements.arc = {\n\t\tbackgroundColor: globalOpts.defaultColor,\n\t\tborderColor: '#fff',\n\t\tborderWidth: 2\n\t};\n\n\tChart.elements.Arc = Chart.Element.extend({\n\t\tinLabelRange: function(mouseX) {\n\t\t\tvar vm = this._view;\n\n\t\t\tif (vm) {\n\t\t\t\treturn (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\tinRange: function(chartX, chartY) {\n\t\t\tvar vm = this._view;\n\n\t\t\tif (vm) {\n\t\t\t\tvar pointRelativePosition = helpers.getAngleFromPoint(vm, {\n\t\t\t\t\t\tx: chartX,\n\t\t\t\t\t\ty: chartY\n\t\t\t\t\t}),\n\t\t\t\t\tangle = pointRelativePosition.angle,\n\t\t\t\t\tdistance = pointRelativePosition.distance;\n\n\t\t\t\t// Sanitise angle range\n\t\t\t\tvar startAngle = vm.startAngle;\n\t\t\t\tvar endAngle = vm.endAngle;\n\t\t\t\twhile (endAngle < startAngle) {\n\t\t\t\t\tendAngle += 2.0 * Math.PI;\n\t\t\t\t}\n\t\t\t\twhile (angle > endAngle) {\n\t\t\t\t\tangle -= 2.0 * Math.PI;\n\t\t\t\t}\n\t\t\t\twhile (angle < startAngle) {\n\t\t\t\t\tangle += 2.0 * Math.PI;\n\t\t\t\t}\n\n\t\t\t\t// Check if within the range of the open/close angle\n\t\t\t\tvar betweenAngles = (angle >= startAngle && angle <= endAngle),\n\t\t\t\t\twithinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);\n\n\t\t\t\treturn (betweenAngles && withinRadius);\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\tgetCenterPoint: function() {\n\t\t\tvar vm = this._view;\n\t\t\tvar halfAngle = (vm.startAngle + vm.endAngle) / 2;\n\t\t\tvar halfRadius = (vm.innerRadius + vm.outerRadius) / 2;\n\t\t\treturn {\n\t\t\t\tx: vm.x + Math.cos(halfAngle) * halfRadius,\n\t\t\t\ty: vm.y + Math.sin(halfAngle) * halfRadius\n\t\t\t};\n\t\t},\n\t\tgetArea: function() {\n\t\t\tvar vm = this._view;\n\t\t\treturn Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));\n\t\t},\n\t\ttooltipPosition: function() {\n\t\t\tvar vm = this._view;\n\n\t\t\tvar centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),\n\t\t\t\trangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;\n\t\t\treturn {\n\t\t\t\tx: vm.x + (Math.cos(centreAngle) * rangeFromCentre),\n\t\t\t\ty: vm.y + (Math.sin(centreAngle) * rangeFromCentre)\n\t\t\t};\n\t\t},\n\t\tdraw: function() {\n\n\t\t\tvar ctx = this._chart.ctx,\n\t\t\t\tvm = this._view,\n\t\t\t\tsA = vm.startAngle,\n\t\t\t\teA = vm.endAngle;\n\n\t\t\tctx.beginPath();\n\n\t\t\tctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);\n\t\t\tctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);\n\n\t\t\tctx.closePath();\n\t\t\tctx.strokeStyle = vm.borderColor;\n\t\t\tctx.lineWidth = vm.borderWidth;\n\n\t\t\tctx.fillStyle = vm.backgroundColor;\n\n\t\t\tctx.fill();\n\t\t\tctx.lineJoin = 'bevel';\n\n\t\t\tif (vm.borderWidth) {\n\t\t\t\tctx.stroke();\n\t\t\t}\n\t\t}\n\t});\n};\n\n},{}],38:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\tvar globalDefaults = Chart.defaults.global;\n\n\tChart.defaults.global.elements.line = {\n\t\ttension: 0.4,\n\t\tbackgroundColor: globalDefaults.defaultColor,\n\t\tborderWidth: 3,\n\t\tborderColor: globalDefaults.defaultColor,\n\t\tborderCapStyle: 'butt',\n\t\tborderDash: [],\n\t\tborderDashOffset: 0.0,\n\t\tborderJoinStyle: 'miter',\n\t\tcapBezierPoints: true,\n\t\tfill: true, // do we fill in the area between the line and its base axis\n\t};\n\n\tChart.elements.Line = Chart.Element.extend({\n\t\tdraw: function() {\n\t\t\tvar me = this;\n\t\t\tvar vm = me._view;\n\t\t\tvar spanGaps = vm.spanGaps;\n\t\t\tvar fillPoint = vm.scaleZero;\n\t\t\tvar loop = me._loop;\n\n\t\t\t// Handle different fill modes for cartesian lines\n\t\t\tif (!loop) {\n\t\t\t\tif (vm.fill === 'top') {\n\t\t\t\t\tfillPoint = vm.scaleTop;\n\t\t\t\t} else if (vm.fill === 'bottom') {\n\t\t\t\t\tfillPoint = vm.scaleBottom;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar ctx = me._chart.ctx;\n\t\t\tctx.save();\n\n\t\t\t// Helper function to draw a line to a point\n\t\t\tfunction lineToPoint(previousPoint, point) {\n\t\t\t\tvar pointVM = point._view;\n\t\t\t\tif (point._view.steppedLine === true) {\n\t\t\t\t\tctx.lineTo(pointVM.x, previousPoint._view.y);\n\t\t\t\t\tctx.lineTo(pointVM.x, pointVM.y);\n\t\t\t\t} else if (point._view.tension === 0) {\n\t\t\t\t\tctx.lineTo(pointVM.x, pointVM.y);\n\t\t\t\t} else {\n\t\t\t\t\tctx.bezierCurveTo(\n\t\t\t\t\t\tpreviousPoint._view.controlPointNextX,\n\t\t\t\t\t\tpreviousPoint._view.controlPointNextY,\n\t\t\t\t\t\tpointVM.controlPointPreviousX,\n\t\t\t\t\t\tpointVM.controlPointPreviousY,\n\t\t\t\t\t\tpointVM.x,\n\t\t\t\t\t\tpointVM.y\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar points = me._children.slice(); // clone array\n\t\t\tvar lastDrawnIndex = -1;\n\n\t\t\t// If we are looping, adding the first point again\n\t\t\tif (loop && points.length) {\n\t\t\t\tpoints.push(points[0]);\n\t\t\t}\n\n\t\t\tvar index, current, previous, currentVM;\n\n\t\t\t// Fill Line\n\t\t\tif (points.length && vm.fill) {\n\t\t\t\tctx.beginPath();\n\n\t\t\t\tfor (index = 0; index < points.length; ++index) {\n\t\t\t\t\tcurrent = points[index];\n\t\t\t\t\tprevious = helpers.previousItem(points, index);\n\t\t\t\t\tcurrentVM = current._view;\n\n\t\t\t\t\t// First point moves to it's starting position no matter what\n\t\t\t\t\tif (index === 0) {\n\t\t\t\t\t\tif (loop) {\n\t\t\t\t\t\t\tctx.moveTo(fillPoint.x, fillPoint.y);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tctx.moveTo(currentVM.x, fillPoint);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!currentVM.skip) {\n\t\t\t\t\t\t\tlastDrawnIndex = index;\n\t\t\t\t\t\t\tctx.lineTo(currentVM.x, currentVM.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprevious = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];\n\n\t\t\t\t\t\tif (currentVM.skip) {\n\t\t\t\t\t\t\t// Only do this if this is the first point that is skipped\n\t\t\t\t\t\t\tif (!spanGaps && lastDrawnIndex === (index - 1)) {\n\t\t\t\t\t\t\t\tif (loop) {\n\t\t\t\t\t\t\t\t\tctx.lineTo(fillPoint.x, fillPoint.y);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tctx.lineTo(previous._view.x, fillPoint);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (lastDrawnIndex !== (index - 1)) {\n\t\t\t\t\t\t\t\t// There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case.\n\t\t\t\t\t\t\t\t// If the first data point is NaN, then there is no real gap to skip\n\t\t\t\t\t\t\t\tif (spanGaps && lastDrawnIndex !== -1) {\n\t\t\t\t\t\t\t\t\t// We are spanning the gap, so simple draw a line to this point\n\t\t\t\t\t\t\t\t\tlineToPoint(previous, current);\n\t\t\t\t\t\t\t\t} else if (loop) {\n\t\t\t\t\t\t\t\t\tctx.lineTo(currentVM.x, currentVM.y);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tctx.lineTo(currentVM.x, fillPoint);\n\t\t\t\t\t\t\t\t\tctx.lineTo(currentVM.x, currentVM.y);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Line to next point\n\t\t\t\t\t\t\t\tlineToPoint(previous, current);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlastDrawnIndex = index;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!loop && lastDrawnIndex !== -1) {\n\t\t\t\t\tctx.lineTo(points[lastDrawnIndex]._view.x, fillPoint);\n\t\t\t\t}\n\n\t\t\t\tctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;\n\t\t\t\tctx.closePath();\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\t// Stroke Line Options\n\t\t\tvar globalOptionLineElements = globalDefaults.elements.line;\n\t\t\tctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;\n\n\t\t\t// IE 9 and 10 do not support line dash\n\t\t\tif (ctx.setLineDash) {\n\t\t\t\tctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);\n\t\t\t}\n\n\t\t\tctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;\n\t\t\tctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;\n\t\t\tctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;\n\t\t\tctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;\n\n\t\t\t// Stroke Line\n\t\t\tctx.beginPath();\n\t\t\tlastDrawnIndex = -1;\n\n\t\t\tfor (index = 0; index < points.length; ++index) {\n\t\t\t\tcurrent = points[index];\n\t\t\t\tprevious = helpers.previousItem(points, index);\n\t\t\t\tcurrentVM = current._view;\n\n\t\t\t\t// First point moves to it's starting position no matter what\n\t\t\t\tif (index === 0) {\n\t\t\t\t\tif (!currentVM.skip) {\n\t\t\t\t\t\tctx.moveTo(currentVM.x, currentVM.y);\n\t\t\t\t\t\tlastDrawnIndex = index;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprevious = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];\n\n\t\t\t\t\tif (!currentVM.skip) {\n\t\t\t\t\t\tif ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {\n\t\t\t\t\t\t\t// There was a gap and this is the first point after the gap\n\t\t\t\t\t\t\tctx.moveTo(currentVM.x, currentVM.y);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Line to next point\n\t\t\t\t\t\t\tlineToPoint(previous, current);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastDrawnIndex = index;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.stroke();\n\t\t\tctx.restore();\n\t\t}\n\t});\n};\n\n},{}],39:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers,\n\t\tglobalOpts = Chart.defaults.global,\n\t\tdefaultColor = globalOpts.defaultColor;\n\n\tglobalOpts.elements.point = {\n\t\tradius: 3,\n\t\tpointStyle: 'circle',\n\t\tbackgroundColor: defaultColor,\n\t\tborderWidth: 1,\n\t\tborderColor: defaultColor,\n\t\t// Hover\n\t\thitRadius: 1,\n\t\thoverRadius: 4,\n\t\thoverBorderWidth: 1\n\t};\n\n\tfunction xRange(mouseX) {\n\t\tvar vm = this._view;\n\t\treturn vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;\n\t}\n\n\tfunction yRange(mouseY) {\n\t\tvar vm = this._view;\n\t\treturn vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;\n\t}\n\n\tChart.elements.Point = Chart.Element.extend({\n\t\tinRange: function(mouseX, mouseY) {\n\t\t\tvar vm = this._view;\n\t\t\treturn vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;\n\t\t},\n\n\t\tinLabelRange: xRange,\n\t\tinXRange: xRange,\n\t\tinYRange: yRange,\n\n\t\tgetCenterPoint: function() {\n\t\t\tvar vm = this._view;\n\t\t\treturn {\n\t\t\t\tx: vm.x,\n\t\t\t\ty: vm.y\n\t\t\t};\n\t\t},\n\t\tgetArea: function() {\n\t\t\treturn Math.PI * Math.pow(this._view.radius, 2);\n\t\t},\n\t\ttooltipPosition: function() {\n\t\t\tvar vm = this._view;\n\t\t\treturn {\n\t\t\t\tx: vm.x,\n\t\t\t\ty: vm.y,\n\t\t\t\tpadding: vm.radius + vm.borderWidth\n\t\t\t};\n\t\t},\n\t\tdraw: function(chartArea) {\n\t\t\tvar vm = this._view;\n\t\t\tvar model = this._model;\n\t\t\tvar ctx = this._chart.ctx;\n\t\t\tvar pointStyle = vm.pointStyle;\n\t\t\tvar radius = vm.radius;\n\t\t\tvar x = vm.x;\n\t\t\tvar y = vm.y;\n\t\t\tvar color = Chart.helpers.color;\n\t\t\tvar errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)\n\t\t\tvar ratio = 0;\n\n\t\t\tif (vm.skip) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.strokeStyle = vm.borderColor || defaultColor;\n\t\t\tctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);\n\t\t\tctx.fillStyle = vm.backgroundColor || defaultColor;\n\n\t\t\t// Cliping for Points.\n\t\t\t// going out from inner charArea?\n\t\t\tif ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right*errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom*errMargin < model.y))) {\n\t\t\t\t// Point fade out\n\t\t\t\tif (model.x < chartArea.left) {\n\t\t\t\t\tratio = (x - model.x) / (chartArea.left - model.x);\n\t\t\t\t} else if (chartArea.right*errMargin < model.x) {\n\t\t\t\t\tratio = (model.x - x) / (model.x - chartArea.right);\n\t\t\t\t} else if (model.y < chartArea.top) {\n\t\t\t\t\tratio = (y - model.y) / (chartArea.top - model.y);\n\t\t\t\t} else if (chartArea.bottom*errMargin < model.y) {\n\t\t\t\t\tratio = (model.y - y) / (model.y - chartArea.bottom);\n\t\t\t\t}\n\t\t\t\tratio = Math.round(ratio*100) / 100;\n\t\t\t\tctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();\n\t\t\t\tctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();\n\t\t\t}\n\n\t\t\tChart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);\n\t\t}\n\t});\n};\n\n},{}],40:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar globalOpts = Chart.defaults.global;\n\n\tglobalOpts.elements.rectangle = {\n\t\tbackgroundColor: globalOpts.defaultColor,\n\t\tborderWidth: 0,\n\t\tborderColor: globalOpts.defaultColor,\n\t\tborderSkipped: 'bottom'\n\t};\n\n\tfunction isVertical(bar) {\n\t\treturn bar._view.width !== undefined;\n\t}\n\n\t/**\n\t * Helper function to get the bounds of the bar regardless of the orientation\n\t * @private\n\t * @param bar {Chart.Element.Rectangle} the bar\n\t * @return {Bounds} bounds of the bar\n\t */\n\tfunction getBarBounds(bar) {\n\t\tvar vm = bar._view;\n\t\tvar x1, x2, y1, y2;\n\n\t\tif (isVertical(bar)) {\n\t\t\t// vertical\n\t\t\tvar halfWidth = vm.width / 2;\n\t\t\tx1 = vm.x - halfWidth;\n\t\t\tx2 = vm.x + halfWidth;\n\t\t\ty1 = Math.min(vm.y, vm.base);\n\t\t\ty2 = Math.max(vm.y, vm.base);\n\t\t} else {\n\t\t\t// horizontal bar\n\t\t\tvar halfHeight = vm.height / 2;\n\t\t\tx1 = Math.min(vm.x, vm.base);\n\t\t\tx2 = Math.max(vm.x, vm.base);\n\t\t\ty1 = vm.y - halfHeight;\n\t\t\ty2 = vm.y + halfHeight;\n\t\t}\n\n\t\treturn {\n\t\t\tleft: x1,\n\t\t\ttop: y1,\n\t\t\tright: x2,\n\t\t\tbottom: y2\n\t\t};\n\t}\n\n\tChart.elements.Rectangle = Chart.Element.extend({\n\t\tdraw: function() {\n\t\t\tvar ctx = this._chart.ctx;\n\t\t\tvar vm = this._view;\n\t\t\tvar left, right, top, bottom, signX, signY, borderSkipped;\n\t\t\tvar borderWidth = vm.borderWidth;\n\n\t\t\tif (!vm.horizontal) {\n\t\t\t\t// bar\n\t\t\t\tleft = vm.x - vm.width / 2;\n\t\t\t\tright = vm.x + vm.width / 2;\n\t\t\t\ttop = vm.y;\n\t\t\t\tbottom = vm.base;\n\t\t\t\tsignX = 1;\n\t\t\t\tsignY = bottom > top? 1: -1;\n\t\t\t\tborderSkipped = vm.borderSkipped || 'bottom';\n\t\t\t} else {\n\t\t\t\t// horizontal bar\n\t\t\t\tleft = vm.base;\n\t\t\t\tright = vm.x;\n\t\t\t\ttop = vm.y - vm.height / 2;\n\t\t\t\tbottom = vm.y + vm.height / 2;\n\t\t\t\tsignX = right > left? 1: -1;\n\t\t\t\tsignY = 1;\n\t\t\t\tborderSkipped = vm.borderSkipped || 'left';\n\t\t\t}\n\n\t\t\t// Canvas doesn't allow us to stroke inside the width so we can\n\t\t\t// adjust the sizes to fit if we're setting a stroke on the line\n\t\t\tif (borderWidth) {\n\t\t\t\t// borderWidth shold be less than bar width and bar height.\n\t\t\t\tvar barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));\n\t\t\t\tborderWidth = borderWidth > barSize? barSize: borderWidth;\n\t\t\t\tvar halfStroke = borderWidth / 2;\n\t\t\t\t// Adjust borderWidth when bar top position is near vm.base(zero).\n\t\t\t\tvar borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0);\n\t\t\t\tvar borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0);\n\t\t\t\tvar borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0);\n\t\t\t\tvar borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0);\n\t\t\t\t// not become a vertical line?\n\t\t\t\tif (borderLeft !== borderRight) {\n\t\t\t\t\ttop = borderTop;\n\t\t\t\t\tbottom = borderBottom;\n\t\t\t\t}\n\t\t\t\t// not become a horizontal line?\n\t\t\t\tif (borderTop !== borderBottom) {\n\t\t\t\t\tleft = borderLeft;\n\t\t\t\t\tright = borderRight;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.beginPath();\n\t\t\tctx.fillStyle = vm.backgroundColor;\n\t\t\tctx.strokeStyle = vm.borderColor;\n\t\t\tctx.lineWidth = borderWidth;\n\n\t\t\t// Corner points, from bottom-left to bottom-right clockwise\n\t\t\t// | 1 2 |\n\t\t\t// | 0 3 |\n\t\t\tvar corners = [\n\t\t\t\t[left, bottom],\n\t\t\t\t[left, top],\n\t\t\t\t[right, top],\n\t\t\t\t[right, bottom]\n\t\t\t];\n\n\t\t\t// Find first (starting) corner with fallback to 'bottom'\n\t\t\tvar borders = ['bottom', 'left', 'top', 'right'];\n\t\t\tvar startCorner = borders.indexOf(borderSkipped, 0);\n\t\t\tif (startCorner === -1) {\n\t\t\t\tstartCorner = 0;\n\t\t\t}\n\n\t\t\tfunction cornerAt(index) {\n\t\t\t\treturn corners[(startCorner + index) % 4];\n\t\t\t}\n\n\t\t\t// Draw rectangle from 'startCorner'\n\t\t\tvar corner = cornerAt(0);\n\t\t\tctx.moveTo(corner[0], corner[1]);\n\n\t\t\tfor (var i = 1; i < 4; i++) {\n\t\t\t\tcorner = cornerAt(i);\n\t\t\t\tctx.lineTo(corner[0], corner[1]);\n\t\t\t}\n\n\t\t\tctx.fill();\n\t\t\tif (borderWidth) {\n\t\t\t\tctx.stroke();\n\t\t\t}\n\t\t},\n\t\theight: function() {\n\t\t\tvar vm = this._view;\n\t\t\treturn vm.base - vm.y;\n\t\t},\n\t\tinRange: function(mouseX, mouseY) {\n\t\t\tvar inRange = false;\n\n\t\t\tif (this._view) {\n\t\t\t\tvar bounds = getBarBounds(this);\n\t\t\t\tinRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;\n\t\t\t}\n\n\t\t\treturn inRange;\n\t\t},\n\t\tinLabelRange: function(mouseX, mouseY) {\n\t\t\tvar me = this;\n\t\t\tif (!me._view) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvar inRange = false;\n\t\t\tvar bounds = getBarBounds(me);\n\n\t\t\tif (isVertical(me)) {\n\t\t\t\tinRange = mouseX >= bounds.left && mouseX <= bounds.right;\n\t\t\t} else {\n\t\t\t\tinRange = mouseY >= bounds.top && mouseY <= bounds.bottom;\n\t\t\t}\n\n\t\t\treturn inRange;\n\t\t},\n\t\tinXRange: function(mouseX) {\n\t\t\tvar bounds = getBarBounds(this);\n\t\t\treturn mouseX >= bounds.left && mouseX <= bounds.right;\n\t\t},\n\t\tinYRange: function(mouseY) {\n\t\t\tvar bounds = getBarBounds(this);\n\t\t\treturn mouseY >= bounds.top && mouseY <= bounds.bottom;\n\t\t},\n\t\tgetCenterPoint: function() {\n\t\t\tvar vm = this._view;\n\t\t\tvar x, y;\n\t\t\tif (isVertical(this)) {\n\t\t\t\tx = vm.x;\n\t\t\t\ty = (vm.y + vm.base) / 2;\n\t\t\t} else {\n\t\t\t\tx = (vm.x + vm.base) / 2;\n\t\t\t\ty = vm.y;\n\t\t\t}\n\n\t\t\treturn {x: x, y: y};\n\t\t},\n\t\tgetArea: function() {\n\t\t\tvar vm = this._view;\n\t\t\treturn vm.width * Math.abs(vm.y - vm.base);\n\t\t},\n\t\ttooltipPosition: function() {\n\t\t\tvar vm = this._view;\n\t\t\treturn {\n\t\t\t\tx: vm.x,\n\t\t\t\ty: vm.y\n\t\t\t};\n\t\t}\n\t});\n\n};\n\n},{}],41:[function(require,module,exports){\n'use strict';\n\n// Chart.Platform implementation for targeting a web browser\nmodule.exports = function(Chart) {\n\tvar helpers = Chart.helpers;\n\n\t// DOM event types -> Chart.js event types.\n\t// Note: only events with different types are mapped.\n\t// https://developer.mozilla.org/en-US/docs/Web/Events\n\tvar eventTypeMap = {\n\t\t// Touch events\n\t\ttouchstart: 'mousedown',\n\t\ttouchmove: 'mousemove',\n\t\ttouchend: 'mouseup',\n\n\t\t// Pointer events\n\t\tpointerenter: 'mouseenter',\n\t\tpointerdown: 'mousedown',\n\t\tpointermove: 'mousemove',\n\t\tpointerup: 'mouseup',\n\t\tpointerleave: 'mouseout',\n\t\tpointerout: 'mouseout'\n\t};\n\n\t/**\n\t * The \"used\" size is the final value of a dimension property after all calculations have\n\t * been performed. This method uses the computed style of `element` but returns undefined\n\t * if the computed style is not expressed in pixels. That can happen in some cases where\n\t * `element` has a size relative to its parent and this last one is not yet displayed,\n\t * for example because of `display: none` on a parent node.\n\t * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value\n\t * @returns {Number} Size in pixels or undefined if unknown.\n\t */\n\tfunction readUsedSize(element, property) {\n\t\tvar value = helpers.getStyle(element, property);\n\t\tvar matches = value && value.match(/(\\d+)px/);\n\t\treturn matches? Number(matches[1]) : undefined;\n\t}\n\n\t/**\n\t * Initializes the canvas style and render size without modifying the canvas display size,\n\t * since responsiveness is handled by the controller.resize() method. The config is used\n\t * to determine the aspect ratio to apply in case no explicit height has been specified.\n\t */\n\tfunction initCanvas(canvas, config) {\n\t\tvar style = canvas.style;\n\n\t\t// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it\n\t\t// returns null or '' if no explicit value has been set to the canvas attribute.\n\t\tvar renderHeight = canvas.getAttribute('height');\n\t\tvar renderWidth = canvas.getAttribute('width');\n\n\t\t// Chart.js modifies some canvas values that we want to restore on destroy\n\t\tcanvas._chartjs = {\n\t\t\tinitial: {\n\t\t\t\theight: renderHeight,\n\t\t\t\twidth: renderWidth,\n\t\t\t\tstyle: {\n\t\t\t\t\tdisplay: style.display,\n\t\t\t\t\theight: style.height,\n\t\t\t\t\twidth: style.width\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t// Force canvas to display as block to avoid extra space caused by inline\n\t\t// elements, which would interfere with the responsive resize process.\n\t\t// https://github.com/chartjs/Chart.js/issues/2538\n\t\tstyle.display = style.display || 'block';\n\n\t\tif (renderWidth === null || renderWidth === '') {\n\t\t\tvar displayWidth = readUsedSize(canvas, 'width');\n\t\t\tif (displayWidth !== undefined) {\n\t\t\t\tcanvas.width = displayWidth;\n\t\t\t}\n\t\t}\n\n\t\tif (renderHeight === null || renderHeight === '') {\n\t\t\tif (canvas.style.height === '') {\n\t\t\t\t// If no explicit render height and style height, let's apply the aspect ratio,\n\t\t\t\t// which one can be specified by the user but also by charts as default option\n\t\t\t\t// (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.\n\t\t\t\tcanvas.height = canvas.width / (config.options.aspectRatio || 2);\n\t\t\t} else {\n\t\t\t\tvar displayHeight = readUsedSize(canvas, 'height');\n\t\t\t\tif (displayWidth !== undefined) {\n\t\t\t\t\tcanvas.height = displayHeight;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn canvas;\n\t}\n\n\tfunction createEvent(type, chart, x, y, native) {\n\t\treturn {\n\t\t\ttype: type,\n\t\t\tchart: chart,\n\t\t\tnative: native || null,\n\t\t\tx: x !== undefined? x : null,\n\t\t\ty: y !== undefined? y : null,\n\t\t};\n\t}\n\n\tfunction fromNativeEvent(event, chart) {\n\t\tvar type = eventTypeMap[event.type] || event.type;\n\t\tvar pos = helpers.getRelativePosition(event, chart);\n\t\treturn createEvent(type, chart, pos.x, pos.y, event);\n\t}\n\n\tfunction createResizer(handler) {\n\t\tvar iframe = document.createElement('iframe');\n\t\tiframe.className = 'chartjs-hidden-iframe';\n\t\tiframe.style.cssText =\n\t\t\t'display:block;'+\n\t\t\t'overflow:hidden;'+\n\t\t\t'border:0;'+\n\t\t\t'margin:0;'+\n\t\t\t'top:0;'+\n\t\t\t'left:0;'+\n\t\t\t'bottom:0;'+\n\t\t\t'right:0;'+\n\t\t\t'height:100%;'+\n\t\t\t'width:100%;'+\n\t\t\t'position:absolute;'+\n\t\t\t'pointer-events:none;'+\n\t\t\t'z-index:-1;';\n\n\t\t// Prevent the iframe to gain focus on tab.\n\t\t// https://github.com/chartjs/Chart.js/issues/3090\n\t\tiframe.tabIndex = -1;\n\n\t\t// If the iframe is re-attached to the DOM, the resize listener is removed because the\n\t\t// content is reloaded, so make sure to install the handler after the iframe is loaded.\n\t\t// https://github.com/chartjs/Chart.js/issues/3521\n\t\thelpers.addEvent(iframe, 'load', function() {\n\t\t\thelpers.addEvent(iframe.contentWindow || iframe, 'resize', handler);\n\n\t\t\t// The iframe size might have changed while loading, which can also\n\t\t\t// happen if the size has been changed while detached from the DOM.\n\t\t\thandler();\n\t\t});\n\n\t\treturn iframe;\n\t}\n\n\tfunction addResizeListener(node, listener, chart) {\n\t\tvar stub = node._chartjs = {\n\t\t\tticking: false\n\t\t};\n\n\t\t// Throttle the callback notification until the next animation frame.\n\t\tvar notify = function() {\n\t\t\tif (!stub.ticking) {\n\t\t\t\tstub.ticking = true;\n\t\t\t\thelpers.requestAnimFrame.call(window, function() {\n\t\t\t\t\tif (stub.resizer) {\n\t\t\t\t\t\tstub.ticking = false;\n\t\t\t\t\t\treturn listener(createEvent('resize', chart));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\t// Let's keep track of this added iframe and thus avoid DOM query when removing it.\n\t\tstub.resizer = createResizer(notify);\n\n\t\tnode.insertBefore(stub.resizer, node.firstChild);\n\t}\n\n\tfunction removeResizeListener(node) {\n\t\tif (!node || !node._chartjs) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar resizer = node._chartjs.resizer;\n\t\tif (resizer) {\n\t\t\tresizer.parentNode.removeChild(resizer);\n\t\t\tnode._chartjs.resizer = null;\n\t\t}\n\n\t\tdelete node._chartjs;\n\t}\n\n\treturn {\n\t\tacquireContext: function(item, config) {\n\t\t\tif (typeof item === 'string') {\n\t\t\t\titem = document.getElementById(item);\n\t\t\t} else if (item.length) {\n\t\t\t\t// Support for array based queries (such as jQuery)\n\t\t\t\titem = item[0];\n\t\t\t}\n\n\t\t\tif (item && item.canvas) {\n\t\t\t\t// Support for any object associated to a canvas (including a context2d)\n\t\t\t\titem = item.canvas;\n\t\t\t}\n\n\t\t\tif (item instanceof HTMLCanvasElement) {\n\t\t\t\t// To prevent canvas fingerprinting, some add-ons undefine the getContext\n\t\t\t\t// method, for example: https://github.com/kkapsner/CanvasBlocker\n\t\t\t\t// https://github.com/chartjs/Chart.js/issues/2807\n\t\t\t\tvar context = item.getContext && item.getContext('2d');\n\t\t\t\tif (context instanceof CanvasRenderingContext2D) {\n\t\t\t\t\tinitCanvas(item, config);\n\t\t\t\t\treturn context;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\n\t\treleaseContext: function(context) {\n\t\t\tvar canvas = context.canvas;\n\t\t\tif (!canvas._chartjs) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar initial = canvas._chartjs.initial;\n\t\t\t['height', 'width'].forEach(function(prop) {\n\t\t\t\tvar value = initial[prop];\n\t\t\t\tif (value === undefined || value === null) {\n\t\t\t\t\tcanvas.removeAttribute(prop);\n\t\t\t\t} else {\n\t\t\t\t\tcanvas.setAttribute(prop, value);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\thelpers.each(initial.style || {}, function(value, key) {\n\t\t\t\tcanvas.style[key] = value;\n\t\t\t});\n\n\t\t\t// The canvas render size might have been changed (and thus the state stack discarded),\n\t\t\t// we can't use save() and restore() to restore the initial state. So make sure that at\n\t\t\t// least the canvas context is reset to the default state by setting the canvas width.\n\t\t\t// https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html\n\t\t\tcanvas.width = canvas.width;\n\n\t\t\tdelete canvas._chartjs;\n\t\t},\n\n\t\taddEventListener: function(chart, type, listener) {\n\t\t\tvar canvas = chart.chart.canvas;\n\t\t\tif (type === 'resize') {\n\t\t\t\t// Note: the resize event is not supported on all browsers.\n\t\t\t\taddResizeListener(canvas.parentNode, listener, chart.chart);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar stub = listener._chartjs || (listener._chartjs = {});\n\t\t\tvar proxies = stub.proxies || (stub.proxies = {});\n\t\t\tvar proxy = proxies[chart.id + '_' + type] = function(event) {\n\t\t\t\tlistener(fromNativeEvent(event, chart.chart));\n\t\t\t};\n\n\t\t\thelpers.addEvent(canvas, type, proxy);\n\t\t},\n\n\t\tremoveEventListener: function(chart, type, listener) {\n\t\t\tvar canvas = chart.chart.canvas;\n\t\t\tif (type === 'resize') {\n\t\t\t\t// Note: the resize event is not supported on all browsers.\n\t\t\t\tremoveResizeListener(canvas.parentNode, listener);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar stub = listener._chartjs || {};\n\t\t\tvar proxies = stub.proxies || {};\n\t\t\tvar proxy = proxies[chart.id + '_' + type];\n\t\t\tif (!proxy) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\thelpers.removeEvent(canvas, type, proxy);\n\t\t}\n\t};\n};\n\n},{}],42:[function(require,module,exports){\n'use strict';\n\n// By default, select the browser (DOM) platform.\n// @TODO Make possible to select another platform at build time.\nvar implementation = require(41);\n\nmodule.exports = function(Chart) {\n\t/**\n\t * @namespace Chart.platform\n\t * @see https://chartjs.gitbooks.io/proposals/content/Platform.html\n\t * @since 2.4.0\n\t */\n\tChart.platform = {\n\t\t/**\n\t\t * Called at chart construction time, returns a context2d instance implementing\n\t\t * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.\n\t\t * @param {*} item - The native item from which to acquire context (platform specific)\n\t\t * @param {Object} options - The chart options\n\t\t * @returns {CanvasRenderingContext2D} context2d instance\n\t\t */\n\t\tacquireContext: function() {},\n\n\t\t/**\n\t\t * Called at chart destruction time, releases any resources associated to the context\n\t\t * previously returned by the acquireContext() method.\n\t\t * @param {CanvasRenderingContext2D} context - The context2d instance\n\t\t * @returns {Boolean} true if the method succeeded, else false\n\t\t */\n\t\treleaseContext: function() {},\n\n\t\t/**\n\t\t * Registers the specified listener on the given chart.\n\t\t * @param {Chart} chart - Chart from which to listen for event\n\t\t * @param {String} type - The ({@link IEvent}) type to listen for\n\t\t * @param {Function} listener - Receives a notification (an object that implements\n\t\t * the {@link IEvent} interface) when an event of the specified type occurs.\n\t\t */\n\t\taddEventListener: function() {},\n\n\t\t/**\n\t\t * Removes the specified listener previously registered with addEventListener.\n\t\t * @param {Chart} chart -Chart from which to remove the listener\n\t\t * @param {String} type - The ({@link IEvent}) type to remove\n\t\t * @param {Function} listener - The listener function to remove from the event target.\n\t\t */\n\t\tremoveEventListener: function() {}\n\t};\n\n\t/**\n\t * @interface IPlatform\n\t * Allows abstracting platform dependencies away from the chart\n\t * @borrows Chart.platform.acquireContext as acquireContext\n\t * @borrows Chart.platform.releaseContext as releaseContext\n\t * @borrows Chart.platform.addEventListener as addEventListener\n\t * @borrows Chart.platform.removeEventListener as removeEventListener\n\t */\n\n\t/**\n\t * @interface IEvent\n\t * @prop {String} type - The event type name, possible values are:\n\t * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',\n\t * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'\n\t * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')\n\t * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)\n\t * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)\n\t */\n\n\tChart.helpers.extend(Chart.platform, implementation(Chart));\n};\n\n},{\"41\":41}],43:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\t// Default config for a category scale\n\tvar defaultConfig = {\n\t\tposition: 'bottom'\n\t};\n\n\tvar DatasetScale = Chart.Scale.extend({\n\t\t/**\n\t\t* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those\n\t\t* else fall back to data.labels\n\t\t* @private\n\t\t*/\n\t\tgetLabels: function() {\n\t\t\tvar data = this.chart.data;\n\t\t\treturn (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;\n\t\t},\n\t\t// Implement this so that\n\t\tdetermineDataLimits: function() {\n\t\t\tvar me = this;\n\t\t\tvar labels = me.getLabels();\n\t\t\tme.minIndex = 0;\n\t\t\tme.maxIndex = labels.length - 1;\n\t\t\tvar findIndex;\n\n\t\t\tif (me.options.ticks.min !== undefined) {\n\t\t\t\t// user specified min value\n\t\t\t\tfindIndex = helpers.indexOf(labels, me.options.ticks.min);\n\t\t\t\tme.minIndex = findIndex !== -1 ? findIndex : me.minIndex;\n\t\t\t}\n\n\t\t\tif (me.options.ticks.max !== undefined) {\n\t\t\t\t// user specified max value\n\t\t\t\tfindIndex = helpers.indexOf(labels, me.options.ticks.max);\n\t\t\t\tme.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;\n\t\t\t}\n\n\t\t\tme.min = labels[me.minIndex];\n\t\t\tme.max = labels[me.maxIndex];\n\t\t},\n\n\t\tbuildTicks: function() {\n\t\t\tvar me = this;\n\t\t\tvar labels = me.getLabels();\n\t\t\t// If we are viewing some subset of labels, slice the original array\n\t\t\tme.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);\n\t\t},\n\n\t\tgetLabelForIndex: function(index, datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar data = me.chart.data;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\n\t\t\tif (data.yLabels && !isHorizontal) {\n\t\t\t\treturn me.getRightValue(data.datasets[datasetIndex].data[index]);\n\t\t\t}\n\t\t\treturn me.ticks[index - me.minIndex];\n\t\t},\n\n\t\t// Used to get data value locations. Value can either be an index or a numerical value\n\t\tgetPixelForValue: function(value, index, datasetIndex, includeOffset) {\n\t\t\tvar me = this;\n\t\t\t// 1 is added because we need the length but we have the indexes\n\t\t\tvar offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);\n\n\t\t\tif (value !== undefined && isNaN(index)) {\n\t\t\t\tvar labels = me.getLabels();\n\t\t\t\tvar idx = labels.indexOf(value);\n\t\t\t\tindex = idx !== -1 ? idx : index;\n\t\t\t}\n\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tvar valueWidth = me.width / offsetAmt;\n\t\t\t\tvar widthOffset = (valueWidth * (index - me.minIndex));\n\n\t\t\t\tif (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {\n\t\t\t\t\twidthOffset += (valueWidth / 2);\n\t\t\t\t}\n\n\t\t\t\treturn me.left + Math.round(widthOffset);\n\t\t\t}\n\t\t\tvar valueHeight = me.height / offsetAmt;\n\t\t\tvar heightOffset = (valueHeight * (index - me.minIndex));\n\n\t\t\tif (me.options.gridLines.offsetGridLines && includeOffset) {\n\t\t\t\theightOffset += (valueHeight / 2);\n\t\t\t}\n\n\t\t\treturn me.top + Math.round(heightOffset);\n\t\t},\n\t\tgetPixelForTick: function(index, includeOffset) {\n\t\t\treturn this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);\n\t\t},\n\t\tgetValueForPixel: function(pixel) {\n\t\t\tvar me = this;\n\t\t\tvar value;\n\t\t\tvar offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);\n\t\t\tvar horz = me.isHorizontal();\n\t\t\tvar valueDimension = (horz ? me.width : me.height) / offsetAmt;\n\n\t\t\tpixel -= horz ? me.left : me.top;\n\n\t\t\tif (me.options.gridLines.offsetGridLines) {\n\t\t\t\tpixel -= (valueDimension / 2);\n\t\t\t}\n\n\t\t\tif (pixel <= 0) {\n\t\t\t\tvalue = 0;\n\t\t\t} else {\n\t\t\t\tvalue = Math.round(pixel / valueDimension);\n\t\t\t}\n\n\t\t\treturn value;\n\t\t},\n\t\tgetBasePixel: function() {\n\t\t\treturn this.bottom;\n\t\t}\n\t});\n\n\tChart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);\n\n};\n\n},{}],44:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tvar defaultConfig = {\n\t\tposition: 'left',\n\t\tticks: {\n\t\t\tcallback: Chart.Ticks.formatters.linear\n\t\t}\n\t};\n\n\tvar LinearScale = Chart.LinearScaleBase.extend({\n\t\tdetermineDataLimits: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar chart = me.chart;\n\t\t\tvar data = chart.data;\n\t\t\tvar datasets = data.datasets;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\n\t\t\tfunction IDMatches(meta) {\n\t\t\t\treturn isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;\n\t\t\t}\n\n\t\t\t// First Calculate the range\n\t\t\tme.min = null;\n\t\t\tme.max = null;\n\n\t\t\tvar hasStacks = opts.stacked;\n\t\t\tif (hasStacks === undefined) {\n\t\t\t\thelpers.each(datasets, function(dataset, datasetIndex) {\n\t\t\t\t\tif (hasStacks) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\t\t\t\t\tif (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&\n\t\t\t\t\t\tmeta.stack !== undefined) {\n\t\t\t\t\t\thasStacks = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (opts.stacked || hasStacks) {\n\t\t\t\tvar valuesPerStack = {};\n\n\t\t\t\thelpers.each(datasets, function(dataset, datasetIndex) {\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\t\t\t\t\tvar key = [\n\t\t\t\t\t\tmeta.type,\n\t\t\t\t\t\t// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined\n\t\t\t\t\t\t((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),\n\t\t\t\t\t\tmeta.stack\n\t\t\t\t\t].join('.');\n\n\t\t\t\t\tif (valuesPerStack[key] === undefined) {\n\t\t\t\t\t\tvaluesPerStack[key] = {\n\t\t\t\t\t\t\tpositiveValues: [],\n\t\t\t\t\t\t\tnegativeValues: []\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Store these per type\n\t\t\t\t\tvar positiveValues = valuesPerStack[key].positiveValues;\n\t\t\t\t\tvar negativeValues = valuesPerStack[key].negativeValues;\n\n\t\t\t\t\tif (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {\n\t\t\t\t\t\thelpers.each(dataset.data, function(rawValue, index) {\n\t\t\t\t\t\t\tvar value = +me.getRightValue(rawValue);\n\t\t\t\t\t\t\tif (isNaN(value) || meta.data[index].hidden) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tpositiveValues[index] = positiveValues[index] || 0;\n\t\t\t\t\t\t\tnegativeValues[index] = negativeValues[index] || 0;\n\n\t\t\t\t\t\t\tif (opts.relativePoints) {\n\t\t\t\t\t\t\t\tpositiveValues[index] = 100;\n\t\t\t\t\t\t\t} else if (value < 0) {\n\t\t\t\t\t\t\t\tnegativeValues[index] += value;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpositiveValues[index] += value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\thelpers.each(valuesPerStack, function(valuesForType) {\n\t\t\t\t\tvar values = valuesForType.positiveValues.concat(valuesForType.negativeValues);\n\t\t\t\t\tvar minVal = helpers.min(values);\n\t\t\t\t\tvar maxVal = helpers.max(values);\n\t\t\t\t\tme.min = me.min === null ? minVal : Math.min(me.min, minVal);\n\t\t\t\t\tme.max = me.max === null ? maxVal : Math.max(me.max, maxVal);\n\t\t\t\t});\n\n\t\t\t} else {\n\t\t\t\thelpers.each(datasets, function(dataset, datasetIndex) {\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\t\t\t\t\tif (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {\n\t\t\t\t\t\thelpers.each(dataset.data, function(rawValue, index) {\n\t\t\t\t\t\t\tvar value = +me.getRightValue(rawValue);\n\t\t\t\t\t\t\tif (isNaN(value) || meta.data[index].hidden) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (me.min === null) {\n\t\t\t\t\t\t\t\tme.min = value;\n\t\t\t\t\t\t\t} else if (value < me.min) {\n\t\t\t\t\t\t\t\tme.min = value;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (me.max === null) {\n\t\t\t\t\t\t\t\tme.max = value;\n\t\t\t\t\t\t\t} else if (value > me.max) {\n\t\t\t\t\t\t\t\tme.max = value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero\n\t\t\tthis.handleTickRangeOptions();\n\t\t},\n\t\tgetTickLimit: function() {\n\t\t\tvar maxTicks;\n\t\t\tvar me = this;\n\t\t\tvar tickOpts = me.options.ticks;\n\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tmaxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));\n\t\t\t} else {\n\t\t\t\t// The factor of 2 used to scale the font size has been experimentally determined.\n\t\t\t\tvar tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize);\n\t\t\t\tmaxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));\n\t\t\t}\n\n\t\t\treturn maxTicks;\n\t\t},\n\t\t// Called after the ticks are built. We need\n\t\thandleDirectionalChanges: function() {\n\t\t\tif (!this.isHorizontal()) {\n\t\t\t\t// We are in a vertical orientation. The top value is the highest. So reverse the array\n\t\t\t\tthis.ticks.reverse();\n\t\t\t}\n\t\t},\n\t\tgetLabelForIndex: function(index, datasetIndex) {\n\t\t\treturn +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);\n\t\t},\n\t\t// Utils\n\t\tgetPixelForValue: function(value) {\n\t\t\t// This must be called after fit has been run so that\n\t\t\t// this.left, this.top, this.right, and this.bottom have been defined\n\t\t\tvar me = this;\n\t\t\tvar start = me.start;\n\n\t\t\tvar rightValue = +me.getRightValue(value);\n\t\t\tvar pixel;\n\t\t\tvar range = me.end - start;\n\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tpixel = me.left + (me.width / range * (rightValue - start));\n\t\t\t\treturn Math.round(pixel);\n\t\t\t}\n\n\t\t\tpixel = me.bottom - (me.height / range * (rightValue - start));\n\t\t\treturn Math.round(pixel);\n\t\t},\n\t\tgetValueForPixel: function(pixel) {\n\t\t\tvar me = this;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\t\t\tvar innerDimension = isHorizontal ? me.width : me.height;\n\t\t\tvar offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;\n\t\t\treturn me.start + ((me.end - me.start) * offset);\n\t\t},\n\t\tgetPixelForTick: function(index) {\n\t\t\treturn this.getPixelForValue(this.ticksAsNumbers[index]);\n\t\t}\n\t});\n\tChart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);\n\n};\n\n},{}],45:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers,\n\t\tnoop = helpers.noop;\n\n\tChart.LinearScaleBase = Chart.Scale.extend({\n\t\thandleTickRangeOptions: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\n\t\t\t// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,\n\t\t\t// do nothing since that would make the chart weird. If the user really wants a weird chart\n\t\t\t// axis, they can manually override it\n\t\t\tif (tickOpts.beginAtZero) {\n\t\t\t\tvar minSign = helpers.sign(me.min);\n\t\t\t\tvar maxSign = helpers.sign(me.max);\n\n\t\t\t\tif (minSign < 0 && maxSign < 0) {\n\t\t\t\t\t// move the top up to 0\n\t\t\t\t\tme.max = 0;\n\t\t\t\t} else if (minSign > 0 && maxSign > 0) {\n\t\t\t\t\t// move the bottom down to 0\n\t\t\t\t\tme.min = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tickOpts.min !== undefined) {\n\t\t\t\tme.min = tickOpts.min;\n\t\t\t} else if (tickOpts.suggestedMin !== undefined) {\n\t\t\t\tme.min = Math.min(me.min, tickOpts.suggestedMin);\n\t\t\t}\n\n\t\t\tif (tickOpts.max !== undefined) {\n\t\t\t\tme.max = tickOpts.max;\n\t\t\t} else if (tickOpts.suggestedMax !== undefined) {\n\t\t\t\tme.max = Math.max(me.max, tickOpts.suggestedMax);\n\t\t\t}\n\n\t\t\tif (me.min === me.max) {\n\t\t\t\tme.max++;\n\n\t\t\t\tif (!tickOpts.beginAtZero) {\n\t\t\t\t\tme.min--;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tgetTickLimit: noop,\n\t\thandleDirectionalChanges: noop,\n\n\t\tbuildTicks: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\n\t\t\t// Figure out what the max number of ticks we can support it is based on the size of\n\t\t\t// the axis area. For now, we say that the minimum tick spacing in pixels must be 50\n\t\t\t// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on\n\t\t\t// the graph. Make sure we always have at least 2 ticks\n\t\t\tvar maxTicks = me.getTickLimit();\n\t\t\tmaxTicks = Math.max(2, maxTicks);\n\n\t\t\tvar numericGeneratorOptions = {\n\t\t\t\tmaxTicks: maxTicks,\n\t\t\t\tmin: tickOpts.min,\n\t\t\t\tmax: tickOpts.max,\n\t\t\t\tstepSize: helpers.getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)\n\t\t\t};\n\t\t\tvar ticks = me.ticks = Chart.Ticks.generators.linear(numericGeneratorOptions, me);\n\n\t\t\tme.handleDirectionalChanges();\n\n\t\t\t// At this point, we need to update our max and min given the tick values since we have expanded the\n\t\t\t// range of the scale\n\t\t\tme.max = helpers.max(ticks);\n\t\t\tme.min = helpers.min(ticks);\n\n\t\t\tif (tickOpts.reverse) {\n\t\t\t\tticks.reverse();\n\n\t\t\t\tme.start = me.max;\n\t\t\t\tme.end = me.min;\n\t\t\t} else {\n\t\t\t\tme.start = me.min;\n\t\t\t\tme.end = me.max;\n\t\t\t}\n\t\t},\n\t\tconvertTicksToLabels: function() {\n\t\t\tvar me = this;\n\t\t\tme.ticksAsNumbers = me.ticks.slice();\n\t\t\tme.zeroLineIndex = me.ticks.indexOf(0);\n\n\t\t\tChart.Scale.prototype.convertTicksToLabels.call(me);\n\t\t}\n\t});\n};\n\n},{}],46:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\n\tvar defaultConfig = {\n\t\tposition: 'left',\n\n\t\t// label settings\n\t\tticks: {\n\t\t\tcallback: Chart.Ticks.formatters.logarithmic\n\t\t}\n\t};\n\n\tvar LogarithmicScale = Chart.Scale.extend({\n\t\tdetermineDataLimits: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\t\t\tvar chart = me.chart;\n\t\t\tvar data = chart.data;\n\t\t\tvar datasets = data.datasets;\n\t\t\tvar getValueOrDefault = helpers.getValueOrDefault;\n\t\t\tvar isHorizontal = me.isHorizontal();\n\t\t\tfunction IDMatches(meta) {\n\t\t\t\treturn isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;\n\t\t\t}\n\n\t\t\t// Calculate Range\n\t\t\tme.min = null;\n\t\t\tme.max = null;\n\t\t\tme.minNotZero = null;\n\n\t\t\tvar hasStacks = opts.stacked;\n\t\t\tif (hasStacks === undefined) {\n\t\t\t\thelpers.each(datasets, function(dataset, datasetIndex) {\n\t\t\t\t\tif (hasStacks) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\t\t\t\t\tif (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&\n\t\t\t\t\t\tmeta.stack !== undefined) {\n\t\t\t\t\t\thasStacks = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (opts.stacked || hasStacks) {\n\t\t\t\tvar valuesPerStack = {};\n\n\t\t\t\thelpers.each(datasets, function(dataset, datasetIndex) {\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\t\t\t\t\tvar key = [\n\t\t\t\t\t\tmeta.type,\n\t\t\t\t\t\t// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined\n\t\t\t\t\t\t((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),\n\t\t\t\t\t\tmeta.stack\n\t\t\t\t\t].join('.');\n\n\t\t\t\t\tif (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {\n\t\t\t\t\t\tif (valuesPerStack[key] === undefined) {\n\t\t\t\t\t\t\tvaluesPerStack[key] = [];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\thelpers.each(dataset.data, function(rawValue, index) {\n\t\t\t\t\t\t\tvar values = valuesPerStack[key];\n\t\t\t\t\t\t\tvar value = +me.getRightValue(rawValue);\n\t\t\t\t\t\t\tif (isNaN(value) || meta.data[index].hidden) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tvalues[index] = values[index] || 0;\n\n\t\t\t\t\t\t\tif (opts.relativePoints) {\n\t\t\t\t\t\t\t\tvalues[index] = 100;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Don't need to split positive and negative since the log scale can't handle a 0 crossing\n\t\t\t\t\t\t\t\tvalues[index] += value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\thelpers.each(valuesPerStack, function(valuesForType) {\n\t\t\t\t\tvar minVal = helpers.min(valuesForType);\n\t\t\t\t\tvar maxVal = helpers.max(valuesForType);\n\t\t\t\t\tme.min = me.min === null ? minVal : Math.min(me.min, minVal);\n\t\t\t\t\tme.max = me.max === null ? maxVal : Math.max(me.max, maxVal);\n\t\t\t\t});\n\n\t\t\t} else {\n\t\t\t\thelpers.each(datasets, function(dataset, datasetIndex) {\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\t\t\t\t\tif (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {\n\t\t\t\t\t\thelpers.each(dataset.data, function(rawValue, index) {\n\t\t\t\t\t\t\tvar value = +me.getRightValue(rawValue);\n\t\t\t\t\t\t\tif (isNaN(value) || meta.data[index].hidden) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (me.min === null) {\n\t\t\t\t\t\t\t\tme.min = value;\n\t\t\t\t\t\t\t} else if (value < me.min) {\n\t\t\t\t\t\t\t\tme.min = value;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (me.max === null) {\n\t\t\t\t\t\t\t\tme.max = value;\n\t\t\t\t\t\t\t} else if (value > me.max) {\n\t\t\t\t\t\t\t\tme.max = value;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {\n\t\t\t\t\t\t\t\tme.minNotZero = value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tme.min = getValueOrDefault(tickOpts.min, me.min);\n\t\t\tme.max = getValueOrDefault(tickOpts.max, me.max);\n\n\t\t\tif (me.min === me.max) {\n\t\t\t\tif (me.min !== 0 && me.min !== null) {\n\t\t\t\t\tme.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);\n\t\t\t\t\tme.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);\n\t\t\t\t} else {\n\t\t\t\t\tme.min = 1;\n\t\t\t\t\tme.max = 10;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tbuildTicks: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\n\t\t\tvar generationOptions = {\n\t\t\t\tmin: tickOpts.min,\n\t\t\t\tmax: tickOpts.max\n\t\t\t};\n\t\t\tvar ticks = me.ticks = Chart.Ticks.generators.logarithmic(generationOptions, me);\n\n\t\t\tif (!me.isHorizontal()) {\n\t\t\t\t// We are in a vertical orientation. The top value is the highest. So reverse the array\n\t\t\t\tticks.reverse();\n\t\t\t}\n\n\t\t\t// At this point, we need to update our max and min given the tick values since we have expanded the\n\t\t\t// range of the scale\n\t\t\tme.max = helpers.max(ticks);\n\t\t\tme.min = helpers.min(ticks);\n\n\t\t\tif (tickOpts.reverse) {\n\t\t\t\tticks.reverse();\n\n\t\t\t\tme.start = me.max;\n\t\t\t\tme.end = me.min;\n\t\t\t} else {\n\t\t\t\tme.start = me.min;\n\t\t\t\tme.end = me.max;\n\t\t\t}\n\t\t},\n\t\tconvertTicksToLabels: function() {\n\t\t\tthis.tickValues = this.ticks.slice();\n\n\t\t\tChart.Scale.prototype.convertTicksToLabels.call(this);\n\t\t},\n\t\t// Get the correct tooltip label\n\t\tgetLabelForIndex: function(index, datasetIndex) {\n\t\t\treturn +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);\n\t\t},\n\t\tgetPixelForTick: function(index) {\n\t\t\treturn this.getPixelForValue(this.tickValues[index]);\n\t\t},\n\t\tgetPixelForValue: function(value) {\n\t\t\tvar me = this;\n\t\t\tvar innerDimension;\n\t\t\tvar pixel;\n\n\t\t\tvar start = me.start;\n\t\t\tvar newVal = +me.getRightValue(value);\n\t\t\tvar range;\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\trange = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0\n\t\t\t\tif (newVal === 0) {\n\t\t\t\t\tpixel = me.left;\n\t\t\t\t} else {\n\t\t\t\t\tinnerDimension = me.width;\n\t\t\t\t\tpixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Bottom - top since pixels increase downward on a screen\n\t\t\t\tinnerDimension = me.height;\n\t\t\t\tif (start === 0 && !tickOpts.reverse) {\n\t\t\t\t\trange = helpers.log10(me.end) - helpers.log10(me.minNotZero);\n\t\t\t\t\tif (newVal === start) {\n\t\t\t\t\t\tpixel = me.bottom;\n\t\t\t\t\t} else if (newVal === me.minNotZero) {\n\t\t\t\t\t\tpixel = me.bottom - innerDimension * 0.02;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));\n\t\t\t\t\t}\n\t\t\t\t} else if (me.end === 0 && tickOpts.reverse) {\n\t\t\t\t\trange = helpers.log10(me.start) - helpers.log10(me.minNotZero);\n\t\t\t\t\tif (newVal === me.end) {\n\t\t\t\t\t\tpixel = me.top;\n\t\t\t\t\t} else if (newVal === me.minNotZero) {\n\t\t\t\t\t\tpixel = me.top + innerDimension * 0.02;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\trange = helpers.log10(me.end) - helpers.log10(start);\n\t\t\t\t\tinnerDimension = me.height;\n\t\t\t\t\tpixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn pixel;\n\t\t},\n\t\tgetValueForPixel: function(pixel) {\n\t\t\tvar me = this;\n\t\t\tvar range = helpers.log10(me.end) - helpers.log10(me.start);\n\t\t\tvar value, innerDimension;\n\n\t\t\tif (me.isHorizontal()) {\n\t\t\t\tinnerDimension = me.width;\n\t\t\t\tvalue = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);\n\t\t\t} else { // todo: if start === 0\n\t\t\t\tinnerDimension = me.height;\n\t\t\t\tvalue = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\t});\n\tChart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);\n\n};\n\n},{}],47:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\tvar globalDefaults = Chart.defaults.global;\n\n\tvar defaultConfig = {\n\t\tdisplay: true,\n\n\t\t// Boolean - Whether to animate scaling the chart from the centre\n\t\tanimate: true,\n\t\tlineArc: false,\n\t\tposition: 'chartArea',\n\n\t\tangleLines: {\n\t\t\tdisplay: true,\n\t\t\tcolor: 'rgba(0, 0, 0, 0.1)',\n\t\t\tlineWidth: 1\n\t\t},\n\n\t\t// label settings\n\t\tticks: {\n\t\t\t// Boolean - Show a backdrop to the scale label\n\t\t\tshowLabelBackdrop: true,\n\n\t\t\t// String - The colour of the label backdrop\n\t\t\tbackdropColor: 'rgba(255,255,255,0.75)',\n\n\t\t\t// Number - The backdrop padding above & below the label in pixels\n\t\t\tbackdropPaddingY: 2,\n\n\t\t\t// Number - The backdrop padding to the side of the label in pixels\n\t\t\tbackdropPaddingX: 2,\n\n\t\t\tcallback: Chart.Ticks.formatters.linear\n\t\t},\n\n\t\tpointLabels: {\n\t\t\t// Number - Point label font size in pixels\n\t\t\tfontSize: 10,\n\n\t\t\t// Function - Used to convert point labels\n\t\t\tcallback: function(label) {\n\t\t\t\treturn label;\n\t\t\t}\n\t\t}\n\t};\n\n\tfunction getValueCount(scale) {\n\t\treturn !scale.options.lineArc ? scale.chart.data.labels.length : 0;\n\t}\n\n\tfunction getPointLabelFontOptions(scale) {\n\t\tvar pointLabelOptions = scale.options.pointLabels;\n\t\tvar fontSize = helpers.getValueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);\n\t\tvar fontStyle = helpers.getValueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);\n\t\tvar fontFamily = helpers.getValueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);\n\t\tvar font = helpers.fontString(fontSize, fontStyle, fontFamily);\n\n\t\treturn {\n\t\t\tsize: fontSize,\n\t\t\tstyle: fontStyle,\n\t\t\tfamily: fontFamily,\n\t\t\tfont: font\n\t\t};\n\t}\n\n\tfunction measureLabelSize(ctx, fontSize, label) {\n\t\tif (helpers.isArray(label)) {\n\t\t\treturn {\n\t\t\t\tw: helpers.longestText(ctx, ctx.font, label),\n\t\t\t\th: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tw: ctx.measureText(label).width,\n\t\t\th: fontSize\n\t\t};\n\t}\n\n\tfunction determineLimits(angle, pos, size, min, max) {\n\t\tif (angle === min || angle === max) {\n\t\t\treturn {\n\t\t\t\tstart: pos - (size / 2),\n\t\t\t\tend: pos + (size / 2)\n\t\t\t};\n\t\t} else if (angle < min || angle > max) {\n\t\t\treturn {\n\t\t\t\tstart: pos - size - 5,\n\t\t\t\tend: pos\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tstart: pos,\n\t\t\tend: pos + size + 5\n\t\t};\n\t}\n\n\t/**\n\t * Helper function to fit a radial linear scale with point labels\n\t */\n\tfunction fitWithPointLabels(scale) {\n\t\t/*\n\t\t * Right, this is really confusing and there is a lot of maths going on here\n\t\t * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9\n\t\t *\n\t\t * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif\n\t\t *\n\t\t * Solution:\n\t\t *\n\t\t * We assume the radius of the polygon is half the size of the canvas at first\n\t\t * at each index we check if the text overlaps.\n\t\t *\n\t\t * Where it does, we store that angle and that index.\n\t\t *\n\t\t * After finding the largest index and angle we calculate how much we need to remove\n\t\t * from the shape radius to move the point inwards by that x.\n\t\t *\n\t\t * We average the left and right distances to get the maximum shape radius that can fit in the box\n\t\t * along with labels.\n\t\t *\n\t\t * Once we have that, we can find the centre point for the chart, by taking the x text protrusion\n\t\t * on each side, removing that from the size, halving it and adding the left x protrusion width.\n\t\t *\n\t\t * This will mean we have a shape fitted to the canvas, as large as it can be with the labels\n\t\t * and position it in the most space efficient manner\n\t\t *\n\t\t * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif\n\t\t */\n\n\t\tvar plFont = getPointLabelFontOptions(scale);\n\n\t\t// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.\n\t\t// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points\n\t\tvar largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);\n\t\tvar furthestLimits = {\n\t\t\tl: scale.width,\n\t\t\tr: 0,\n\t\t\tt: scale.height,\n\t\t\tb: 0\n\t\t};\n\t\tvar furthestAngles = {};\n\t\tvar i;\n\t\tvar textSize;\n\t\tvar pointPosition;\n\n\t\tscale.ctx.font = plFont.font;\n\t\tscale._pointLabelSizes = [];\n\n\t\tvar valueCount = getValueCount(scale);\n\t\tfor (i = 0; i < valueCount; i++) {\n\t\t\tpointPosition = scale.getPointPosition(i, largestPossibleRadius);\n\t\t\ttextSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');\n\t\t\tscale._pointLabelSizes[i] = textSize;\n\n\t\t\t// Add quarter circle to make degree 0 mean top of circle\n\t\t\tvar angleRadians = scale.getIndexAngle(i);\n\t\t\tvar angle = helpers.toDegrees(angleRadians) % 360;\n\t\t\tvar hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);\n\t\t\tvar vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);\n\n\t\t\tif (hLimits.start < furthestLimits.l) {\n\t\t\t\tfurthestLimits.l = hLimits.start;\n\t\t\t\tfurthestAngles.l = angleRadians;\n\t\t\t}\n\n\t\t\tif (hLimits.end > furthestLimits.r) {\n\t\t\t\tfurthestLimits.r = hLimits.end;\n\t\t\t\tfurthestAngles.r = angleRadians;\n\t\t\t}\n\n\t\t\tif (vLimits.start < furthestLimits.t) {\n\t\t\t\tfurthestLimits.t = vLimits.start;\n\t\t\t\tfurthestAngles.t = angleRadians;\n\t\t\t}\n\n\t\t\tif (vLimits.end > furthestLimits.b) {\n\t\t\t\tfurthestLimits.b = vLimits.end;\n\t\t\t\tfurthestAngles.b = angleRadians;\n\t\t\t}\n\t\t}\n\n\t\tscale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);\n\t}\n\n\t/**\n\t * Helper function to fit a radial linear scale with no point labels\n\t */\n\tfunction fit(scale) {\n\t\tvar largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);\n\t\tscale.drawingArea = Math.round(largestPossibleRadius);\n\t\tscale.setCenterPoint(0, 0, 0, 0);\n\t}\n\n\tfunction getTextAlignForAngle(angle) {\n\t\tif (angle === 0 || angle === 180) {\n\t\t\treturn 'center';\n\t\t} else if (angle < 180) {\n\t\t\treturn 'left';\n\t\t}\n\n\t\treturn 'right';\n\t}\n\n\tfunction fillText(ctx, text, position, fontSize) {\n\t\tif (helpers.isArray(text)) {\n\t\t\tvar y = position.y;\n\t\t\tvar spacing = 1.5 * fontSize;\n\n\t\t\tfor (var i = 0; i < text.length; ++i) {\n\t\t\t\tctx.fillText(text[i], position.x, y);\n\t\t\t\ty+= spacing;\n\t\t\t}\n\t\t} else {\n\t\t\tctx.fillText(text, position.x, position.y);\n\t\t}\n\t}\n\n\tfunction adjustPointPositionForLabelHeight(angle, textSize, position) {\n\t\tif (angle === 90 || angle === 270) {\n\t\t\tposition.y -= (textSize.h / 2);\n\t\t} else if (angle > 270 || angle < 90) {\n\t\t\tposition.y -= textSize.h;\n\t\t}\n\t}\n\n\tfunction drawPointLabels(scale) {\n\t\tvar ctx = scale.ctx;\n\t\tvar getValueOrDefault = helpers.getValueOrDefault;\n\t\tvar opts = scale.options;\n\t\tvar angleLineOpts = opts.angleLines;\n\t\tvar pointLabelOpts = opts.pointLabels;\n\n\t\tctx.lineWidth = angleLineOpts.lineWidth;\n\t\tctx.strokeStyle = angleLineOpts.color;\n\n\t\tvar outerDistance = scale.getDistanceFromCenterForValue(opts.reverse ? scale.min : scale.max);\n\n\t\t// Point Label Font\n\t\tvar plFont = getPointLabelFontOptions(scale);\n\n\t\tctx.textBaseline = 'top';\n\n\t\tfor (var i = getValueCount(scale) - 1; i >= 0; i--) {\n\t\t\tif (angleLineOpts.display) {\n\t\t\t\tvar outerPosition = scale.getPointPosition(i, outerDistance);\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(scale.xCenter, scale.yCenter);\n\t\t\t\tctx.lineTo(outerPosition.x, outerPosition.y);\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.closePath();\n\t\t\t}\n\t\t\t// Extra 3px out for some label spacing\n\t\t\tvar pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);\n\n\t\t\t// Keep this in loop since we may support array properties here\n\t\t\tvar pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);\n\t\t\tctx.font = plFont.font;\n\t\t\tctx.fillStyle = pointLabelFontColor;\n\n\t\t\tvar angleRadians = scale.getIndexAngle(i);\n\t\t\tvar angle = helpers.toDegrees(angleRadians);\n\t\t\tctx.textAlign = getTextAlignForAngle(angle);\n\t\t\tadjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);\n\t\t\tfillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);\n\t\t}\n\t}\n\n\tfunction drawRadiusLine(scale, gridLineOpts, radius, index) {\n\t\tvar ctx = scale.ctx;\n\t\tctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);\n\t\tctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);\n\n\t\tif (scale.options.lineArc) {\n\t\t\t// Draw circular arcs between the points\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);\n\t\t\tctx.closePath();\n\t\t\tctx.stroke();\n\t\t} else {\n\t\t\t// Draw straight lines connecting each index\n\t\t\tvar valueCount = getValueCount(scale);\n\n\t\t\tif (valueCount === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.beginPath();\n\t\t\tvar pointPosition = scale.getPointPosition(0, radius);\n\t\t\tctx.moveTo(pointPosition.x, pointPosition.y);\n\n\t\t\tfor (var i = 1; i < valueCount; i++) {\n\t\t\t\tpointPosition = scale.getPointPosition(i, radius);\n\t\t\t\tctx.lineTo(pointPosition.x, pointPosition.y);\n\t\t\t}\n\n\t\t\tctx.closePath();\n\t\t\tctx.stroke();\n\t\t}\n\t}\n\n\tfunction numberOrZero(param) {\n\t\treturn helpers.isNumber(param) ? param : 0;\n\t}\n\n\tvar LinearRadialScale = Chart.LinearScaleBase.extend({\n\t\tsetDimensions: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar tickOpts = opts.ticks;\n\t\t\t// Set the unconstrained dimension before label rotation\n\t\t\tme.width = me.maxWidth;\n\t\t\tme.height = me.maxHeight;\n\t\t\tme.xCenter = Math.round(me.width / 2);\n\t\t\tme.yCenter = Math.round(me.height / 2);\n\n\t\t\tvar minSize = helpers.min([me.height, me.width]);\n\t\t\tvar tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);\n\t\t\tme.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);\n\t\t},\n\t\tdetermineDataLimits: function() {\n\t\t\tvar me = this;\n\t\t\tvar chart = me.chart;\n\t\t\tvar min = Number.POSITIVE_INFINITY;\n\t\t\tvar max = Number.NEGATIVE_INFINITY;\n\n\t\t\thelpers.each(chart.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tif (chart.isDatasetVisible(datasetIndex)) {\n\t\t\t\t\tvar meta = chart.getDatasetMeta(datasetIndex);\n\n\t\t\t\t\thelpers.each(dataset.data, function(rawValue, index) {\n\t\t\t\t\t\tvar value = +me.getRightValue(rawValue);\n\t\t\t\t\t\tif (isNaN(value) || meta.data[index].hidden) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tmin = Math.min(value, min);\n\t\t\t\t\t\tmax = Math.max(value, max);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tme.min = (min === Number.POSITIVE_INFINITY ? 0 : min);\n\t\t\tme.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);\n\n\t\t\t// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero\n\t\t\tme.handleTickRangeOptions();\n\t\t},\n\t\tgetTickLimit: function() {\n\t\t\tvar tickOpts = this.options.ticks;\n\t\t\tvar tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);\n\t\t\treturn Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));\n\t\t},\n\t\tconvertTicksToLabels: function() {\n\t\t\tvar me = this;\n\t\t\tChart.LinearScaleBase.prototype.convertTicksToLabels.call(me);\n\n\t\t\t// Point labels\n\t\t\tme.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);\n\t\t},\n\t\tgetLabelForIndex: function(index, datasetIndex) {\n\t\t\treturn +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);\n\t\t},\n\t\tfit: function() {\n\t\t\tif (this.options.lineArc) {\n\t\t\t\tfit(this);\n\t\t\t} else {\n\t\t\t\tfitWithPointLabels(this);\n\t\t\t}\n\t\t},\n\t\t/**\n\t\t * Set radius reductions and determine new radius and center point\n\t\t * @private\n\t\t */\n\t\tsetReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {\n\t\t\tvar me = this;\n\t\t\tvar radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);\n\t\t\tvar radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);\n\t\t\tvar radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);\n\t\t\tvar radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);\n\n\t\t\tradiusReductionLeft = numberOrZero(radiusReductionLeft);\n\t\t\tradiusReductionRight = numberOrZero(radiusReductionRight);\n\t\t\tradiusReductionTop = numberOrZero(radiusReductionTop);\n\t\t\tradiusReductionBottom = numberOrZero(radiusReductionBottom);\n\n\t\t\tme.drawingArea = Math.min(\n\t\t\t\tMath.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),\n\t\t\t\tMath.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));\n\t\t\tme.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);\n\t\t},\n\t\tsetCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {\n\t\t\tvar me = this;\n\t\t\tvar maxRight = me.width - rightMovement - me.drawingArea,\n\t\t\t\tmaxLeft = leftMovement + me.drawingArea,\n\t\t\t\tmaxTop = topMovement + me.drawingArea,\n\t\t\t\tmaxBottom = me.height - bottomMovement - me.drawingArea;\n\n\t\t\tme.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);\n\t\t\tme.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);\n\t\t},\n\n\t\tgetIndexAngle: function(index) {\n\t\t\tvar angleMultiplier = (Math.PI * 2) / getValueCount(this);\n\t\t\tvar startAngle = this.chart.options && this.chart.options.startAngle ?\n\t\t\t\tthis.chart.options.startAngle :\n\t\t\t\t0;\n\n\t\t\tvar startAngleRadians = startAngle * Math.PI * 2 / 360;\n\n\t\t\t// Start from the top instead of right, so remove a quarter of the circle\n\t\t\treturn index * angleMultiplier + startAngleRadians;\n\t\t},\n\t\tgetDistanceFromCenterForValue: function(value) {\n\t\t\tvar me = this;\n\n\t\t\tif (value === null) {\n\t\t\t\treturn 0; // null always in center\n\t\t\t}\n\n\t\t\t// Take into account half font size + the yPadding of the top value\n\t\t\tvar scalingFactor = me.drawingArea / (me.max - me.min);\n\t\t\tif (me.options.reverse) {\n\t\t\t\treturn (me.max - value) * scalingFactor;\n\t\t\t}\n\t\t\treturn (value - me.min) * scalingFactor;\n\t\t},\n\t\tgetPointPosition: function(index, distanceFromCenter) {\n\t\t\tvar me = this;\n\t\t\tvar thisAngle = me.getIndexAngle(index) - (Math.PI / 2);\n\t\t\treturn {\n\t\t\t\tx: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,\n\t\t\t\ty: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter\n\t\t\t};\n\t\t},\n\t\tgetPointPositionForValue: function(index, value) {\n\t\t\treturn this.getPointPosition(index, this.getDistanceFromCenterForValue(value));\n\t\t},\n\n\t\tgetBasePosition: function() {\n\t\t\tvar me = this;\n\t\t\tvar min = me.min;\n\t\t\tvar max = me.max;\n\n\t\t\treturn me.getPointPositionForValue(0,\n\t\t\t\tme.beginAtZero? 0:\n\t\t\t\tmin < 0 && max < 0? max :\n\t\t\t\tmin > 0 && max > 0? min :\n\t\t\t\t0);\n\t\t},\n\n\t\tdraw: function() {\n\t\t\tvar me = this;\n\t\t\tvar opts = me.options;\n\t\t\tvar gridLineOpts = opts.gridLines;\n\t\t\tvar tickOpts = opts.ticks;\n\t\t\tvar getValueOrDefault = helpers.getValueOrDefault;\n\n\t\t\tif (opts.display) {\n\t\t\t\tvar ctx = me.ctx;\n\n\t\t\t\t// Tick Font\n\t\t\t\tvar tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);\n\t\t\t\tvar tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);\n\t\t\t\tvar tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);\n\t\t\t\tvar tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);\n\n\t\t\t\thelpers.each(me.ticks, function(label, index) {\n\t\t\t\t\t// Don't draw a centre value (if it is minimum)\n\t\t\t\t\tif (index > 0 || opts.reverse) {\n\t\t\t\t\t\tvar yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);\n\t\t\t\t\t\tvar yHeight = me.yCenter - yCenterOffset;\n\n\t\t\t\t\t\t// Draw circular lines around the scale\n\t\t\t\t\t\tif (gridLineOpts.display && index !== 0) {\n\t\t\t\t\t\t\tdrawRadiusLine(me, gridLineOpts, yCenterOffset, index);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (tickOpts.display) {\n\t\t\t\t\t\t\tvar tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);\n\t\t\t\t\t\t\tctx.font = tickLabelFont;\n\n\t\t\t\t\t\t\tif (tickOpts.showLabelBackdrop) {\n\t\t\t\t\t\t\t\tvar labelWidth = ctx.measureText(label).width;\n\t\t\t\t\t\t\t\tctx.fillStyle = tickOpts.backdropColor;\n\t\t\t\t\t\t\t\tctx.fillRect(\n\t\t\t\t\t\t\t\t\tme.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX,\n\t\t\t\t\t\t\t\t\tyHeight - tickFontSize / 2 - tickOpts.backdropPaddingY,\n\t\t\t\t\t\t\t\t\tlabelWidth + tickOpts.backdropPaddingX * 2,\n\t\t\t\t\t\t\t\t\ttickFontSize + tickOpts.backdropPaddingY * 2\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tctx.textAlign = 'center';\n\t\t\t\t\t\t\tctx.textBaseline = 'middle';\n\t\t\t\t\t\t\tctx.fillStyle = tickFontColor;\n\t\t\t\t\t\t\tctx.fillText(label, me.xCenter, yHeight);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (!opts.lineArc) {\n\t\t\t\t\tdrawPointLabels(me);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\tChart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);\n\n};\n\n},{}],48:[function(require,module,exports){\n/* global window: false */\n'use strict';\n\nvar moment = require(1);\nmoment = typeof(moment) === 'function' ? moment : window.moment;\n\nmodule.exports = function(Chart) {\n\n\tvar helpers = Chart.helpers;\n\tvar time = {\n\t\tunits: [{\n\t\t\tname: 'millisecond',\n\t\t\tsteps: [1, 2, 5, 10, 20, 50, 100, 250, 500]\n\t\t}, {\n\t\t\tname: 'second',\n\t\t\tsteps: [1, 2, 5, 10, 30]\n\t\t}, {\n\t\t\tname: 'minute',\n\t\t\tsteps: [1, 2, 5, 10, 30]\n\t\t}, {\n\t\t\tname: 'hour',\n\t\t\tsteps: [1, 2, 3, 6, 12]\n\t\t}, {\n\t\t\tname: 'day',\n\t\t\tsteps: [1, 2, 5]\n\t\t}, {\n\t\t\tname: 'week',\n\t\t\tmaxStep: 4\n\t\t}, {\n\t\t\tname: 'month',\n\t\t\tmaxStep: 3\n\t\t}, {\n\t\t\tname: 'quarter',\n\t\t\tmaxStep: 4\n\t\t}, {\n\t\t\tname: 'year',\n\t\t\tmaxStep: false\n\t\t}]\n\t};\n\n\tvar defaultConfig = {\n\t\tposition: 'bottom',\n\n\t\ttime: {\n\t\t\tparser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment\n\t\t\tformat: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/\n\t\t\tunit: false, // false == automatic or override with week, month, year, etc.\n\t\t\tround: false, // none, or override with week, month, year, etc.\n\t\t\tdisplayFormat: false, // DEPRECATED\n\t\t\tisoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/\n\t\t\tminUnit: 'millisecond',\n\n\t\t\t// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/\n\t\t\tdisplayFormats: {\n\t\t\t\tmillisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,\n\t\t\t\tsecond: 'h:mm:ss a', // 11:20:01 AM\n\t\t\t\tminute: 'h:mm:ss a', // 11:20:01 AM\n\t\t\t\thour: 'MMM D, hA', // Sept 4, 5PM\n\t\t\t\tday: 'll', // Sep 4 2015\n\t\t\t\tweek: 'll', // Week 46, or maybe \"[W]WW - YYYY\" ?\n\t\t\t\tmonth: 'MMM YYYY', // Sept 2015\n\t\t\t\tquarter: '[Q]Q - YYYY', // Q3\n\t\t\t\tyear: 'YYYY' // 2015\n\t\t\t}\n\t\t},\n\t\tticks: {\n\t\t\tautoSkip: false\n\t\t}\n\t};\n\n\tvar TimeScale = Chart.Scale.extend({\n\t\tinitialize: function() {\n\t\t\tif (!moment) {\n\t\t\t\tthrow new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');\n\t\t\t}\n\n\t\t\tChart.Scale.prototype.initialize.call(this);\n\t\t},\n\t\tgetLabelMoment: function(datasetIndex, index) {\n\t\t\tif (datasetIndex === null || index === null) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (typeof this.labelMoments[datasetIndex] !== 'undefined') {\n\t\t\t\treturn this.labelMoments[datasetIndex][index];\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t\tgetLabelDiff: function(datasetIndex, index) {\n\t\t\tvar me = this;\n\t\t\tif (datasetIndex === null || index === null) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (me.labelDiffs === undefined) {\n\t\t\t\tme.buildLabelDiffs();\n\t\t\t}\n\n\t\t\tif (typeof me.labelDiffs[datasetIndex] !== 'undefined') {\n\t\t\t\treturn me.labelDiffs[datasetIndex][index];\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t\tgetMomentStartOf: function(tick) {\n\t\t\tvar me = this;\n\t\t\tif (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) {\n\t\t\t\treturn tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday);\n\t\t\t}\n\t\t\treturn tick.clone().startOf(me.tickUnit);\n\t\t},\n\t\tdetermineDataLimits: function() {\n\t\t\tvar me = this;\n\t\t\tme.labelMoments = [];\n\n\t\t\t// Only parse these once. If the dataset does not have data as x,y pairs, we will use\n\t\t\t// these\n\t\t\tvar scaleLabelMoments = [];\n\t\t\tif (me.chart.data.labels && me.chart.data.labels.length > 0) {\n\t\t\t\thelpers.each(me.chart.data.labels, function(label) {\n\t\t\t\t\tvar labelMoment = me.parseTime(label);\n\n\t\t\t\t\tif (labelMoment.isValid()) {\n\t\t\t\t\t\tif (me.options.time.round) {\n\t\t\t\t\t\t\tlabelMoment.startOf(me.options.time.round);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscaleLabelMoments.push(labelMoment);\n\t\t\t\t\t}\n\t\t\t\t}, me);\n\n\t\t\t\tme.firstTick = moment.min.call(me, scaleLabelMoments);\n\t\t\t\tme.lastTick = moment.max.call(me, scaleLabelMoments);\n\t\t\t} else {\n\t\t\t\tme.firstTick = null;\n\t\t\t\tme.lastTick = null;\n\t\t\t}\n\n\t\t\thelpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {\n\t\t\t\tvar momentsForDataset = [];\n\t\t\t\tvar datasetVisible = me.chart.isDatasetVisible(datasetIndex);\n\n\t\t\t\tif (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {\n\t\t\t\t\thelpers.each(dataset.data, function(value) {\n\t\t\t\t\t\tvar labelMoment = me.parseTime(me.getRightValue(value));\n\n\t\t\t\t\t\tif (labelMoment.isValid()) {\n\t\t\t\t\t\t\tif (me.options.time.round) {\n\t\t\t\t\t\t\t\tlabelMoment.startOf(me.options.time.round);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmomentsForDataset.push(labelMoment);\n\n\t\t\t\t\t\t\tif (datasetVisible) {\n\t\t\t\t\t\t\t\t// May have gone outside the scale ranges, make sure we keep the first and last ticks updated\n\t\t\t\t\t\t\t\tme.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment;\n\t\t\t\t\t\t\t\tme.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, me);\n\t\t\t\t} else {\n\t\t\t\t\t// We have no labels. Use the ones from the scale\n\t\t\t\t\tmomentsForDataset = scaleLabelMoments;\n\t\t\t\t}\n\n\t\t\t\tme.labelMoments.push(momentsForDataset);\n\t\t\t}, me);\n\n\t\t\t// Set these after we've done all the data\n\t\t\tif (me.options.time.min) {\n\t\t\t\tme.firstTick = me.parseTime(me.options.time.min);\n\t\t\t}\n\n\t\t\tif (me.options.time.max) {\n\t\t\t\tme.lastTick = me.parseTime(me.options.time.max);\n\t\t\t}\n\n\t\t\t// We will modify these, so clone for later\n\t\t\tme.firstTick = (me.firstTick || moment()).clone();\n\t\t\tme.lastTick = (me.lastTick || moment()).clone();\n\t\t},\n\t\tbuildLabelDiffs: function() {\n\t\t\tvar me = this;\n\t\t\tme.labelDiffs = [];\n\t\t\tvar scaleLabelDiffs = [];\n\t\t\t// Parse common labels once\n\t\t\tif (me.chart.data.labels && me.chart.data.labels.length > 0) {\n\t\t\t\thelpers.each(me.chart.data.labels, function(label) {\n\t\t\t\t\tvar labelMoment = me.parseTime(label);\n\n\t\t\t\t\tif (labelMoment.isValid()) {\n\t\t\t\t\t\tif (me.options.time.round) {\n\t\t\t\t\t\t\tlabelMoment.startOf(me.options.time.round);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscaleLabelDiffs.push(labelMoment.diff(me.firstTick, me.tickUnit, true));\n\t\t\t\t\t}\n\t\t\t\t}, me);\n\t\t\t}\n\n\t\t\thelpers.each(me.chart.data.datasets, function(dataset) {\n\t\t\t\tvar diffsForDataset = [];\n\n\t\t\t\tif (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {\n\t\t\t\t\thelpers.each(dataset.data, function(value) {\n\t\t\t\t\t\tvar labelMoment = me.parseTime(me.getRightValue(value));\n\n\t\t\t\t\t\tif (labelMoment.isValid()) {\n\t\t\t\t\t\t\tif (me.options.time.round) {\n\t\t\t\t\t\t\t\tlabelMoment.startOf(me.options.time.round);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdiffsForDataset.push(labelMoment.diff(me.firstTick, me.tickUnit, true));\n\t\t\t\t\t\t}\n\t\t\t\t\t}, me);\n\t\t\t\t} else {\n\t\t\t\t\t// We have no labels. Use common ones\n\t\t\t\t\tdiffsForDataset = scaleLabelDiffs;\n\t\t\t\t}\n\n\t\t\t\tme.labelDiffs.push(diffsForDataset);\n\t\t\t}, me);\n\t\t},\n\t\tbuildTicks: function() {\n\t\t\tvar me = this;\n\n\t\t\tme.ctx.save();\n\t\t\tvar tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);\n\t\t\tvar tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);\n\t\t\tvar tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);\n\t\t\tvar tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);\n\t\t\tme.ctx.font = tickLabelFont;\n\n\t\t\tme.ticks = [];\n\t\t\tme.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step\n\t\t\tme.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)\n\n\t\t\t// Set unit override if applicable\n\t\t\tif (me.options.time.unit) {\n\t\t\t\tme.tickUnit = me.options.time.unit || 'day';\n\t\t\t\tme.displayFormat = me.options.time.displayFormats[me.tickUnit];\n\t\t\t\tme.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);\n\t\t\t\tme.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1);\n\t\t\t} else {\n\t\t\t\t// Determine the smallest needed unit of the time\n\t\t\t\tvar innerWidth = me.isHorizontal() ? me.width : me.height;\n\n\t\t\t\t// Crude approximation of what the label length might be\n\t\t\t\tvar tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []);\n\t\t\t\tvar tickLabelWidth = me.ctx.measureText(tempFirstLabel).width;\n\t\t\t\tvar cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation));\n\t\t\t\tvar sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation));\n\t\t\t\ttickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);\n\t\t\t\tvar labelCapacity = innerWidth / (tickLabelWidth);\n\n\t\t\t\t// Start as small as possible\n\t\t\t\tme.tickUnit = me.options.time.minUnit;\n\t\t\t\tme.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);\n\t\t\t\tme.displayFormat = me.options.time.displayFormats[me.tickUnit];\n\n\t\t\t\tvar unitDefinitionIndex = 0;\n\t\t\t\tvar unitDefinition = time.units[unitDefinitionIndex];\n\n\t\t\t\t// While we aren't ideal and we don't have units left\n\t\t\t\twhile (unitDefinitionIndex < time.units.length) {\n\t\t\t\t\t// Can we scale this unit. If `false` we can scale infinitely\n\t\t\t\t\tme.unitScale = 1;\n\n\t\t\t\t\tif (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {\n\t\t\t\t\t\t// Use one of the predefined steps\n\t\t\t\t\t\tfor (var idx = 0; idx < unitDefinition.steps.length; ++idx) {\n\t\t\t\t\t\t\tif (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) {\n\t\t\t\t\t\t\t\tme.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {\n\t\t\t\t\t\t// We have a max step. Scale this unit\n\t\t\t\t\t\tme.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Move to the next unit up\n\t\t\t\t\t\t++unitDefinitionIndex;\n\t\t\t\t\t\tunitDefinition = time.units[unitDefinitionIndex];\n\n\t\t\t\t\t\tme.tickUnit = unitDefinition.name;\n\t\t\t\t\t\tvar leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true);\n\t\t\t\t\t\tvar trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true);\n\t\t\t\t\t\tme.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer;\n\t\t\t\t\t\tme.displayFormat = me.options.time.displayFormats[unitDefinition.name];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar roundedStart;\n\n\t\t\t// Only round the first tick if we have no hard minimum\n\t\t\tif (!me.options.time.min) {\n\t\t\t\tme.firstTick = me.getMomentStartOf(me.firstTick);\n\t\t\t\troundedStart = me.firstTick;\n\t\t\t} else {\n\t\t\t\troundedStart = me.getMomentStartOf(me.firstTick);\n\t\t\t}\n\n\t\t\t// Only round the last tick if we have no hard maximum\n\t\t\tif (!me.options.time.max) {\n\t\t\t\tvar roundedEnd = me.getMomentStartOf(me.lastTick);\n\t\t\t\tvar delta = roundedEnd.diff(me.lastTick, me.tickUnit, true);\n\t\t\t\tif (delta < 0) {\n\t\t\t\t\t// Do not use end of because we need me to be in the next time unit\n\t\t\t\t\tme.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit));\n\t\t\t\t} else if (delta >= 0) {\n\t\t\t\t\tme.lastTick = roundedEnd;\n\t\t\t\t}\n\n\t\t\t\tme.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);\n\t\t\t}\n\n\t\t\t// Tick displayFormat override\n\t\t\tif (me.options.time.displayFormat) {\n\t\t\t\tme.displayFormat = me.options.time.displayFormat;\n\t\t\t}\n\n\t\t\t// first tick. will have been rounded correctly if options.time.min is not specified\n\t\t\tme.ticks.push(me.firstTick.clone());\n\n\t\t\t// For every unit in between the first and last moment, create a moment and add it to the ticks tick\n\t\t\tfor (var i = me.unitScale; i <= me.scaleSizeInUnits; i += me.unitScale) {\n\t\t\t\tvar newTick = roundedStart.clone().add(i, me.tickUnit);\n\n\t\t\t\t// Are we greater than the max time\n\t\t\t\tif (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tme.ticks.push(newTick);\n\t\t\t}\n\n\t\t\t// Always show the right tick\n\t\t\tvar diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit);\n\t\t\tif (diff !== 0 || me.scaleSizeInUnits === 0) {\n\t\t\t\t// this is a weird case. If the option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart\n\t\t\t\t// but the last tick was not rounded.\n\t\t\t\tif (me.options.time.max) {\n\t\t\t\t\tme.ticks.push(me.lastTick.clone());\n\t\t\t\t\tme.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true);\n\t\t\t\t} else {\n\t\t\t\t\tme.ticks.push(me.lastTick.clone());\n\t\t\t\t\tme.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tme.ctx.restore();\n\n\t\t\t// Invalidate label diffs cache\n\t\t\tme.labelDiffs = undefined;\n\t\t},\n\t\t// Get tooltip label\n\t\tgetLabelForIndex: function(index, datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : '';\n\t\t\tvar value = me.chart.data.datasets[datasetIndex].data[index];\n\n\t\t\tif (value !== null && typeof value === 'object') {\n\t\t\t\tlabel = me.getRightValue(value);\n\t\t\t}\n\n\t\t\t// Format nicely\n\t\t\tif (me.options.time.tooltipFormat) {\n\t\t\t\tlabel = me.parseTime(label).format(me.options.time.tooltipFormat);\n\t\t\t}\n\n\t\t\treturn label;\n\t\t},\n\t\t// Function to format an individual tick mark\n\t\ttickFormatFunction: function(tick, index, ticks) {\n\t\t\tvar formattedTick = tick.format(this.displayFormat);\n\t\t\tvar tickOpts = this.options.ticks;\n\t\t\tvar callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);\n\n\t\t\tif (callback) {\n\t\t\t\treturn callback(formattedTick, index, ticks);\n\t\t\t}\n\t\t\treturn formattedTick;\n\t\t},\n\t\tconvertTicksToLabels: function() {\n\t\t\tvar me = this;\n\t\t\tme.tickMoments = me.ticks;\n\t\t\tme.ticks = me.ticks.map(me.tickFormatFunction, me);\n\t\t},\n\t\tgetPixelForValue: function(value, index, datasetIndex) {\n\t\t\tvar me = this;\n\t\t\tvar offset = null;\n\t\t\tif (index !== undefined && datasetIndex !== undefined) {\n\t\t\t\toffset = me.getLabelDiff(datasetIndex, index);\n\t\t\t}\n\n\t\t\tif (offset === null) {\n\t\t\t\tif (!value || !value.isValid) {\n\t\t\t\t\t// not already a moment object\n\t\t\t\t\tvalue = me.parseTime(me.getRightValue(value));\n\t\t\t\t}\n\t\t\t\tif (value && value.isValid && value.isValid()) {\n\t\t\t\t\toffset = value.diff(me.firstTick, me.tickUnit, true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (offset !== null) {\n\t\t\t\tvar decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset;\n\n\t\t\t\tif (me.isHorizontal()) {\n\t\t\t\t\tvar valueOffset = (me.width * decimal);\n\t\t\t\t\treturn me.left + Math.round(valueOffset);\n\t\t\t\t}\n\n\t\t\t\tvar heightOffset = (me.height * decimal);\n\t\t\t\treturn me.top + Math.round(heightOffset);\n\t\t\t}\n\t\t},\n\t\tgetPixelForTick: function(index) {\n\t\t\treturn this.getPixelForValue(this.tickMoments[index], null, null);\n\t\t},\n\t\tgetValueForPixel: function(pixel) {\n\t\t\tvar me = this;\n\t\t\tvar innerDimension = me.isHorizontal() ? me.width : me.height;\n\t\t\tvar offset = (pixel - (me.isHorizontal() ? me.left : me.top)) / innerDimension;\n\t\t\toffset *= me.scaleSizeInUnits;\n\t\t\treturn me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds');\n\t\t},\n\t\tparseTime: function(label) {\n\t\t\tvar me = this;\n\t\t\tif (typeof me.options.time.parser === 'string') {\n\t\t\t\treturn moment(label, me.options.time.parser);\n\t\t\t}\n\t\t\tif (typeof me.options.time.parser === 'function') {\n\t\t\t\treturn me.options.time.parser(label);\n\t\t\t}\n\t\t\t// Date objects\n\t\t\tif (typeof label.getMonth === 'function' || typeof label === 'number') {\n\t\t\t\treturn moment(label);\n\t\t\t}\n\t\t\t// Moment support\n\t\t\tif (label.isValid && label.isValid()) {\n\t\t\t\treturn label;\n\t\t\t}\n\t\t\t// Custom parsing (return an instance of moment)\n\t\t\tif (typeof me.options.time.format !== 'string' && me.options.time.format.call) {\n\t\t\t\tconsole.warn('options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale');\n\t\t\t\treturn me.options.time.format(label);\n\t\t\t}\n\t\t\t// Moment format parsing\n\t\t\treturn moment(label, me.options.time.format);\n\t\t}\n\t});\n\tChart.scaleService.registerScaleType('time', TimeScale, defaultConfig);\n\n};\n\n},{\"1\":1}]},{},[7])(7)\n});\n","define('modules/reports/appointment-count-by-month/index',[\n 'angular',\n 'moment',\n 'angular-chart'\n], function (angular, moment) {\n 'use strict';\n\n return angular.module('bl.reports.appointment-count-by-month', ['chart.js'])\n\n .config(function ($stateProvider, Enums) {\n $stateProvider\n .state('reports.appointment-count-by-month', {\n url: '/appointment-count-by-month',\n views: {\n content: {\n controller: 'AppointmentCountByMonthCtrl',\n templateUrl: 'js/modules/reports/appointment-count-by-month/main.tpl.html'\n }\n },\n resolve: {\n employees: function (Employee) {\n return Employee.query({sortField: 'firstName', filterObject:{status:Enums.statuses.active.value}});\n },\n appt_types: function (AppointmentType) {\n return AppointmentType.query({filterObject:{status:Enums.statuses.active.value}});\n }\n }\n });\n })\n\n .controller('AppointmentCountByMonthCtrl', function ($scope, $controller, $translate, modalService, employees, appt_types, Reports) {\n\n $scope.chart = {\n showChart: false,\n labels: [''],\n data: ['']\n };\n\n\n var headers = [\n {\n title: 'calendar.month',\n sortField: 'yearMonth'\n },\n {\n title: 'reports.headers.number-of-appointments',\n sortField: 'nbrAppts'\n },\n {\n title: 'reports.headers.percent-of-total'\n }\n ];\n\n // Draw Graph\n\n $scope.drawGraph = function (results) {\n\n $scope.chart.labels = [];\n $scope.chart.data = [];\n\n\n _.each(results, function (item) {\n\n if(!item.totalRow){ // last item Total\n\n _.map(item, function (val, key) {\n\n if(key == 'yearMonth'){\n $scope.chart.labels.push(val);\n }\n if(key == 'nbrAppts'){\n $scope.chart.data.push(val);\n }\n });\n }\n });\n };\n\n angular.extend(this, $controller('CommonReportsCtrl', {$scope: $scope, headers:headers}));\n\n if (employees) {\n employees.unshift({firstName: $translate.instant('common.all') });\n }\n $scope.employees = employees;\n\n $scope.appt_types = appt_types;\n\n // default search values\n $scope.initialValues = {startDate: moment().startOf('month').toDate(), endDate:new Date(), employeeId:undefined, appointmentTypeId:undefined};\n $scope.formValues = angular.copy($scope.initialValues);\n\n $scope.results = $scope.selectedResults = [];\n $scope.runReport = function () {\n Reports.appointmentCountByMonth($scope.buildReportsObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.exportToExcel = function () {\n Reports.appointmentCountByMonthExport($scope.buildReportsObject(), \"xls\");\n };\n\n $scope.exportToPdf = function (print) {\n Reports.appointmentCountByMonthExport($scope.buildReportsObject(), \"pdf\", print);\n };\n });\n});\n\n","define('modules/reports/favorite-items-summary/index',[\n 'angular',\n 'moment'\n], function (angular, moment) {\n 'use strict';\n\n return angular.module('bl.reports.favorite-items-summary', [])\n\n .config(function ($stateProvider, Enums) {\n $stateProvider\n .state('reports.favorite-items-summary', {\n url: '/favorite-items-summary',\n views: {\n content: {\n controller: 'FavoriteItemsSummaryCtrl',\n templateUrl: 'js/modules/reports/favorite-items-summary/main.tpl.html'\n }\n },\n resolve: {\n departments: function (Department) {\n return Department.query({ sortField: 'name', filterObject:{ status: Enums.statuses.active.value }});\n },\n vendors: function (Vendor) {\n return Vendor.query({ sortField: 'name', filterObject:{ status: Enums.statuses.active.value }});\n }\n }\n });\n })\n\n .controller('FavoriteItemsSummaryCtrl', function ($scope, $controller, $translate, AppLicense, modalService, Enums, departments, vendors, Reports, Department, Vendor) {\n\n var headers = [\n {\n title: 'item.name',\n sortField: 'name'\n },\n {\n title: 'item.vendor-name',\n sortField: 'vendorItemName'\n },\n {\n title: 'common.vendor',\n sortField: 'vendorCode'\n },\n {\n title: 'common.department',\n sortField: 'deptCode'\n },\n {\n title: 'reports.headers.number-of-times-favorited',\n sortField: 'count'\n }\n ];\n\n angular.extend(this, $controller('CommonReportsCtrl', { $scope: $scope, headers: headers }));\n\n initializeDepartments(departments);\n initializeVendors(vendors);\n\n var license = AppLicense.get();\n if(license.linkedAccounts.length > 1 && license.plan.multiStore) {\n $scope.linkedAccounts = angular.copy(license.linkedAccounts);\n $scope.linkedAccounts.unshift({linkedRetailerId:\"ALL_LOCATIONS\", nickName: $translate.instant('reports.common.all-locations') });\n }\n\n $scope.triedOnFlagOptions = [\n { value: '', label: $translate.instant('common.all') },\n { value: 'Y', label: $translate.instant('common.yes') },\n { value: 'N', label: $translate.instant('common.no') }\n ];\n\n $scope.statuses = [{ description:\"All\" }, Enums.statuses.active, Enums.statuses.inactive, Enums.statuses.discontinued, Enums.statuses.activeOrDiscontinued];\n\n // default search values\n $scope.initialValues = { triedOnFlag: $scope.triedOnFlagOptions[0].value ,retailerId: license.retailerId, departmentId:0, vendorId:0, startDate: moment().subtract('days', 7).toDate(), endDate: new Date() };\n $scope.formValues = angular.copy($scope.initialValues);\n\n $scope.item = {};\n $scope.itemSelectorOptions = {\n addNewItem: false,\n settingsPopoverPlacement: 'bottom',\n onChooseItem: function(item) {\n $scope.formValues.name = item.name;\n },\n onAddNewItem: function(item) {\n $scope.formValues.name = item.name;\n },\n onUnselectItem: function() {\n $scope.formValues.name = null;\n }\n };\n\n $scope.results = $scope.selectedResults = [];\n $scope.runReport = function () {\n Reports.favoriteItemSummary($scope.buildReportsObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.exportToExcel = function () {\n Reports.favoriteItemSummaryExport($scope.buildReportsObject(), \"xls\");\n };\n\n $scope.exportToPdf = function (print) {\n Reports.favoriteItemSummaryExport($scope.buildReportsObject(), \"pdf\", print);\n };\n\n $scope.onLocationChange = function() {\n $scope.initializeResults();\n\n //when the location changes, we need to reset the drop downs because they will need to be\n //re-initialized with the new retailerId for this location\n $scope.formValues.vendorId = 0;\n $scope.formValues.selectedVendors = [];\n $scope.formValues.departmentId = 0;\n $scope.formValues.selectedDepartments = [];\n $scope.disableLinks = $scope.formValues.retailerId !== license.retailerId;\n $scope.itemCriteriaHidden = $scope.formValues.retailerId !== license.retailerId && $scope.formValues.retailerId !== 'ALL_LOCATIONS';\n\n if($scope.formValues.retailerId !== \"ALL_LOCATIONS\") {\n //show inputs that are location specific\n $scope.departmentCriteriaHidden = false;\n $scope.vendorCriteriaHidden = false;\n\n var filter = { retailerId: $scope.formValues.retailerId, status: Enums.statuses.active.value };\n\n //now re-initialize the data for the drop downs\n Vendor.query({ sortField: 'name', filterObject: filter }, function(vendors) {\n initializeVendors(vendors);\n });\n\n //now re-initialize the data for the drop downs\n Department.query({ sortField: 'name', filterObject: filter }, function(departments) {\n initializeDepartments(departments);\n });\n } else {\n //hide inputs that are location specific\n $scope.departmentCriteriaHidden = true;\n $scope.vendorCriteriaHidden = true;\n }\n };\n\n function initializeDepartments(departments) {\n $scope.departments = departments.result;\n }\n\n function initializeVendors(vendors) {\n $scope.vendors = vendors.result;\n }\n });\n});\n\n","define('modules/reports/sales-by-month/index',[\n 'angular',\n 'angular-chart'\n], function (angular) {\n 'use strict';\n\n return angular.module('bl.reports.sales-by-month', ['chart.js'])\n\n .config(function ($stateProvider, Enums) {\n $stateProvider\n .state('reports.sales-by-month', {\n url: '/sales-by-month',\n views: {\n content: {\n controller: 'SalesByMonthCtrl',\n templateUrl: 'js/modules/reports/sales-by-month/main.tpl.html'\n }\n },\n resolve: {\n employees: function (Employee) {\n return Employee.query({sortField: 'firstName', filterObject:{status:Enums.statuses.active.value}});\n },\n departments: function (Department) {\n return Department.query({sortField: 'name', filterObject:{status:Enums.statuses.active.value}});\n },\n vendors: function (Vendor) {\n return Vendor.query({sortField: 'name', filterObject:{status:Enums.statuses.active.value}});\n }\n }\n });\n })\n\n .controller('SalesByMonthCtrl', function ($scope, $controller, modalService, Enums, employees, departments, vendors, security) {\n\n $scope.chart = {\n showChart: false,\n labels: [''],\n data: ['']\n };\n\n var headers = [\n {\n title: 'calendar.month',\n sortField: 'yearMonth'\n },\n {\n title: 'reports.headers.qty-sold',\n sortField: 'qtySold'\n },\n {\n title: 'common.price',\n sortField: 'totalPrice'\n },\n {\n title: 'common.cost',\n sortField: 'totalCost',\n isHidden: !security.hasPermission('ITEM_COST')\n },\n {\n title: 'reports.headers.margin-percent',\n sortField: 'marginPercent',\n isHidden: !security.hasPermission('ITEM_COST')\n },\n {\n title: 'reports.headers.total-profit',\n sortField: 'totalProfit',\n isHidden: !security.hasPermission('ITEM_COST')\n }\n ];\n\n // Draw Graph\n\n $scope.drawGraph = function (results) {\n\n $scope.chart.labels = [];\n $scope.chart.data = [];\n\n\n _.each(results, function (item) {\n\n if(!item.totalRow){ // last item Total\n\n _.map(item, function (val, key) {\n\n if(key == 'yearMonth'){\n $scope.chart.labels.push(val);\n }\n if(key == 'totalProfit'){\n $scope.chart.data.push(val);\n }\n });\n }\n });\n };\n\n angular.extend(this, $controller('CommonReportsCtrl', {$scope: $scope, headers:headers}));\n angular.extend(this, $controller('CommonSalesReportsCtrl', {$scope: $scope, reportId:\"salesBy/yearMonth\", employees:employees, departments:departments, vendors:vendors, security:security, taxCodes: null }));\n });\n});\n\n","define('modules/reports/payment-journal/index',[\n 'angular',\n 'underscore',\n '../../common/license-service'\n], function (angular, _) {\n 'use strict';\n\n return angular.module('bl.reports.payment-journal', [\n 'bl.common.license'\n ])\n\n .config(function ($stateProvider, Enums) {\n $stateProvider\n .state('reports.payment-journal', {\n url: '/payment-journal?startDate?endDate',\n views: {\n content: {\n controller: 'PaymentJournalCtrl',\n templateUrl: 'js/modules/reports/payment-journal/main.tpl.html'\n }\n },\n resolve: {\n employees: function (Employee) {\n return Employee.query({sortField: 'firstName', filterObject:{status:Enums.statuses.active.value}});\n },\n payment_methods: function (PaymentMethod) {\n return PaymentMethod.query({filterObject:{status:Enums.statuses.active.value}});\n },\n posSettings: function (CompanySetting) {\n return CompanySetting.getPOSSettings();\n },\n registers: function (Register) {\n return Register.query({sortField: 'name'});\n }\n }\n });\n })\n\n .controller('PaymentJournalCtrl', function (\n $scope,\n $state,\n $stateParams,\n $controller,\n $translate,\n $window,\n $locale,\n $filter,\n AppLicense,\n Contact,\n EmailService,\n modalService,\n Enums,\n PaymentMethod,\n Register,\n registers,\n employees,\n payment_methods,\n posSettings,\n Reports,\n ResourcesHelper,\n PaymentReceiptService,\n TransactionPaymentsModalService,\n Employee,\n User,\n UtilsHelper,\n PORTAL,\n PAYMENT_PLAN) {\n\n $scope.locale = $locale;\n\n var headers = [\n {\n title: 'reports.headers.pmt-number',\n sortField: 'receiptNumber'\n },\n {\n title: 'appointment.modal.date',\n sortField: 'paymentDate'\n },\n {\n title: 'contact.purchase.headers.trx-#',\n sortField: 'trxNumber'\n },\n {\n title: 'common.contact',\n sortField: 'contactName'\n },\n {\n title: 'common.associate',\n sortField: 'empName'\n },\n {\n title: 'reports.headers.method',\n sortField: 'methodDescription'\n },\n {\n title: $scope.locale.id === 'SP' ? 'reports.headers.contact-address' : 'transaction.approval-code',\n sortField: $scope.locale.id === 'SP' ? 'address1' : 'approvalCode'\n },\n\n {\n title: 'reports.headers.amount',\n sortField: 'pmtAmount'\n }\n ];\n\n $scope.userTypes = [\n { value: '', description: 'common.all' },\n { value: PORTAL.USERNAME, description: 'settings.account.client-portal' },\n { value: PAYMENT_PLAN.USERNAME, description: 'payment-plans.payment-plan' }\n ];\n\n initializeEmployees(employees);\n initializePaymentMethods(payment_methods);\n initializeRegisters(registers);\n\n $scope.hasAllXWebSettings = TransactionPaymentsModalService.hasAllXWebSettings(posSettings);\n $scope.hasAllChargeItProSettings = TransactionPaymentsModalService.hasAllChargeItProSettings(posSettings);\n $scope.hasAllFullSteamSettings = TransactionPaymentsModalService.hasAllFullSteamSettings(posSettings);\n $scope.isEMV = false; //xweb clients\n\n if($scope.hasAllChargeItProSettings) {\n $scope.locationIdSetting = _(posSettings).findWhere({type: 'POS_CIP_LOCATION_ID'}) || {};\n $scope.controllerNameSetting = _(posSettings).findWhere({type: 'POS_CIP_CONTROLLER_NAME'}) || {};\n if ($scope.locationIdSetting && $scope.locationIdSetting.value && $scope.controllerNameSetting && $scope.controllerNameSetting.value) {\n $scope.isEMV = true; // CIP clients\n }\n } else {\n $scope.isEMV = $window.localStorage.isEMVTerminal === \"true\";\n }\n\n $scope.categories = angular.copy(Enums.paymentMethodCategories);\n $scope.categories.unshift({id:-1, description: 'common.all' });\n\n $scope.types = [\n Enums.trxTypes.all,\n Enums.trxTypes.sale,\n Enums.trxTypes.special_order,\n Enums.trxTypes.return_trx,\n Enums.trxTypes.layaway\n ];\n\n var license = AppLicense.get();\n if(license.linkedAccounts.length > 1 && license.plan.multiStore) {\n $scope.linkedAccounts = angular.copy(license.linkedAccounts);\n $scope.linkedAccounts.unshift({ linkedRetailerId:\"ALL_LOCATIONS\", nickName: $translate.instant('reports.common.all-locations') });\n }\n\n angular.extend(this, $controller('CommonReportsCtrl', {$scope: $scope, headers:headers}));\n\n $scope.trxStatuses = [Enums.statuses.pendingAndComplete, Enums.statuses.pending, Enums.statuses.complete, Enums.statuses.voided];\n\n var startDate = new Date();\n var endDate = new Date();\n\n if ($stateParams.startDate && $stateParams.endDate) {\n startDate = $stateParams.startDate;\n endDate = $stateParams.endDate;\n }\n\n $scope.fundedFilter = Enums.bridalLivePayFundedFilter;\n\n $scope.initialValues = { paymentReceivedMethod: '', retailerId: license.retailerId, methodId: 0, methodCategoryId: -1, startDate: startDate, endDate: endDate, trxStatus: Enums.statuses.pendingAndComplete.value,\n trxTypeId: Enums.trxTypes.all.value, showVoidedPayments: false, fundedFlag: '' };\n\n $scope.formValues = angular.copy($scope.initialValues);\n\n $scope.results = $scope.selectedResults = [];\n $scope.runReport = function () {\n Reports.salesReport('paymentJournal', $scope.buildReportsObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.exportToExcel = function () {\n Reports.salesReportExport('paymentJournal', $scope.buildReportsObject(), \"xls\");\n };\n\n $scope.exportToPdf = function (print) {\n Reports.salesReportExport('paymentJournal', $scope.buildReportsObject(), \"pdf\", print);\n };\n\n $scope.paymentCreatedDateTooltip = function (date) {\n if(!date) {\n return '';\n }\n\n return $translate.instant('transaction.payment-created-on') + ' ' + $filter('date')(date, 'medium');\n };\n\n $scope.paymentTipAmountTooltip = function (tip) {\n if(!tip) {\n return '';\n }\n\n return $translate.instant('transaction.tip-amount') + ': ' + $filter('currency')(tip);\n };\n\n $scope.transactionLink = function (trxTypeId, trxId) {\n return $state.href(UtilsHelper.getTrxRoute(trxTypeId), { id: trxId });\n };\n\n $scope.sendEmail = function(result, defaultType) {\n var payment = {\n id: result.paymentId,\n transactionId: result.trxId,\n xwebTransactionId: result.xWebTransactionId\n };\n\n if(defaultType === 'splitPayment') {\n payment.splitNumber = result.splitNumber;\n }\n\n Contact.getById(result.contactId, function (contact) {\n EmailService.showSendModal(contact, null, null, { id: payment.transactionId }, null, null, {\n defaultTemplateType: defaultType,\n }, false, payment);\n });\n };\n\n $scope.getReceipt = function (result, index) {\n var payment = {\n id: result.paymentId,\n transactionId: result.trxId,\n xwebTransactionId: result.xWebTransactionId\n };\n\n $scope.printing = true;\n $scope.printIndex = index;\n\n PaymentReceiptService.printReceipt(payment, $scope.isEMV, $scope.hasAllChargeItProSettings, $scope.hasAllXWebSettings, $scope.hasAllFullSteamSettings, User, function(response) {\n $scope.printing = false;\n $scope.printIndex = null;\n console.log(response + \"Receipt\");\n }, function(error) {\n $scope.printing = false;\n $scope.printIndex = null;\n });\n };\n\n $scope.printBulkPayment = function (result, index) {\n var payment = {\n id: result.paymentId,\n transactionId: result.trxId,\n xwebTransactionId: result.xWebTransactionId,\n splitNumber: result.splitNumber\n };\n\n $scope.printing = true;\n $scope.printIndex = index;\n\n PaymentReceiptService.printReceipt(payment, $scope.isEMV, $scope.hasAllChargeItProSettings, $scope.hasAllXWebSettings, $scope.hasAllFullSteamSettings, User, function(response) {\n $scope.printing = false;\n $scope.printIndex = null;\n console.log(response + \"Receipt\");\n }, function(error) {\n $scope.printing = false;\n $scope.printIndex = null;\n });\n };\n\n $scope.printBulkPayment = function (result, index) {\n var splitPayment = {\n id: result.paymentId,\n transactionId: result.trxId,\n xwebTransactionId: result.xWebTransactionId,\n splitNumber: result.splitNumber\n };\n\n $scope.printingCombined = true;\n $scope.printingCombinedIndex = index;\n PaymentReceiptService.printSplitPaymentReceipt(splitPayment, $scope.isEMV, $scope.hasAllChargeItProSettings, $scope.hasAllXWebSettings, $scope.hasAllFullSteamSettings, User, function(response) {\n $scope.printingCombined = false;\n $scope.printingCombinedIndex = null;\n console.log(response + \"Split Payment Receipt\");\n }, function(error) {\n $scope.printingCombined = false;\n $scope.printingCombinedIndex = null;\n });\n };\n\n /**\n * Init\n */\n\n if ($stateParams.startDate && $stateParams.endDate) {\n $scope.runReport();\n }\n\n $scope.onLocationChange = function() {\n\n $scope.initializeResults();\n\n //when the location changes, we need to reset the drop downs because they will need to be\n //re-initialized with the new retailerId for this location\n $scope.formValues.methodId = 0;\n $scope.formValues.selectedMethods = [];\n $scope.formValues.employeeId = undefined;\n $scope.formValues.registerId = undefined;\n $scope.disableLinks = $scope.formValues.retailerId !== license.retailerId;\n\n if($scope.formValues.retailerId !== \"ALL_LOCATIONS\") {\n\n //show inputs that are location specific\n $scope.employeeCriteriaHidden = false;\n $scope.registerCriteriaHidden = false;\n\n var filter = {retailerId: $scope.formValues.retailerId, status:Enums.statuses.active.value};\n\n //now re-initialize the data for the drop downs\n Employee.query({sortField: 'firstName', filterObject:filter}, function(employees) {\n initializeEmployees(employees);\n });\n\n // //now re-initialize the data for the drop downs\n PaymentMethod.query({filterObject:{status:Enums.statuses.active.value}}, function(payment_methods) {\n initializePaymentMethods(payment_methods);\n });\n\n Register.query({sortField: 'name', filterObject:{ retailerId: $scope.formValues.retailerId }}, function(registers) {\n initializeRegisters(registers);\n });\n\n } else {\n //hide inputs that are location specific\n $scope.employeeCriteriaHidden = true;\n $scope.registerCriteriaHidden = true;\n }\n };\n\n /**\n * Init\n */\n\n if ($stateParams.startDate && $stateParams.endDate) {\n $scope.runReport();\n }\n\n function initializeEmployees(employees) {\n employees = ResourcesHelper.adaptList('employees', employees);\n if (employees) {\n employees.unshift({fullName: $translate.instant('common.all') });\n }\n $scope.employees = employees;\n }\n\n function initializePaymentMethods(payment_methods) {\n $scope.payment_methods = payment_methods;\n }\n\n function initializeRegisters(registers) {\n if(registers) {\n registers.unshift({ name: $translate.instant('common.all') });\n }\n $scope.registers = registers;\n }\n });\n});\n\n","define('modules/reports/email-history-log/index',[\n 'angular',\n 'moment',\n 'underscore',\n '../../common/license-service'\n], function(angular, moment, _) {\n 'use strict';\n\n return angular.module('bl.reports.email-history-log', [\n 'bl.common.license'\n ])\n\n .config(function($stateProvider, Enums) {\n $stateProvider\n .state('reports.email-history-log', {\n url: '/email-history-log?startDate?endDate',\n views: {\n content: {\n controller: 'EmailHistoryLogCtrl',\n templateUrl: 'js/modules/reports/email-history-log/main.tpl.html'\n }\n },\n resolve: {\n employees: function(Employee) {\n return Employee.query({sortField: 'firstName', filterObject:{status: Enums.statuses.active.value}});\n },\n templates: function(EmailTemplate) {\n return EmailTemplate.query();\n }\n }\n });\n })\n\n .controller('EmailHistoryLogCtrl', function($scope, $stateParams, $controller, $translate, AppLicense, modalService, Reports, EmailService, Employee, Enums, employees, templates, ResourcesHelper) {\n\n var headers = [\n {\n title: 'appointment.request.date/time'\n },\n {\n title: 'modal-service.email.recipient-email',\n sortField:'recipientEmail'\n },\n {\n title: 'modal-service.email.recipient-name',\n sortField:'recipientName'\n },\n {\n title: 'contact.messages.sent-by',\n sortField: 'senderName'\n },\n {\n title: 'common.status',\n sortField: 'status',\n active: true\n }\n ];\n\n angular.extend(this, $controller('CommonReportsCtrl', { $scope: $scope, headers:headers }));\n\n var startDate = new Date();\n var endDate = new Date();\n\n if($stateParams.startDate && $stateParams.endDate) {\n startDate = $stateParams.startDate;\n endDate = $stateParams.endDate;\n }\n\n initializeEmployees(employees);\n\n var license = AppLicense.get();\n if(license.linkedAccounts.length > 1 && license.plan.multiStore) {\n $scope.linkedAccounts = angular.copy(license.linkedAccounts);\n $scope.linkedAccounts.unshift({ linkedRetailerId: \"ALL_LOCATIONS\", nickName: $translate.instant('reports.common.all-locations') });\n }\n\n $scope.statuses = [\n Enums.statuses.sent,\n { description: $translate.instant('common.scheduled'), value: Enums.statuses.pending.value },\n Enums.statuses.archived,\n Enums.statuses.error\n ];\n\n $scope.templates = templates;\n $scope.templates.unshift({ name: $translate.instant('common.all') });\n\n $scope.initialValues = { startDate: startDate, endDate: endDate, retailerId: license.retailerId, status: Enums.statuses.sent.value, templateId: undefined };\n $scope.formValues = angular.copy($scope.initialValues);\n\n $scope.results = $scope.selectedResults = [];\n $scope.runReport = function() {\n Reports.emailHistoryLog($scope.buildReportsObject(), function(resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.exportToExcel = function() {\n Reports.emailHistoryLogExport($scope.buildReportsObject(), \"xls\");\n };\n\n $scope.exportToPdf = function(print) {\n Reports.emailHistoryLogExport($scope.buildReportsObject(), \"pdf\", print);\n };\n\n $scope.showViewSentEmailModal = function(email) {\n EmailService.showViewModal(email);\n };\n\n $scope.onLocationChange = function() {\n $scope.initializeResults();\n\n //when the location changes, we need to reset the drop downs because they will need to be\n //re-initialized with the new retailerId for this location\n $scope.formValues.employeeId = undefined;\n $scope.disableLinks = $scope.formValues.retailerId !== license.retailerId;\n\n if($scope.formValues.retailerId !== \"ALL_LOCATIONS\") {\n //show inputs that are location specific\n $scope.employeeCriteriaHidden = false;\n $scope.templateCriteriaHidden = true;\n\n var filter = { retailerId: $scope.formValues.retailerId, status: Enums.statuses.active.value };\n\n //now re-initialize the data for the drop downs\n Employee.query({ sortField: 'firstName', filterObject: filter }, function(employees) {\n initializeEmployees(employees);\n });\n\n } else {\n //hide inputs that are location specific\n $scope.employeeCriteriaHidden = true;\n $scope.templateCriteriaHidden = true;\n }\n };\n\n function initializeEmployees(employees) {\n employees = ResourcesHelper.adaptList('employees', employees);\n if(employees) {\n employees.unshift({fullName: $translate.instant('common.all') });\n }\n $scope.employees = employees;\n }\n\n /**\n * Init\n */\n\n if($stateParams.startDate && $stateParams.endDate) {\n $scope.runReport();\n }\n });\n});\n\n","define('modules/reports/funding-report/index',[\n 'angular',\n 'underscore',\n 'moment',\n '../../common/license-service'\n], function (angular, _, moment) {\n 'use strict';\n\n return angular.module('bl.reports.funding-report', [\n 'bl.common.license'\n ])\n\n .config(function ($stateProvider, Enums) {\n $stateProvider\n .state('reports.funding-report', {\n url: '/funding-report?startDate?endDate',\n views: {\n content: {\n controller: 'FundingReportCtrl',\n templateUrl: 'js/modules/reports/funding-report/main.tpl.html'\n }\n }\n });\n })\n\n .controller('FundingReportCtrl', function ($scope, $state, $controller, $locale, $translate, $filter, $stateParams, AppLicense, modalService, Enums, Reports) {\n var license = AppLicense.get();\n if(license.linkedAccounts.length > 1 && license.plan.multiStore) {\n $scope.linkedAccounts = angular.copy(license.linkedAccounts);\n $scope.linkedAccounts.unshift({ linkedRetailerId:\"ALL_LOCATIONS\", nickName: $translate.instant('reports.common.all-locations') });\n }\n\n $scope.locale = $locale;\n\n var headers = [\n {\n title: 'calendar.day',\n sortField: 'dayNumber'\n },\n {\n title:'purchase-order.total',\n sortField:'amount'\n },\n {\n title:'transaction.fees',\n sortField:'feeAmount'\n },\n {\n title:'transaction.net',\n sortField:'fundedAmount'\n }\n ];\n\n $scope.chart = {\n showChart: false,\n labels: [''],\n series: [\n $translate.instant('purchase-order.total'),\n $translate.instant('transaction.fees'),\n $translate.instant('transaction.net')\n ],\n data: [''],\n colors: [\n {\n backgroundColor: '#9686b1'\n },\n {\n backgroundColor: '#ddd'\n },\n {\n backgroundColor: '#5cb85c'\n }\n ],\n options: {\n legend: {\n display: true\n },\n scales: {\n yAxes: [{\n ticks: {\n // Include a currency sign in the ticks\n callback: function(value, index, values) {\n return $filter('currency')(value);\n }\n }\n }]\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.datasets[tooltipItem.datasetIndex].label || '';\n\n if(label) {\n label += ': ';\n }\n\n label += $filter('currency')(tooltipItem.yLabel);\n return label;\n }\n }\n }\n }\n };\n\n var days = [\n { label: 'settings.day-of-week.0', day: 1 },\n { label: 'settings.day-of-week.1', day: 2 },\n { label: 'settings.day-of-week.2', day: 3 },\n { label: 'settings.day-of-week.3', day: 4 },\n { label: 'settings.day-of-week.4', day: 5 },\n { label: 'settings.day-of-week.5', day: 6 },\n { label: 'settings.day-of-week.6', day: 7 }\n ];\n\n angular.extend(this, $controller('CommonReportsCtrl', { $scope: $scope, headers:headers }));\n\n var startDate = moment().subtract('days', 7).toDate();\n var endDate = new Date();\n\n if($stateParams.startDate && $stateParams.endDate) {\n startDate = $stateParams.startDate;\n endDate = $stateParams.endDate;\n $scope.chart.showChart = true;\n }\n\n // default search values\n $scope.initialValues = { retailerId: license.retailerId, startDate: startDate, endDate: endDate };\n $scope.formValues = angular.copy($scope.initialValues);\n\n $scope.results = $scope.selectedResults = [];\n $scope.runReport = function () {\n Reports.fundingReport($scope.buildReportsObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.exportToExcel = function () {\n Reports.fundingReportExport($scope.buildReportsObject(), \"xls\");\n };\n\n $scope.exportToPdf = function (print) {\n Reports.fundingReportExport($scope.buildReportsObject(), \"pdf\", print);\n };\n\n // Draw Graph\n $scope.drawGraph = function (results) {\n $scope.chart.labels = [];\n $scope.chart.data = [];\n $scope.chart.amount = [];\n $scope.chart.fee = [];\n $scope.chart.net = [];\n\n _.each(days, function (day) {\n var amount = 0;\n var fee = 0;\n var net = 0;\n\n $scope.chart.labels.push($translate.instant(day.label));\n\n if(results.length) {\n _.each(results, function (item) {\n if(!item.totalRow) {\n if(item.dayNumber === day.day) {\n _.map(item, function (val, key) {\n if(key === 'amount'){\n amount = val;\n }\n if(key === 'feeAmount'){\n fee = val;\n }\n\n if(key === 'fundedAmount'){\n net = val;\n }\n });\n }\n }\n });\n }\n\n $scope.chart.amount.push(amount);\n $scope.chart.fee.push(fee);\n $scope.chart.net.push(net);\n });\n\n $scope.chart.data.push($scope.chart.amount);\n $scope.chart.data.push($scope.chart.fee);\n $scope.chart.data.push($scope.chart.net);\n };\n\n });\n});\n\n","define('modules/referrals/module',[\n 'angular',\n 'ui-router',\n '../resources/referral'\n], function (angular) {\n 'use strict';\n\n return angular.module('bl.referrals', [\n 'ui.router',\n 'bl.enums',\n 'bl.resources.referral'\n ])\n .config(function ($stateProvider) {\n $stateProvider\n .state('referrals', {\n url: '/referrals',\n views: {\n \"main@\": {\n controller: 'ReferralsCtrl',\n templateUrl: 'js/modules/referrals/referrals.tpl.html'\n }\n },\n data: {\n 'public': true\n }\n });\n });\n});\n\n","define('modules/settings/calendar-black-out-dates/index',[\n 'angular',\n 'moment',\n 'underscore',\n 'jquery',\n './bulk-black-out-ctrl',\n '../settings-list-ctrl',\n '../../resources/calendar-black-out-dates'\n], function (angular, moment, _, $) {\n 'use strict';\n\n return angular.module('bl.settings.calendar-black-out-dates', [\n 'bl.settings.calendar-black-out-dates.bulk-black-out',\n 'bl.settings.settings-list-ctrl',\n 'bl.resources.calendar-black-out-dates'\n ])\n\n .config(function ($stateProvider, Permissions) {\n $stateProvider\n .state('settings.calendar_black_out_dates', {\n url: '/calendar_black_out_dates',\n permissions: Permissions.SETTINGS_AC,\n views: {\n content: {\n controller: 'SettingsCalendarBlackOutDatesCtrl',\n templateUrl: 'js/modules/settings/calendar-black-out-dates/main.tpl.html'\n }\n }\n });\n })\n\n .controller('SettingsCalendarBlackOutDatesCtrl', function ($scope, $modal, $locale, $controller, User, modalService, alerts, CalendarBlackOutDate, FullcalendarHelper) {\n $scope.selectedResults = [];\n $scope.results = $scope.selectedResults = [];\n\n angular.extend(this, $controller('SearchCtrl', { $scope: $scope, headers: null }));\n\n // pagination settings wrapper\n $scope.pagination = {\n page: 1,\n perPage: 10,\n perPageOptions: [\n { size: 10, label: 10 },\n { size: 25, label: 25 },\n { size: 50, label: 50 }\n ],\n totalItems: 0\n };\n\n $scope.initialValues = {\n startDate: moment().startOf('month').toDate(),\n endDate: moment().endOf('month').toDate()\n };\n\n $scope.formValues = angular.copy($scope.initialValues);\n\n var timeFormat = $locale.DATETIME_FORMATS.timepickerFormat.moment;\n $scope.newBlackOutDate = initNewBlackOutDate('S');\n $scope.days = initializeDaysChecked();\n\n\n $scope.onDayTypeChange = function(type) {\n $scope.newBlackOutDate = initNewBlackOutDate(type);\n $('.time-off').timepicker('setTime', null);\n };\n\n $scope.isBlackOutMultipleValid = function () {\n return $scope.newBlackOutDate.startDateTime && $scope.newBlackOutDate.endDateTime && !moment($scope.newBlackOutDate.endDateTime).isBefore($scope.newBlackOutDate.startDateTime, 'day') && $scope.newBlackOutDate.startTime && $scope.newBlackOutDate.endTime;\n };\n\n $scope.isBlackOutValid = function () {\n return $scope.newBlackOutDate.startDateTime && $scope.newBlackOutDate.startTime && $scope.newBlackOutDate.endTime;\n };\n\n $scope.generateBlackOut = function(blackOutEntry) {\n $scope.generating = true;\n\n if(!blackOutEntry.endDateTime || !blackOutEntry.startDateTime || !blackOutEntry.startTime || !blackOutEntry.endTime || moment(blackOutEntry.endDateTime).isBefore(blackOutEntry.startDateTime, 'day')) {\n $scope.generating = false;\n return modalService.showMessageStaticModal('settings.employee.message.enter-time-off-date-and-time-range');\n }\n\n var days = moment(blackOutEntry.endDateTime).diff(blackOutEntry.startDateTime, 'days') + (blackOutEntry.startDateTime === blackOutEntry.endDateTime ? 0 : 1);\n\n if(days > 90) {\n $scope.generating = false;\n return modalService.showMessageStaticModal('settings.employee.message.enter-start-and-end-date-less-than-90-days');\n }\n\n var blackOutEntries = [];\n var startDate = new Date(blackOutEntry.startDateTime);\n\n for(var i = 0; i < days; i++) {\n\n if(i > 0) {\n startDate = new Date(moment(blackOutEntry.startDateTime).add(i, 'days'));\n }\n\n var startTime = FullcalendarHelper.timeToArray(blackOutEntry.startTime);\n var endTime = FullcalendarHelper.timeToArray(blackOutEntry.endTime);\n\n if(addBlackOutEntry(startDate)) {\n blackOutEntries.push(new CalendarBlackOutDate({\n retailerId: User.retailerId,\n createdByUser: User.username,\n modifiedByUser: User.username,\n modifiedDate: Date.now(),\n startTime: blackOutEntry.startTime,\n endTime: blackOutEntry.endTime,\n startDateTime: (new Date(startDate)).setHours(startTime.hours, startTime.minutes, 0, 0),\n endDateTime: (new Date(startDate)).setHours(endTime.hours, endTime.minutes, 0, 0),\n comment: blackOutEntry.comment\n }));\n }\n }\n\n $modal.open({\n templateUrl: 'js/modules/settings/calendar-black-out-dates/modal-bulk-black-out.tpl.html',\n controller: 'BulkBlackOutCtrl',\n resolve: {\n entries: function() {\n return blackOutEntries;\n }\n }\n }).result.then(function(response) {\n if(response) {\n $scope.search();\n $scope.days = initializeDaysChecked();\n $scope.newBlackOutDate = initNewBlackOutDate('M');\n }\n\n $scope.generating = false;\n });\n };\n\n $scope.onAllDayChange = function(blackOutEntry) {\n if(blackOutEntry.allDay) {\n blackOutEntry.startTime = moment().startOf('day').format(timeFormat);\n blackOutEntry.endTime = moment().endOf('day').format(timeFormat);\n } else {\n blackOutEntry.startTime = null;\n blackOutEntry.endTime = null;\n $('.bulk-time').timepicker('setTime', null);\n }\n };\n\n $scope.onAddClick = function () {\n var blackOutDate = angular.copy(_($scope.newBlackOutDate).omit('startTime', 'endTime'), new CalendarBlackOutDate());\n blackOutDate.retailerId = User.retailerId;\n blackOutDate.createdByUser = User.username;\n blackOutDate.modifiedByUser = User.username;\n blackOutDate.modifiedDate = Date.now();\n\n var startTime = FullcalendarHelper.timeToArray($scope.newBlackOutDate.startTime);\n var endTime = FullcalendarHelper.timeToArray($scope.newBlackOutDate.endTime);\n\n if(startTime.hours === 0 && startTime.minutes === 0 && startTime.hours === endTime.hours && startTime.minutes === endTime.minutes) {\n endTime.hours = 23;\n endTime.minutes = 59;\n }\n\n if(startTime.hours > endTime.hours) {\n modalService.showMessageStaticModal('settings.calendar-black-out-dates.start-time-is-required-before-end-time', 'settings.employee.message.ooops');\n return;\n } else if(startTime.hours >= endTime.hours && startTime.minutes >= endTime.minutes) {\n modalService.showMessageStaticModal('settings.calendar-black-out-dates.start-time-is-required-before-end-time', 'settings.employee.message.ooops');\n return;\n }\n\n var integerStartTime = +[startTime.hours, startTime.minutes || '00'].join('');\n var integerEndTime = +[endTime.hours, endTime.minutes || '00'].join('');\n\n if(integerEndTime <= integerStartTime) {\n endTime.hours += 24;\n }\n\n if(endTime.hours === 24 && endTime.minutes === 0) {\n endTime.hours = 23;\n endTime.minutes = 59;\n }\n\n blackOutDate.startDateTime = (new Date(blackOutDate.startDateTime)).setHours(startTime.hours, startTime.minutes, 0, 0);\n blackOutDate.endDateTime = (new Date(blackOutDate.startDateTime)).setHours(endTime.hours, endTime.minutes, 0, 0);\n\n blackOutDate.$save(function() {\n $scope.newBlackOutDate.startTime = moment().startOf('day').format(timeFormat);\n $scope.newBlackOutDate.endTime = moment().endOf('day').format(timeFormat);\n\n $scope.search();\n alerts.pushSuccess('alerts.settings.successfully-added-calendar-black-out-date');\n });\n };\n\n $scope.onRemoveClick = function (blackOutDate) {\n modalService.showConfirmation('settings.calendar-black-out-dates.would-like-to-delete-date?', function () {\n blackOutDate.$remove(function () {\n $scope.results = _($scope.results).without(blackOutDate);\n });\n });\n };\n\n $scope.editMode = function(blackOut) {\n blackOut.editMode = !blackOut.editMode;\n\n if(blackOut.editMode) {\n blackOut.startDate = blackOut.startDateTime;\n blackOut.start = blackOut.startDateTime;\n blackOut.startTime = moment(blackOut.startDateTime).format(timeFormat);\n\n if(blackOut.endDateTime) {\n blackOut.end = blackOut.endDateTime;\n blackOut.endTime = moment(blackOut.endDateTime).format(timeFormat);\n }\n }\n };\n\n $scope.onEditClick = function(bo, idx) {\n var blackOut = angular.extend(new CalendarBlackOutDate(), _(bo).omit('start', 'end', 'editMode', 'startTime', 'endTime', 'startDate', 'clockingOut'));\n blackOut.modifiedByUser = User.username;\n blackOut.modifiedDate = Date.now();\n\n if(!bo.startDate || !bo.startTime || !bo.endTime || moment(bo.endTime, timeFormat).isBefore(moment(bo.startTime, timeFormat))) {\n modalService.showMessageStaticModal('settings.employee.message.enter-validate-start-and-end-times', 'settings.employee.message.ooops');\n return;\n }\n\n var startDate = bo.startDate;\n var startTime = FullcalendarHelper.timeToArray(bo.startTime);\n var endTime = FullcalendarHelper.timeToArray(bo.endTime);\n\n if(startTime.hours > endTime.hours) {\n modalService.showMessageStaticModal('settings.calendar-black-out-dates.start-time-is-required-before-end-time', 'settings.employee.message.ooops');\n return;\n } else if(startTime.hours >= endTime.hours && startTime.minutes >= endTime.minutes) {\n modalService.showMessageStaticModal('settings.calendar-black-out-dates.start-time-is-required-before-end-time', 'settings.employee.message.ooops');\n return;\n }\n\n var integerStartTime = +[startTime.hours, startTime.minutes || '00'].join('');\n var integerEndTime = +[endTime.hours, endTime.minutes || '00'].join('');\n\n if(integerEndTime <= integerStartTime) {\n endTime.hours += 24;\n }\n\n if(endTime.hours === 24 && endTime.minutes === 0) {\n endTime.hours = 23;\n endTime.minutes = 59;\n }\n\n blackOut.startDateTime = (new Date(startDate)).setHours(startTime.hours, startTime.minutes, 0, 0);\n blackOut.endDateTime = (new Date(startDate)).setHours(endTime.hours, endTime.minutes, 0, 0);\n\n blackOut.$update(function(updatedBlackOut) {\n $scope.results[idx] = updatedBlackOut;\n alerts.pushSuccess('alerts.settings.successfully-updated-calendar-black-out-date');\n }, function (data, status) {\n bo.startDateTime = bo.start;\n bo.endDateTime = bo.end;\n bo.editMode = false;\n CalendarBlackOutDate.defaultErrorHandler(data, status);\n });\n };\n\n $scope.search = function () {\n var params = $scope.buildFilterObject();\n params.sortField = \"startDateTime\";\n\n if($scope.formValues.startDate) {\n params.filterObject.startDate = moment($scope.formValues.startDate).format('YYYY-MM-DD');\n }\n\n if($scope.formValues.endDate) {\n params.filterObject.endDate = moment($scope.formValues.endDate).format('YYYY-MM-DD');\n }\n\n CalendarBlackOutDate.query(params, function(resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.selectAll = false;\n $scope.toggleSelectAll = function (removeAll) {\n $scope.selectAll = !$scope.selectAll;\n if ($scope.selectAll) {\n $scope.selectedResults = _.union($scope.selectedResults, $scope.results);\n } else {\n $scope.selectedResults = removeAll ? [] : _($scope.selectedResults).difference($scope.results);\n }\n\n $scope.results = _($scope.results).map(function(blackOutDate) {\n blackOutDate.checked = $scope.selectAll;\n return blackOutDate;\n });\n };\n\n $scope.syncSelectedResults = function(obj) {\n var checkedItWasSelected = _($scope.selectedResults).findWhere({ id: obj.id });\n if(checkedItWasSelected) {\n $scope.selectedResults = _($scope.selectedResults).without(checkedItWasSelected);\n } else {\n $scope.selectedResults.push(obj);\n }\n };\n\n $scope.uncheckSelectedResults = function() {\n $scope.selectAll = true;\n $scope.toggleSelectAll(true);\n };\n\n $scope.deleteAll = function () {\n if(!$scope.selectedResults.length) {\n modalService.showMessage('settings.calendar-black-out-dates.select-one', 'common.message.wooops');\n return false;\n }\n\n var successcb = function (result) {\n $scope.toggleSelectAll(true);\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.settings.successfully-deleted-black-out-dates',\n params: { number: result.nbrRowsAffected }\n });\n $scope.selectAll = false;\n };\n\n modalService.showConfirmation({\n key: 'settings.calendar-black-out-dates.delete-black-out-dates?',\n params: { number: $scope.selectedResults.length }\n }, function () {\n CalendarBlackOutDate.bulkDelete(_($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.search();\n\n function initNewBlackOutDate(dayType) {\n return new CalendarBlackOutDate({\n startDateTime: Date.now(),\n endDateTime: Date.now(),\n allDay: false,\n dayType: dayType\n });\n }\n\n function addBlackOutEntry(date) {\n var dayOfWeek = moment(date).day();\n var addDate = true;\n\n _($scope.days).each(function(day) {\n if(day.isChecked && day.day === dayOfWeek) {\n addDate = false;\n }\n });\n\n return addDate;\n }\n\n function initializeDaysChecked() {\n return {\n sun: {\n isChecked: false,\n day: 0\n },\n mon: {\n isChecked: false,\n day: 1\n },\n tue: {\n isChecked: false,\n day: 2\n },\n wed: {\n isChecked: false,\n day: 3\n },\n thu: {\n isChecked: false,\n day: 4\n },\n fri: {\n isChecked: false,\n day: 5\n },\n sat: {\n isChecked: false,\n day: 6\n }\n };\n }\n });\n});\n\n","define('modules/settings/client-portal-preferences/index',[\n 'angular',\n 'equals',\n 'underscore',\n 'ui-sortable',\n '../../common/enums-service',\n '../../alerts/index',\n '../../modal/modal-service',\n '../../resources/item',\n '../../resources/company-setting',\n '../../resources/portal-event-management-step',\n '../../resources/portal-message-setting',\n '../../resources/contact',\n '../../security/user-service',\n '../../color-theme/index',\n '../../client-portal/state/index'\n], function (angular, equals, _) {\n 'use strict';\n\n return angular.module('bl.settings.client-portal', [\n 'ui.sortable',\n 'bl.enums',\n 'bl.alerts',\n 'bl.modal',\n 'bl.resources.item',\n 'bl.resources.company-setting',\n 'bl.resources.portal-event-management-step',\n 'bl.resources.portal-message-setting',\n 'bl.resources.contact',\n 'bl.security.user',\n 'bl.color-theme',\n 'bl.client-portal.state'\n ])\n\n .config(function ($stateProvider, Permissions, Enums) {\n var template = function (name) {\n return 'js/modules/settings/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('settings.client_portal_preferences', {\n url: '/client_portal_preferences',\n permissions: Permissions.CLIENT_PORTAL_SETTINGS,\n views: {\n content: {\n controller: 'SettingsClientPortalPreferencesCtrl',\n templateUrl: template('client-portal-preferences/main'),\n resolve: {\n settings: function (CompanySetting) {\n return CompanySetting.getClientPortalSettings();\n },\n smsTemplates: function (SMSTemplate) {\n return SMSTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n emailTemplates: function (EmailTemplate) {\n return EmailTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n messageSettings: function (PortalMessageSetting) {\n return PortalMessageSetting.getPortalMessageSettings();\n },\n twilioSettings: function (TwilioSettings) {\n return TwilioSettings.get();\n }\n }\n }\n }\n });\n })\n\n .controller('SettingsClientPortalPreferencesCtrl', function ($scope, $q, $controller, $translate, $state, $locale, $timeout, $modal, AppLicense, Contact, ItemModal, settings, PORTAL_COLOR_THEMES, ContactModal, security, MergeFields, Item, CompanySetting, PortalEventManagementStep, PortalMessageSetting, messageSettings, alerts, User, modalService, SMS, Sms, Enums, emailTemplates, smsTemplates, ColorThemeService, twilioSettings) {\n $scope.licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n\n $scope.twilioSettings = twilioSettings;\n $scope.templates = emailTemplates;\n\n buildTemplates(emailTemplates, smsTemplates);\n $timeout(initializeCardHeight);\n\n // Check for Portal Message Settings, if the user doesn't have any. Go ahead and automatically install them for the user.\n if(messageSettings && messageSettings.length && messageSettings.length >= 6) {\n $scope.messageSettings = messageSettings;\n } else {\n installPortalMessageSettings();\n }\n\n $scope.colorThemes = Enums.colorThemes;\n $scope.greetingTypes = Enums.greetingTypes;\n $scope.heroImageTypes = Enums.heroImageTypes;\n $scope.portalViews = Enums.portalViews;\n $scope.appointmentFormType = Enums.appointmentFormType;\n $scope.fontTypes = Enums.fontTypes;\n $scope.locale = $locale;\n $scope.portal = {};\n $scope.breakdown = false;\n\n //set the default milestone type\n var model = new PortalEventManagementStep();\n model.milestoneCode = Enums.portalMileStoneTypes[0].value;\n\n var modalScope = {\n twilioSettings: $scope.twilioSettings,\n emailTemplates: $scope.emailTemplates,\n smsTemplates: $scope.smsTemplates,\n milestoneTypes: Enums.portalMileStoneTypes,\n defaultModel: model\n };\n\n var modalTemplate = 'js/modules/settings/client-portal-preferences/modal.tpl.html';\n var obj = { $scope: $scope, Resource: PortalEventManagementStep, modelName: $translate.instant('settings.client-portal-preferences.event-management-step'), modalTemplate: modalTemplate, modalScope: modalScope };\n angular.extend(this, $controller('SettingsListCtrl', obj));\n\n // storage to know when user changes data\n var cache = {\n setting1: null,\n setting2: null,\n setting3: null,\n setting4: null,\n setting5: null,\n setting6: null,\n setting7: null,\n setting8: null,\n setting9: null,\n setting10: null,\n setting11: null,\n setting12: null,\n setting13: null,\n setting14: null,\n setting15: null,\n setting16: null,\n setting17: null,\n setting18: null,\n setting19: null,\n setting20: null,\n setting21: null,\n setting22: null,\n setting23: null,\n setting24: null,\n setting25: null,\n setting26: null,\n setting27: null,\n setting28: null,\n setting29: null,\n setting30: null,\n setting31: null,\n setting32: null,\n setting33: null,\n setting34: null,\n setting35: null,\n setting36: null,\n setting37: null,\n setting38: null,\n setting39: null,\n setting40: null,\n setting41: null,\n setting42: null,\n setting43: null,\n setting44: null,\n setting45: null,\n setting46: null,\n setting47: null,\n setting48: null,\n setting49: null,\n setting50: null,\n setting51: null,\n setting52: null,\n setting53: null,\n setting54: null,\n setting55: null,\n setting56: null,\n setting57: null,\n setting58: null,\n setting59: null,\n setting60: null,\n setting61: null,\n setting62: null,\n setting63: null,\n setting64: null,\n setting65: null,\n setting66: null,\n setting67: null,\n setting68: null,\n setting69: null,\n setting70: null,\n setting71: null,\n setting72: null,\n setting73: null,\n setting74: null,\n setting75: null,\n set: function (setting1, setting2, setting3, setting4, setting5, setting6, setting7, setting8, setting9, setting10, setting11, setting12, setting13,\n setting14, setting15, setting16, setting17, setting18, setting19, setting20, setting21, setting22, setting23, setting24, setting25, setting26, setting27,\n setting28, setting29, setting30, setting31, setting32, setting33, setting34, setting35, setting36, setting37, setting38, setting39, setting40, setting41,\n setting42, setting43, setting44, setting45, setting46, setting47, setting48, setting49, setting50, setting51, setting52, setting53, setting54, setting55,\n setting56, setting57, setting58, setting59, setting60, setting61, setting62, setting63, setting64, setting65, setting66, setting67, setting68, setting69,\n setting70, setting71, setting72, setting73, setting74, setting75) {\n cache.setting1 = angular.copy(setting1);\n cache.setting2 = angular.copy(setting2);\n cache.setting3 = angular.copy(setting3);\n cache.setting4 = angular.copy(setting4);\n cache.setting5 = angular.copy(setting5);\n cache.setting6 = angular.copy(setting6);\n cache.setting7 = angular.copy(setting7);\n cache.setting8 = angular.copy(setting8);\n cache.setting9 = angular.copy(setting9);\n cache.setting10 = angular.copy(setting10);\n cache.setting11 = angular.copy(setting11);\n cache.setting12 = angular.copy(setting12);\n cache.setting13 = angular.copy(setting13);\n cache.setting14 = angular.copy(setting14);\n cache.setting15 = angular.copy(setting15);\n cache.setting16 = angular.copy(setting16);\n cache.setting17 = angular.copy(setting17);\n cache.setting18 = angular.copy(setting18);\n cache.setting19 = angular.copy(setting19);\n cache.setting20 = angular.copy(setting20);\n cache.setting21 = angular.copy(setting21);\n cache.setting22 = angular.copy(setting22);\n cache.setting23 = angular.copy(setting23);\n cache.setting24 = angular.copy(setting24);\n cache.setting25 = angular.copy(setting25);\n cache.setting26 = angular.copy(setting26);\n cache.setting27 = angular.copy(setting27);\n cache.setting28 = angular.copy(setting28);\n cache.setting29 = angular.copy(setting29);\n cache.setting30 = angular.copy(setting30);\n cache.setting31 = angular.copy(setting31);\n cache.setting32 = angular.copy(setting32);\n cache.setting33 = angular.copy(setting33);\n cache.setting34 = angular.copy(setting34);\n cache.setting35 = angular.copy(setting35);\n cache.setting36 = angular.copy(setting36);\n cache.setting37 = angular.copy(setting37);\n cache.setting38 = angular.copy(setting38);\n cache.setting39 = angular.copy(setting39);\n cache.setting40 = angular.copy(setting40);\n cache.setting41 = angular.copy(setting41);\n cache.setting42 = angular.copy(setting42);\n cache.setting43 = angular.copy(setting43);\n cache.setting44 = angular.copy(setting44);\n cache.setting45 = angular.copy(setting45);\n cache.setting46 = angular.copy(setting46);\n cache.setting47 = angular.copy(setting47);\n cache.setting48 = angular.copy(setting48);\n cache.setting49 = angular.copy(setting49);\n cache.setting50 = angular.copy(setting50);\n cache.setting51 = angular.copy(setting51);\n cache.setting52 = angular.copy(setting52);\n cache.setting53 = angular.copy(setting53);\n cache.setting54 = angular.copy(setting54);\n cache.setting55 = angular.copy(setting55);\n cache.setting56 = angular.copy(setting56);\n cache.setting57 = angular.copy(setting57);\n cache.setting58 = angular.copy(setting58);\n cache.setting59 = angular.copy(setting59);\n cache.setting60 = angular.copy(setting60);\n cache.setting61 = angular.copy(setting61);\n cache.setting62 = angular.copy(setting62);\n cache.setting63 = angular.copy(setting63);\n cache.setting64 = angular.copy(setting64);\n cache.setting65 = angular.copy(setting65);\n cache.setting66 = angular.copy(setting66);\n cache.setting67 = angular.copy(setting67);\n cache.setting68 = angular.copy(setting68);\n cache.setting69 = angular.copy(setting69);\n cache.setting70 = angular.copy(setting70);\n cache.setting71 = angular.copy(setting71);\n cache.setting72 = angular.copy(setting72);\n cache.setting73 = angular.copy(setting73);\n cache.setting74 = angular.copy(setting74);\n cache.setting75 = angular.copy(setting75);\n },\n isDifferent: function () {\n return !equals(cache.setting1, $scope.setting1) ||\n !equals(cache.setting2, $scope.setting2) ||\n !equals(cache.setting3, $scope.setting3) ||\n !equals(cache.setting4, $scope.setting4) ||\n !equals(cache.setting5, $scope.setting5) ||\n !equals(cache.setting6, $scope.setting6) ||\n !equals(cache.setting7, $scope.setting7) ||\n !equals(cache.setting8, $scope.setting8) ||\n !equals(cache.setting9, $scope.setting9) ||\n !equals(cache.setting10, $scope.setting10) ||\n !equals(cache.setting11, $scope.setting11) ||\n !equals(cache.setting12, $scope.setting12) ||\n !equals(cache.setting13, $scope.setting13) ||\n !equals(cache.setting14, $scope.setting14) ||\n !equals(cache.setting15, $scope.setting15) ||\n !equals(cache.setting16, $scope.setting16) ||\n !equals(cache.setting17, $scope.setting17) ||\n !equals(cache.setting18, $scope.setting18) ||\n !equals(cache.setting19, $scope.setting19) ||\n !equals(cache.setting20, $scope.setting20) ||\n !equals(cache.setting21, $scope.setting21) ||\n !equals(cache.setting22, $scope.setting22) ||\n !equals(cache.setting23, $scope.setting23) ||\n !equals(cache.setting24, $scope.setting24) ||\n !equals(cache.setting25, $scope.setting25) ||\n !equals(cache.setting26, $scope.setting26) ||\n !equals(cache.setting27, $scope.setting27) ||\n !equals(cache.setting28, $scope.setting28) ||\n !equals(cache.setting29, $scope.setting29) ||\n !equals(cache.setting30, $scope.setting30) ||\n !equals(cache.setting31, $scope.setting31) ||\n !equals(cache.setting32, $scope.setting32) ||\n !equals(cache.setting33, $scope.setting33) ||\n !equals(cache.setting34, $scope.setting34) ||\n !equals(cache.setting35, $scope.setting35) ||\n !equals(cache.setting36, $scope.setting36) ||\n !equals(cache.setting37, $scope.setting37) ||\n !equals(cache.setting38, $scope.setting38) ||\n !equals(cache.setting39, $scope.setting39) ||\n !equals(cache.setting40, $scope.setting40) ||\n !equals(cache.setting41, $scope.setting41) ||\n !equals(cache.setting42, $scope.setting42) ||\n !equals(cache.setting43, $scope.setting43) ||\n !equals(cache.setting44, $scope.setting44) ||\n !equals(cache.setting45, $scope.setting45) ||\n !equals(cache.setting46, $scope.setting46) ||\n !equals(cache.setting47, $scope.setting47) ||\n !equals(cache.setting48, $scope.setting48) ||\n !equals(cache.setting49, $scope.setting49) ||\n !equals(cache.setting50, $scope.setting50) ||\n !equals(cache.setting51, $scope.setting51) ||\n !equals(cache.setting52, $scope.setting52) ||\n !equals(cache.setting53, $scope.setting53) ||\n !equals(cache.setting54, $scope.setting54) ||\n !equals(cache.setting55, $scope.setting55) ||\n !equals(cache.setting56, $scope.setting56) ||\n !equals(cache.setting57, $scope.setting57) ||\n !equals(cache.setting58, $scope.setting58) ||\n !equals(cache.setting59, $scope.setting59) ||\n !equals(cache.setting60, $scope.setting60) ||\n !equals(cache.setting61, $scope.setting61) ||\n !equals(cache.setting62, $scope.setting62) ||\n !equals(cache.setting63, $scope.setting63) ||\n !equals(cache.setting64, $scope.setting64) ||\n !equals(cache.setting65, $scope.setting65) ||\n !equals(cache.setting66, $scope.setting66) ||\n !equals(cache.setting67, $scope.setting67) ||\n !equals(cache.setting68, $scope.setting68) ||\n !equals(cache.setting69, $scope.setting69) ||\n !equals(cache.setting70, $scope.setting70) ||\n !equals(cache.setting71, $scope.setting71) ||\n !equals(cache.setting72, $scope.setting72) ||\n !equals(cache.setting73, $scope.setting73) ||\n !equals(cache.setting74, $scope.setting74) ||\n !equals(cache.setting75, $scope.setting75);\n }\n };\n\n function populateScopeWithSettings($scope, settings) {\n _.each(settings, function (setting) {\n if (setting.type === CompanySetting.CP_FEATURE_PIN_ENABLED) {\n $scope.setting1 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_PAYMENTS) {\n $scope.setting2 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_EVENT_MANAGEMENT) {\n $scope.setting3 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_APPOINTMENTS) {\n $scope.setting4 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_LOOKBOOK) {\n $scope.setting5 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_DIGITAL_SIGNATURES) {\n $scope.setting6 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_MEASUREMENTS) {\n $scope.setting7 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_COLOR_THEME) {\n $scope.setting8 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_HEADER_FOOTER_BG_COLOR) {\n $scope.setting9 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_HEADER_FOOTER_TEXT_COLOR) {\n $scope.setting10 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_PRIMARY_BTN_BG_COLOR) {\n $scope.setting11 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_PRIMARY_BTN_TEXT_COLOR) {\n $scope.setting12 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_FONT_PACKAGE) {\n $scope.setting13 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_GREETING_FORMAT) {\n $scope.setting14 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_GREETING_SUB_TEXT) {\n $scope.setting15 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_HERO_IMAGE) {\n $scope.setting16 = setting;\n } else if (setting.type === CompanySetting.CP_FEATURE_ORDERS) {\n $scope.setting17 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_HERO_IMAGE_TYPE) {\n $scope.setting18 = setting;\n } else if (setting.type === CompanySetting.CP_LOOK_PORTAL_LOGO_URL && setting.value) {\n $scope.portalLogoUrl = setting.value;\n } else if (setting.type === CompanySetting.CP_ADV_GENERAL_HIDE_BREADCRUMB) {\n $scope.setting19 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_DASHBOARD_HIDE_TIMELINE) {\n $scope.setting20 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_ORDERS_HIDE_PAYMENT_BUTTON) {\n $scope.setting21 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_ORDERS_HIDE_PRODUCT_IMAGE) {\n $scope.setting22 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_ITEM_NAME) {\n $scope.setting23 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_VENDOR_NAME) {\n $scope.setting24 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_PRICE_DISPLAY) {\n $scope.setting25 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_DESIGNER_FILTER) {\n $scope.setting26 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_CATEGORY_FILTER) {\n $scope.setting27 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_PRICE_FILTER) {\n $scope.setting28 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_SILHOUETTE_FILTER) {\n $scope.setting29 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_NOTES) {\n $scope.setting30 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_LOOKBOOK_HIDE_IN_STOCK_LABEL) {\n $scope.setting31 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_MEASUREMENTS_HIDE_GUIDE) {\n $scope.setting32 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_SHOW_GENDER_NEUTRAL_MEASUREMENTS_GUIDE) {\n $scope.setting33 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_MEASUREMENTS_AGREEMENT) {\n $scope.setting34 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_MEASUREMENTS_HELP_TEXT) {\n $scope.setting35 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_DASHBOARD_HIDE_HERO_IMAGE_AND_GREETING) {\n $scope.setting36 = setting;\n } else if (setting.type === CompanySetting.CP_ADV_EM_HIDE_MEMBERS_PROGRESS_FROM_OTHER_MEMBERS) {\n $scope.setting37 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_EVENT_TYPES_TO_DISPLAY) {\n $scope.setting38 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_EVENT_TYPE_BTN_1) {\n $scope.setting39 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_EVENT_TYPE_BTN_2) {\n $scope.setting40 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_DONT_REQUIRE_EMAIL_ON_ADD_MEMBER_SECTION) {\n $scope.setting41 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_HELP_TEXT) {\n $scope.setting42 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_HIDE_LOOKBOOK_WHEN_PURCHASE_HISTORY) {\n $scope.setting43 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_MEASUREMENTS_WOMEN_GUIDE_IMAGE_URL) {\n $scope.setting44 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_MEASUREMENTS_MEN_GUIDE_IMAGE_URL) {\n $scope.setting45 = setting;\n } else if(setting.type === CompanySetting.CP_LOOK_GREETING_TEXT_COLOR) {\n $scope.setting46 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_MIN_RANGE_PRICE) {\n $scope.setting47 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_MAX_RANGE_PRICE) {\n $scope.setting48 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_VENDOR_CODE) {\n $scope.setting49 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_ALLOW_CONTACT_HEAD_VIEW_DETAILS) {\n $scope.setting50 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_ANY_PRICE_FILTER) {\n $scope.setting51 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_SHOW_ORDER_LVL_DISCOUNT) {\n $scope.setting52 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_HIDE_ORDER_NOTES) {\n $scope.setting53 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_ROLE_TYPES_TO_DISPLAY) {\n $scope.setting54 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_SIZES) {\n $scope.setting55 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_COLORS) {\n $scope.setting56 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_SHOW_BUDGET_REGARDLESS_OF_TYPE) {\n $scope.setting57 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_SHOW_BUDGET_RANGE_FIELD) {\n $scope.setting58 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_EM_REQUIRE_BUDGET) {\n $scope.setting59 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_SHOW_SIGNED_SALES_AGREEMENTS) {\n $scope.setting60 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_HIDE_FAVORITES) {\n $scope.setting61 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_ADDITIONAL_IMAGES) {\n $scope.setting62 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_SHOW_TIP_AMOUNT) {\n $scope.setting63 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_TIP_OPTIONS) {\n $scope.setting64 = setting;\n $scope.tipSettings = JSON.parse($scope.setting64.value);\n } else if(setting.type === CompanySetting.CP_FEATURE_BILLING) {\n $scope.setting65 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_BILLING_PAYMENT_RECEIPT) {\n if(setting.value) {\n setting.value = +setting.value;\n }\n $scope.setting66 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_BILLING_INVOICE_RECEIPT) {\n if(setting.value) {\n setting.value = +setting.value;\n }\n $scope.setting67 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_SHOW_RECEIPT) {\n $scope.setting68 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_BILLING_UPDATE_PAYMENT) {\n $scope.setting69 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_BILLING_SHOW_PAYMENT_HISTORY) {\n $scope.setting70 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_SHOW_ITEM_ADD_ONS) {\n $scope.setting71 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_GENERAL_SELF_HELP_TEXT) {\n $scope.setting72 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_GENERAL_SELF_REG_REDIRECT) {\n $scope.setting73 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_LOOKBOOK_SHOW_TEXT_SEARCH_BOX) {\n $scope.setting74 = setting;\n } else if(setting.type === CompanySetting.CP_ADV_ORDERS_ENABLE_PLATFORM_FEE_AMOUNT) {\n $scope.setting75 = setting;\n }\n });\n\n if (!$scope.setting1) {\n $scope.setting1 = createSetting(CompanySetting.CP_FEATURE_PIN_ENABLED, 'N');\n }\n\n if (!$scope.setting2) {\n $scope.setting2 = createSetting(CompanySetting.CP_FEATURE_PAYMENTS, 'Y');\n }\n\n if (!$scope.setting3) {\n $scope.setting3 = createSetting(CompanySetting.CP_FEATURE_EVENT_MANAGEMENT, 'Y');\n }\n\n if (!$scope.setting4) {\n $scope.setting4 = createSetting(CompanySetting.CP_FEATURE_APPOINTMENTS, 'Y');\n }\n\n if (!$scope.setting5) {\n $scope.setting5 = createSetting(CompanySetting.CP_FEATURE_LOOKBOOK, 'Y');\n }\n\n if (!$scope.setting6) {\n $scope.setting6 = createSetting(CompanySetting.CP_FEATURE_DIGITAL_SIGNATURES, 'Y');\n }\n\n if (!$scope.setting7) {\n $scope.setting7 = createSetting(CompanySetting.CP_FEATURE_MEASUREMENTS, 'Y');\n }\n\n if (!$scope.setting8) {\n $scope.setting8 = createSetting(CompanySetting.CP_LOOK_COLOR_THEME, 'BL');\n }\n\n if (!$scope.setting9) {\n $scope.setting9 = createSetting(CompanySetting.CP_LOOK_HEADER_FOOTER_BG_COLOR, '');\n }\n\n if (!$scope.setting10) {\n $scope.setting10 = createSetting(CompanySetting.CP_LOOK_HEADER_FOOTER_TEXT_COLOR, '');\n }\n\n if (!$scope.setting11) {\n $scope.setting11 = createSetting(CompanySetting.CP_LOOK_PRIMARY_BTN_BG_COLOR, '');\n }\n\n if (!$scope.setting12) {\n $scope.setting12 = createSetting(CompanySetting.CP_LOOK_PRIMARY_BTN_TEXT_COLOR, '');\n }\n\n if (!$scope.setting13) {\n $scope.setting13 = createSetting(CompanySetting.CP_LOOK_FONT_PACKAGE, 'Default');\n }\n\n if (!$scope.setting14) {\n $scope.setting14 = createSetting(CompanySetting.CP_LOOK_GREETING_FORMAT, 'WCN');\n }\n\n if (!$scope.setting15) {\n $scope.setting15 = createSetting(CompanySetting.CP_LOOK_GREETING_SUB_TEXT, 'Let\\'s get ready for your special day!');\n }\n\n if (!$scope.setting16) {\n $scope.setting16 = createSetting(CompanySetting.CP_LOOK_HERO_IMAGE, '/assets/img/hero-image-1.jpg');\n }\n\n if (!$scope.setting17) {\n $scope.setting17 = createSetting(CompanySetting.CP_FEATURE_ORDERS, 'Y');\n }\n\n if(!$scope.setting18) {\n $scope.setting18 = createSetting(CompanySetting.CP_LOOK_HERO_IMAGE_TYPE, '1');\n }\n\n if(!$scope.setting19) {\n $scope.setting19 = createSetting(CompanySetting.CP_ADV_GENERAL_HIDE_BREADCRUMB, 'N');\n }\n\n if(!$scope.setting20) {\n $scope.setting20 = createSetting(CompanySetting.CP_ADV_DASHBOARD_HIDE_TIMELINE, 'N');\n }\n\n if(!$scope.setting21) {\n $scope.setting21 = createSetting(CompanySetting.CP_ADV_ORDERS_HIDE_PAYMENT_BUTTON, 'N');\n }\n\n if(!$scope.setting22) {\n $scope.setting22 = createSetting(CompanySetting.CP_ADV_ORDERS_HIDE_PRODUCT_IMAGE, 'N');\n }\n\n if(!$scope.setting23) {\n $scope.setting23 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_ITEM_NAME, 'N');\n }\n\n if(!$scope.setting24) {\n $scope.setting24 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_VENDOR_NAME, 'N');\n }\n\n if(!$scope.setting25) {\n $scope.setting25 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_PRICE_DISPLAY, 'A');\n }\n\n if(!$scope.setting26) {\n $scope.setting26 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_DESIGNER_FILTER, 'N');\n }\n\n if(!$scope.setting27) {\n $scope.setting27 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_CATEGORY_FILTER, 'N');\n }\n\n if(!$scope.setting28) {\n $scope.setting28 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_PRICE_FILTER, 'N');\n }\n\n if(!$scope.setting29) {\n $scope.setting29 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_SILHOUETTE_FILTER, 'N');\n }\n\n if(!$scope.setting30) {\n $scope.setting30 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_NOTES, 'Y');\n }\n\n if(!$scope.setting31) {\n $scope.setting31 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HIDE_IN_STOCK_LABEL, 'N');\n }\n\n if(!$scope.setting32) {\n $scope.setting32 = createSetting(CompanySetting.CP_ADV_MEASUREMENTS_HIDE_GUIDE, 'N');\n }\n\n if(!$scope.setting33) {\n $scope.setting33 = createSetting(CompanySetting.CP_ADV_SHOW_GENDER_NEUTRAL_MEASUREMENTS_GUIDE, 'N');\n }\n\n if(!$scope.setting34) {\n $scope.setting34 = createSetting(CompanySetting.CP_ADV_MEASUREMENTS_AGREEMENT, '');\n }\n\n if(!$scope.setting35) {\n $scope.setting35 = createSetting(CompanySetting.CP_ADV_MEASUREMENTS_HELP_TEXT, '');\n }\n\n if(!$scope.setting36) {\n $scope.setting36 = createSetting(CompanySetting.CP_ADV_DASHBOARD_HIDE_HERO_IMAGE_AND_GREETING, 'N');\n }\n\n if(!$scope.setting37) {\n $scope.setting37 = createSetting(CompanySetting.CP_ADV_EM_HIDE_MEMBERS_PROGRESS_FROM_OTHER_MEMBERS, 'N');\n }\n\n if(!$scope.setting38) {\n $scope.setting38 = createSetting(CompanySetting.CP_ADV_EM_EVENT_TYPES_TO_DISPLAY, 'ALL');\n }\n\n if(!$scope.setting39) {\n $scope.setting39 = createSetting(CompanySetting.CP_ADV_EM_EVENT_TYPE_BTN_1, '1');\n }\n\n if(!$scope.setting40) {\n $scope.setting40 = createSetting(CompanySetting.CP_ADV_EM_EVENT_TYPE_BTN_2, '3');\n }\n\n if(!$scope.setting41) {\n $scope.setting41 = createSetting(CompanySetting.CP_ADV_EM_DONT_REQUIRE_EMAIL_ON_ADD_MEMBER_SECTION, 'N');\n }\n\n if(!$scope.setting42) {\n $scope.setting42 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_HELP_TEXT, '');\n }\n\n if(!$scope.setting43) {\n $scope.setting43 = createSetting(CompanySetting.CP_ADV_HIDE_LOOKBOOK_WHEN_PURCHASE_HISTORY, 'N');\n }\n\n if(!$scope.setting44) {\n $scope.setting44 = createSetting(CompanySetting.CP_ADV_MEASUREMENTS_WOMEN_GUIDE_IMAGE_URL, '');\n }\n\n if(!$scope.setting45) {\n $scope.setting45 = createSetting(CompanySetting.CP_ADV_MEASUREMENTS_MEN_GUIDE_IMAGE_URL, '');\n }\n\n if(!$scope.setting46) {\n $scope.setting46 = createSetting(CompanySetting.CP_LOOK_GREETING_TEXT_COLOR, '#FFFFFF');\n }\n\n if(!$scope.setting47) {\n $scope.setting47 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_MIN_RANGE_PRICE, '5000');\n }\n\n if(!$scope.setting48) {\n $scope.setting48 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_MAX_RANGE_PRICE, '5000');\n }\n\n if(!$scope.setting49) {\n $scope.setting49 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_VENDOR_CODE, 'N');\n }\n\n if(!$scope.setting50) {\n $scope.setting50 = createSetting(CompanySetting.CP_ADV_EM_ALLOW_CONTACT_HEAD_VIEW_DETAILS, 'N');\n }\n\n if(!$scope.setting51) {\n $scope.setting51 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_ANY_PRICE_FILTER, 'N');\n }\n\n if(!$scope.setting52) {\n $scope.setting52 = createSetting(CompanySetting.CP_ADV_ORDERS_SHOW_ORDER_LVL_DISCOUNT, 'N');\n }\n\n if(!$scope.setting53) {\n $scope.setting53 = createSetting(CompanySetting.CP_ADV_ORDERS_HIDE_ORDER_NOTES, 'Y');\n }\n\n if(!$scope.setting54) {\n $scope.setting54 = createSetting(CompanySetting.CP_ADV_EM_ROLE_TYPES_TO_DISPLAY, 'ALL');\n }\n\n if(!$scope.setting55) {\n $scope.setting55 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_SIZES, 'N');\n }\n\n if(!$scope.setting56) {\n $scope.setting56 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_COLORS, 'N');\n }\n\n if(!$scope.setting57) {\n $scope.setting57 = createSetting(CompanySetting.CP_ADV_EM_SHOW_BUDGET_REGARDLESS_OF_TYPE, 'N');\n }\n\n if(!$scope.setting58) {\n $scope.setting58 = createSetting(CompanySetting.CP_ADV_EM_SHOW_BUDGET_RANGE_FIELD, 'N');\n }\n\n if(!$scope.setting59) {\n $scope.setting59 = createSetting(CompanySetting.CP_ADV_EM_REQUIRE_BUDGET, 'N');\n }\n\n if(!$scope.setting60) {\n $scope.setting60 = createSetting(CompanySetting.CP_ADV_ORDERS_SHOW_SIGNED_SALES_AGREEMENTS, 'N');\n }\n\n if(!$scope.setting61) {\n $scope.setting61 = createSetting(CompanySetting.CP_ADV_HIDE_FAVORITES, 'N');\n }\n\n if(!$scope.setting62) {\n $scope.setting62 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_DISPLAY_ADDITIONAL_IMAGES, 'N');\n }\n\n if(!$scope.setting63) {\n $scope.setting63 = createSetting(CompanySetting.CP_ADV_ORDERS_SHOW_TIP_AMOUNT, 'N');\n }\n\n if(!$scope.setting64) {\n $scope.setting64 = createSetting(CompanySetting.CP_ADV_ORDERS_TIP_OPTIONS, JSON.stringify(Enums.tipAmounts));\n $scope.tipSettings = JSON.parse($scope.setting64.value);\n }\n\n if(!$scope.setting65) {\n $scope.setting65 = createSetting(CompanySetting.CP_FEATURE_BILLING, 'N');\n }\n\n if(!$scope.setting66) {\n $scope.setting66 = createSetting(CompanySetting.CP_ADV_BILLING_PAYMENT_RECEIPT, '');\n }\n\n if(!$scope.setting67) {\n $scope.setting67 = createSetting(CompanySetting.CP_ADV_BILLING_INVOICE_RECEIPT, '');\n }\n\n if(!$scope.setting68) {\n $scope.setting68 = createSetting(CompanySetting.CP_ADV_ORDERS_SHOW_RECEIPT, '');\n }\n\n if(!$scope.setting69) {\n $scope.setting69 = createSetting(CompanySetting.CP_ADV_BILLING_UPDATE_PAYMENT, 'N');\n }\n\n if(!$scope.setting70) {\n $scope.setting70 = createSetting(CompanySetting.CP_ADV_BILLING_SHOW_PAYMENT_HISTORY, 'N');\n }\n\n if(!$scope.setting71) {\n $scope.setting71 = createSetting(CompanySetting.CP_ADV_ORDERS_SHOW_ITEM_ADD_ONS, 'N');\n }\n\n if(!$scope.setting72) {\n $scope.setting72 = createSetting(CompanySetting.CP_ADV_GENERAL_SELF_HELP_TEXT, '');\n }\n\n if(!$scope.setting73) {\n $scope.setting73 = createSetting(CompanySetting.CP_ADV_GENERAL_SELF_REG_REDIRECT, 'dashboard');\n }\n\n if(!$scope.setting74) {\n $scope.setting74 = createSetting(CompanySetting.CP_ADV_LOOKBOOK_SHOW_TEXT_SEARCH_BOX, 'N');\n }\n\n if(!$scope.setting75) {\n $scope.setting75 = createSetting(CompanySetting.CP_ADV_ORDERS_ENABLE_PLATFORM_FEE_AMOUNT, 'N');\n }\n\n // Filter the event type drop-downs for the event type buttons based on the multi select values\n populateEventTypesMultiSelect($scope.setting38);\n $scope.filteredEventTypes = filterEventTypeArrays($scope.setting38);\n\n populateRoleTypesMultiSelect($scope.setting54);\n\n // Populate the default colors when settings page loads.\n populateDefaultColors($scope);\n\n // Set the Font Example Container when settings page loads.\n setFont($scope.setting13);\n\n // Cache the hero image if the user has a custom hero image. Do this so if we change the hero image type, we can show the user their previous image.\n cacheHeroImage($scope.setting18);\n\n cache.set($scope.setting1, $scope.setting2, $scope.setting3, $scope.setting4, $scope.setting5, $scope.setting6, $scope.setting7, $scope.setting8, $scope.setting9,\n $scope.setting10, $scope.setting11, $scope.setting12, $scope.setting13, $scope.setting14, $scope.setting15, $scope.setting16, $scope.setting17, $scope.setting18,\n $scope.setting19, $scope.setting20, $scope.setting21, $scope.setting22, $scope.setting23, $scope.setting24, $scope.setting25, $scope.setting26, $scope.setting27,\n $scope.setting28, $scope.setting29, $scope.setting30, $scope.setting31, $scope.setting32, $scope.setting33, $scope.setting34, $scope.setting35, $scope.setting36,\n $scope.setting37, $scope.setting38, $scope.setting39, $scope.setting40, $scope.setting41, $scope.setting42, $scope.setting43, $scope.setting44, $scope.setting45,\n $scope.setting46, $scope.setting47, $scope.setting48, $scope.setting49, $scope.setting50, $scope.setting51, $scope.setting52, $scope.setting53, $scope.setting54,\n $scope.setting55, $scope.setting56, $scope.setting57, $scope.setting58, $scope.setting59, $scope.setting60, $scope.setting61, $scope.setting62, $scope.setting63,\n $scope.setting64, $scope.setting65, $scope.setting66, $scope.setting67, $scope.setting68, $scope.setting69, $scope.setting70, $scope.setting71, $scope.setting72,\n $scope.setting73, $scope.setting74, $scope.setting75);\n\n }\n\n function createSetting(type, defaultValue) {\n return {\n type: type,\n description: 'Client Portal Setting',\n value: defaultValue,\n createdByUser: User.username,\n createdDate: new Date()\n };\n }\n\n populateScopeWithSettings($scope, settings);\n\n /**\n * Methods\n */\n\n $scope.onSaveClick = function (callback) {\n if ($scope.clientPortalSettingsForm.$invalid) {\n return modalService.showInvalidFormError('Client Portal Settings');\n }\n\n if ($scope.onSaveClick.isInProgress) {\n // quit if save is already going on\n return;\n }\n\n if(validatePriceRange($scope)) {\n modalService.showMessageStaticModal('client-portal.messages.required-min-max-price-values-error-message', 'settings.employee.message.ooops');\n return;\n }\n\n if(validateMinMax($scope)) {\n modalService.showMessageStaticModal('client-portal.messages.required-min-greater-than-max-error-message', 'settings.employee.message.ooops');\n return;\n }\n\n if(validateIncrement($scope.setting47.value, $scope.setting48.value)) {\n modalService.showMessageStaticModal('client-portal.messages.required-min-max-increment-100-error-message', 'settings.employee.message.ooops');\n return;\n }\n\n if(validateTipSettings($scope.tipSettings)) {\n modalService.showMessageStaticModal('client-portal.messages.tip-settings-error-message', 'settings.employee.message.ooops');\n return;\n }\n\n if(validateRedirectSetting($scope.setting73, getAllDashboardViewSettings($scope))) {\n modalService.showMessageStaticModal('settings.client-portal-preferences.advanced-settings.general.redirect-setting-error-message', 'settings.employee.message.ooops');\n return;\n }\n\n $scope.setting64.value = JSON.stringify($scope.tipSettings);\n\n $scope.onSaveClick.isInProgress = true;\n\n CompanySetting.batchUpdate([$scope.setting1, $scope.setting2, $scope.setting3, $scope.setting4, $scope.setting5, $scope.setting6,\n $scope.setting7, $scope.setting8, $scope.setting9, $scope.setting10, $scope.setting11, $scope.setting12,\n $scope.setting13, $scope.setting14, $scope.setting15, $scope.setting16, $scope.setting17, $scope.setting18,\n $scope.setting19, $scope.setting20, $scope.setting21, $scope.setting22, $scope.setting23, $scope.setting24,\n $scope.setting25, $scope.setting26, $scope.setting27, $scope.setting28, $scope.setting29, $scope.setting30,\n $scope.setting31, $scope.setting32, $scope.setting33, $scope.setting34, $scope.setting35, $scope.setting36,\n $scope.setting37, $scope.setting38, $scope.setting39, $scope.setting40, $scope.setting41, $scope.setting42,\n $scope.setting43, $scope.setting44, $scope.setting45, $scope.setting46, $scope.setting47, $scope.setting48,\n $scope.setting49, $scope.setting50, $scope.setting51, $scope.setting52, $scope.setting53, $scope.setting54,\n $scope.setting55, $scope.setting56, $scope.setting57, $scope.setting58, $scope.setting59, $scope.setting60,\n $scope.setting61, $scope.setting62, $scope.setting63, $scope.setting64, $scope.setting65, $scope.setting66,\n $scope.setting67, $scope.setting68, $scope.setting69, $scope.setting70, $scope.setting71, $scope.setting72,\n $scope.setting73, $scope.setting74, $scope.setting75], function (updatedModels) {\n populateScopeWithSettings($scope, updatedModels);\n alerts.push('alerts.settings.successfully-saved-client-portal-preferences');\n callback && callback();\n }, null).finally(function () {\n $scope.onSaveClick.isInProgress = false;\n $scope.clientPortalSettingsForm.$setPristine();\n });\n };\n $scope.onSaveClick.isInProgress = false;\n\n $scope.onSettingChanged = function (setting) {\n setting.modifiedByUser = User.username;\n setting.modifiedDate = new Date();\n };\n\n function validatePriceRange($scope) {\n var min = $scope.setting47 && $scope.setting47.value ? +$scope.setting47.value : undefined;\n var max = $scope.setting48 && $scope.setting48.value ? +$scope.setting48.value : undefined;\n\n if(!$scope.setting47 || !$scope.setting48 || !$scope.setting47.value || !$scope.setting48.value) {\n return true;\n } else if(min < 100 || min > 1000000) {\n return true;\n } else if(max < 100 || max > 1000000) {\n return true;\n }\n\n return false;\n }\n\n function validateIncrement(min, max) {\n var remainder = (max - min) % 100;\n\n if(remainder !== 0) {\n return true;\n }\n\n return false;\n }\n\n function validateTipSettings(settings) {\n if(settings && settings.length) {\n for(var i = 0; i < settings.length; i++) {\n var setting = settings[i];\n\n if(setting.type === 'P' && (setting.value < 0 || setting.value > 100)) {\n return true;\n } else if(setting.type === 'D' && setting.value < 0) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n function validateRedirectSetting(setting, views) {\n if(!setting || !setting.value) {\n return false;\n }\n\n return !views[setting.value];\n }\n\n function validateMinMax($scope) {\n var min = +$scope.setting47.value;\n var max = +$scope.setting48.value;\n return min > max;\n }\n\n $scope.validateMinMaxValues = function () {\n return validateMinMax($scope);\n };\n\n function validateMaxMin($scope) {\n var min = +$scope.setting47.value;\n var max = +$scope.setting48.value;\n return max > min;\n }\n\n $scope.validateMaxMinValues = function () {\n return validateMaxMin($scope);\n };\n\n function getAllDashboardViewSettings(scope) {\n return {\n lookbook: (scope && scope.setting5 && scope.setting5.value === 'Y'),\n billing: (scope && scope.setting65 && scope.setting65.value === 'Y'),\n events: (scope && scope.setting3 && scope.setting3.value === 'Y'),\n orders: (scope && scope.setting17 && scope.setting17.value === 'Y'),\n measurements: (scope && scope.setting7 && scope.setting7.value === 'Y'),\n appointments: (scope && scope.setting4 && scope.setting4.value === 'Y'),\n dashboard: true\n };\n }\n\n $scope.copyUrl = function() {\n Contact.generatePortalRegistrationURL(function(response) {\n var el = document.createElement('textarea');\n el.value = response.url;\n el.setAttribute('readonly', '');\n el.style.position = 'absolute';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n document.body.removeChild(el);\n alerts.push('alerts.settings.successfully-copied-self-registration-link');\n });\n };\n\n /**\n * Portal Event Management functions\n */\n\n $scope.installDefaultSteps = function () {\n PortalEventManagementStep.installDefaultEventManagementSteps(function (installedSteps) {\n $scope.resources = sortSteps(installedSteps);\n modalService.showMessage('alerts.settings.successfully-installed-default-event-management-step');\n });\n };\n\n $scope.onAddClick = function (e) {\n e.preventDefault();\n\n return $modal.open({\n templateUrl: modalTemplate,\n controller: function ($scope) {\n $scope.modelName = obj.modelName;\n\n //allow for prefilled resources\n if(!obj.modalScope.defaultModel) {\n $scope.model = new PortalEventManagementStep();\n } else {\n $scope.model = angular.copy(obj.modalScope.defaultModel, new PortalEventManagementStep());\n }\n\n angular.extend($scope, obj.modalScope);\n }\n }).result.then(function (r) {\n var deferred = $q.defer();\n\n var max;\n\n if($scope.resources.length) {\n max = _.max($scope.resources, function(resource) { return resource.sequenceNumber; } );\n }\n\n if(max) {\n r.sequenceNumber = max.sequenceNumber + 1;\n } else {\n r.sequenceNumber = 0;\n }\n\n r.createdByUser = User.username;\n r.$save(function (newResource) {\n $scope.resources.push(newResource);\n deferred.resolve(newResource);\n });\n\n return deferred.promise;\n });\n };\n\n $scope.onEditClick = function (r) {\n var index = _.indexOf($scope.resources, r);\n $modal.open({\n templateUrl: modalTemplate,\n controller: function ($scope) {\n $scope.modelName = obj.modelName;\n $scope.model = new PortalEventManagementStep();\n angular.extend($scope, modalScope);\n angular.extend($scope.model, r);\n\n $scope.$broadcast('show-errors-check-validity');\n }\n }).result.then(function (r) {\n r.modifiedByUser = User.username;\n r.modifiedDate = new Date();\n _(r).omit('checked').$update(function (updatedResource) {\n alerts.push('alerts.settings.successfully-updated-event-management-step');\n $scope.resources[index] = updatedResource;\n });\n });\n };\n\n $scope.sortableOptions = {\n tolerance: 'pointer',\n revert: 100,\n distance: 5,\n forcePlaceholderSize: true,\n handle: \".handle\",\n helper: function (e, ui) {\n ui.children().each(function () {\n $(this).width($(this).width());\n });\n\n return ui;\n },\n stop: function () {\n // Reindex line items\n _($scope.resources).each(function (line, index) {\n line.sequenceNumber = index;\n line.modifiedDate = Date.now();\n line.modifiedByUser = User.username;\n });\n\n $scope.saveSteps();\n }\n };\n\n $scope.tipOptions = {\n tolerance: 'pointer',\n revert: 100,\n distance: 5,\n forcePlaceholderSize: true,\n handle: \".handle\",\n helper: function (e, ui) {\n ui.children().each(function () {\n $(this).width($(this).width());\n });\n\n return ui;\n },\n stop: function () {\n // Reindex line items\n _($scope.tipSettings).each(function (line, index) {\n line.id = index + 1;\n line.modifiedDate = Date.now();\n line.modifiedByUser = User.username;\n });\n\n $scope.clientPortalSettingsForm.$setDirty();\n }\n };\n\n $scope.onTipValueChanged = _.debounce(function(value) {\n if(!value || value < 0) {\n return;\n }\n\n $scope.clientPortalSettingsForm.$setDirty();\n }, 1000);\n\n $scope.saveSteps = _.debounce(function () {\n PortalEventManagementStep.batchUpdate($scope.resources, function (steps) {\n alerts.push('alerts.settings.successfully-updated-event-management-step');\n $scope.resources = sortSteps(steps);\n });\n }, 1000);\n\n function sortSteps(steps) {\n return _.sortBy(steps, function (step) {\n return step.sequenceNumber;\n });\n }\n\n /**\n * Price Display Methods\n */\n\n $scope.showBreakdown = function() {\n $scope.ranges = [];\n\n $scope.breakdown = false;\n Item.getLookBookPriceRanges(function (ranges) {\n $scope.breakdown = true;\n $scope.ranges = ranges;\n });\n };\n\n $scope.hideBreakdown = function() {\n $scope.breakdown = false;\n };\n \n /**\n * Portal logo functions\n */\n\n $scope.uploadPortalLogo = function (event) {\n event.stopPropagation();\n\n var files = event.target.files;\n if(files && files[0]) {\n var file = files[0];\n\n if(file.name.indexOf('.png') === -1 && file.name.indexOf('.jpg') === -1 && file.name.indexOf('.gif') === -1 && file.name.indexOf('.jpeg') === -1\n && file.name.indexOf('.PNG') === -1 && file.name.indexOf('.JPG') === -1 && file.name.indexOf('.GIF') === -1 && file.name.indexOf('.JPEG') === -1) {\n modalService.showMessage('settings.point-of-sale.file-type-not-allowed');\n return;\n }\n\n var formData = new FormData();\n formData.append('Content-Type', file.type);\n formData.append('file', file);\n formData.append('userName', User.username);\n\n $scope.status = $translate.instant('settings.point-of-sale.uploading-logo');\n\n CompanySetting.uploadPortalLogo(formData, function() {\n alerts.pushSuccess('alerts.settings.successfully-uploaded-logo');\n $scope.portalLogoUrl = CompanySetting.getPortalLogoUrl() + '?bust=' + Math.random();\n $scope.status = \"\";\n event.target.value = '';\n }, function () {\n event.target.value = '';\n });\n }\n };\n\n $scope.deletePortalLogo = function (e) {\n e.preventDefault();\n\n CompanySetting.deletePortalLogo(function() {\n alerts.pushSuccess('alerts.settings.successfully-deleted-logo');\n $scope.portalLogoUrl = null;\n $scope.status = \"\";\n });\n };\n\n /**\n * Image upload functions & Preview Image functions\n */\n\n $scope.uploadPicture = function (event) {\n uploadPictureWithSetting(event, $scope.setting16, true);\n };\n\n $scope.uploadPictureWomenMeasurementsGuide = function(event) {\n uploadPictureWithSetting(event, $scope.setting44, false);\n };\n\n $scope.uploadPictureMenMeasurementsGuide = function(event) {\n uploadPictureWithSetting(event, $scope.setting45, false);\n };\n\n var s3Policy = null;\n\n function uploadPictureWithSetting(event, setting, cacheImage) {\n event.stopPropagation();\n var file = event.target.files[0];\n\n if(file) {\n $scope.portal.mediaUrlObj = {\n _ui: {\n file: file,\n status: 'new',\n percentage: 0,\n index: 0\n }\n };\n\n if(!s3Policy) {\n s3Policy = SMS.getS3Policy().then(function (result) {\n uploadImage(result, setting, cacheImage);\n s3Policy = result;\n });\n } else {\n uploadImage(s3Policy, setting, cacheImage);\n }\n }\n }\n\n function uploadImage(s3Policy, setting, cacheImage) {\n $scope.portal.mediaUrlObj._ui.status = 'uploading';\n Sms.uploadImage(0, $scope.portal.mediaUrlObj, s3Policy, function (image) {\n\n $scope.portal.mediaUrlObj = image;\n if ($scope.portal.mediaUrlObj._ui.status !== 'error') {\n setting.value = image.imageUrl;\n\n if(cacheImage) {\n cachedHeroImage = image.imageUrl;\n }\n\n $scope.portal.mediaUrlObj._ui.percentage = 100;\n $scope.portal.mediaUrlObj._ui.status = 'done';\n $scope.onSettingChanged(setting);\n\n $scope.clientPortalSettingsForm.$setDirty();\n }\n }, function (image) {\n $scope.$apply(function () {\n $scope.portal.mediaUrlObj._ui.status = 'error';\n $scope.portal.mediaUrlObj._ui.errorMessage = image._ui.errorMessage;\n });\n });\n }\n\n $scope.removeImage = function (setting) {\n setting.value = '';\n setting.modifiedByUser = User.username;\n setting.modifiedDate = new Date();\n\n $scope.onSettingChanged(setting);\n $scope.clientPortalSettingsForm.$setDirty();\n };\n\n $scope.onHeroImageTypeChanged = function (setting) {\n if(setting && setting.value) {\n var image = _(Enums.heroImageTypes).findWhere({ id: setting.value });\n\n if(image && image.value) {\n $scope.setting16.value = image.value;\n $scope.onSettingChanged($scope.setting16);\n } else if(setting.value === '0') {\n $scope.setting16.value = cachedHeroImage;\n $scope.onSettingChanged($scope.setting16);\n }\n\n $scope.onSettingChanged(setting);\n $scope.clientPortalSettingsForm.$setDirty();\n }\n };\n\n $scope.openPicturesGalleryModal = function (imageUrl) {\n var pictures = [];\n\n if(_.isArray(imageUrl)) {\n if(imageUrl.length > 1) {\n _(imageUrl).each(function(image) {\n pictures.push({url: image});\n });\n } else if(imageUrl.length) {\n pictures.push({url: imageUrl[0] });\n }\n } else {\n pictures.push({ url: imageUrl });\n }\n\n if(pictures.length) {\n ItemModal.openPicturesGallery(pictures);\n }\n };\n\n /**\n * Theme functions\n */\n\n $scope.previewPortal = function (e) {\n if(e) {\n e.preventDefault();\n }\n\n ContactModal.openPreviewPortal(settings);\n };\n\n $scope.onColorThemeChanged = function (setting) {\n if(setting && setting.value) {\n setColors(setting.value);\n $scope.onSettingChanged(setting);\n }\n };\n\n $scope.onFontPackChanged = function (setting) {\n if(setting && setting.value) {\n setFont(setting);\n $scope.onSettingChanged(setting);\n }\n };\n\n function setFont(setting) {\n ColorThemeService.initFontExample(setting);\n }\n\n function setColors(themeId) {\n var theme;\n\n if(themeId) {\n theme = _(PORTAL_COLOR_THEMES).findWhere({ id: themeId });\n }\n\n if(theme) {\n // Set themes\n $scope.setting9.value = theme.headerFooterBG;\n $scope.setting10.value = theme.headerFooterText;\n $scope.setting11.value = theme.primaryBtnBG;\n $scope.setting12.value = theme.primaryBtnText;\n }\n }\n\n function populateDefaultColors($scope) {\n if($scope.setting8 && $scope.setting8.value && (!$scope.setting9.value || !$scope.setting10.value || !$scope.setting11.value || !$scope.setting12.value)) {\n setColors($scope.setting8.value);\n }\n }\n\n var cachedHeroImage;\n\n function cacheHeroImage(setting) {\n if(setting && setting.value) {\n var type = _(Enums.heroImageTypes).findWhere({ id: setting.value });\n\n if(type && type.id === setting.value) {\n cachedHeroImage = $scope.setting16.value;\n }\n }\n }\n\n /**\n * Portal Message Settings functions\n */\n\n $scope.onPortalMessageSettingChanged = function (setting) {\n setting.modifiedByUser = User.username;\n setting.modifiedDate = new Date();\n\n setting.$update(function (updatedSetting) {\n setting.version = updatedSetting.version;\n });\n };\n\n function installPortalMessageSettings() {\n PortalMessageSetting.installDefaultPortalMessageSettings(function (settings) {\n $scope.messageSettings = settings;\n });\n }\n\n $scope.onEventTypeChanged = function () {\n var types = _.where($scope.eventTypes, { ticked: true });\n\n if(types && types.length) {\n var typeIds = _.pluck(types, \"value\");\n $scope.setting38.value = typeIds.join(\",\");\n } else {\n $scope.setting38.value = 'ALL';\n }\n\n $scope.filteredEventTypes = filterEventTypeArrays($scope.setting38);\n $scope.onSettingChanged($scope.setting38);\n $scope.clientPortalSettingsForm.$setDirty();\n };\n\n $scope.onRoleTypeChanged = function () {\n var types = _.where($scope.roleTypes, { ticked: true });\n\n if(types && types.length) {\n var typeIds = _.pluck(types, \"value\");\n $scope.setting54.value = typeIds.join(\",\");\n } else {\n $scope.setting54.value = 'ALL';\n }\n\n $scope.onSettingChanged($scope.setting54);\n $scope.clientPortalSettingsForm.$setDirty();\n };\n\n /**\n * Helpers\n */\n\n $scope.validateIncrement = function (min, max) {\n return validateIncrement(min, max);\n };\n\n function filterEventTypeArrays(setting) {\n var filteredArray = [];\n\n if(!setting || !setting.value || setting.value === 'ALL') {\n filteredArray = $scope.eventTypes;\n }\n\n var eventTypeIds = setting.value.split(\",\");\n\n if(!eventTypeIds || !eventTypeIds.length) {\n filteredArray = $scope.eventTypes;\n }\n\n if(!filteredArray || !filteredArray.length) {\n filteredArray = _(Enums.eventTypes).filter(function (type) {\n return _.contains(eventTypeIds, type.id);\n });\n }\n\n if(filteredArray && filteredArray.length) {\n var eventTypeBtn1 = _(filteredArray).findWhere({ id: $scope.setting40.value });\n\n if(!eventTypeBtn1) {\n $scope.setting39.value = filteredArray[0].id;\n }\n\n if(!$scope.setting39.value) {\n $scope.setting39.value = filteredArray[0].id;\n }\n\n var eventTypeBtn2 = _(filteredArray).findWhere({ id: $scope.setting40.value });\n\n if(!eventTypeBtn2) {\n $scope.setting40.value = filteredArray.length > 1 ? filteredArray[1].id : filteredArray[0].id;\n }\n\n if(!$scope.setting40.value) {\n $scope.setting40.value = filteredArray.length > 1 ? filteredArray[1].id : filteredArray[0].id;\n }\n }\n\n return filteredArray;\n }\n\n function populateEventTypesMultiSelect(setting) {\n $scope.eventTypes = [];\n\n var ids = null;\n\n if(setting && setting.value && setting.value !== 'ALL') {\n ids = setting.value.split(\",\");\n }\n\n _(Enums.eventTypes).each(function (type) {\n type.description = $translate.instant(type.description);\n\n if(ids) {\n type.ticked = _.contains(ids, type.value.toString());\n }\n\n $scope.eventTypes.push(type);\n });\n }\n\n function populateRoleTypesMultiSelect(setting) {\n $scope.roleTypes = [];\n\n var ids = null;\n\n if(setting && setting.value && setting.value !== 'ALL') {\n ids = setting.value.split(\",\");\n }\n\n _(Enums.roleTypes).each(function (type) {\n type.description = $translate.instant(type.description);\n\n if(ids) {\n type.ticked = _.contains(ids, type.value.toString());\n }\n\n $scope.roleTypes.push(type);\n });\n }\n\n function initializeCardHeight() {\n var currHeight = 0;\n var tallestHeight = 0;\n\n var cards = document.querySelectorAll('.card');\n var textContent = document.querySelectorAll('.text-content');\n\n _(cards).each(function (card) {\n currHeight = card.clientHeight;\n if (currHeight > tallestHeight) {\n tallestHeight = currHeight;\n }\n });\n\n angular.element('.card').height(tallestHeight);\n\n currHeight = 0;\n tallestHeight = 0;\n\n _(textContent).each(function (content) {\n currHeight = content.clientHeight;\n if (currHeight > tallestHeight) {\n tallestHeight = currHeight;\n }\n });\n\n angular.element('.text-content').height(tallestHeight);\n }\n \n\n function isInvalidMergeField(field) {\n var str = field.key;\n\n if(!String.prototype.startsWith) {\n console.log('IE browser');\n String.prototype.startsWith = function(searchString, position) {\n position = position || 0;\n return this.indexOf(searchString, position) === position;\n };\n }\n\n if(str.startsWith(\"[Appointment\")) {\n return true;\n } else if(str.startsWith(\"[POSTransaction\")) {\n return true;\n } else if (str.startsWith(\"[Vendor\")) {\n return true;\n } else if (str.startsWith(\"[InterviewWidgetSettings\")) {\n return true;\n } else if (str.startsWith(\"[Employee\")) {\n return true;\n } else if(str.startsWith(\"[PurchaseOrder\")) {\n return true;\n }\n\n return false;\n }\n\n function buildTemplates(emailTemps, smsTemps) {\n $scope.emailTemplates = emailTemps;\n $scope.smsTemplates = smsTemps;\n\n var invalidMergeFields = _(MergeFields).filter(isInvalidMergeField);\n\n // Loop through the templates, and if you find a template that contains a merge field that isn't valid, remove that template.\n if(invalidMergeFields && invalidMergeFields.length) {\n if (emailTemps && emailTemps.length) {\n _.each(invalidMergeFields, function (mergeField) {\n _.each(emailTemps, function (template) {\n if (template && template.body) {\n if (includesValue(template.body, mergeField.key)) {\n $scope.emailTemplates = _($scope.emailTemplates).without(template);\n }\n }\n });\n });\n }\n\n if(smsTemps && smsTemps.length) {\n _.each(invalidMergeFields, function (mergeField) {\n _.each(smsTemps, function (template) {\n if (template && template.body) {\n if (includesValue(template.body, mergeField.key)) {\n $scope.smsTemplates = _($scope.smsTemplates).without(template);\n }\n }\n });\n });\n }\n }\n }\n\n function includesValue(container, value) {\n var returnValue = false;\n var pos = container.indexOf(value);\n if (pos >= 0) {\n returnValue = true;\n }\n return returnValue;\n }\n\n /**\n * Watches\n */\n\n var ignoreRedirectWarning = false;\n\n $scope.$on('$stateChangeStart', function (event, toState, toParams) {\n var continueNavigation = function () {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, { location: 'replace' });\n };\n\n if (!ignoreRedirectWarning && cache.isDifferent()) {\n event.preventDefault();\n\n modalService.showStaticConfirmation('item.message.do-you-want-changes-to-be-saved?',\n function () {\n $scope.onSaveClick(continueNavigation);\n },\n continueNavigation\n );\n }\n });\n });\n});\n\n","define('modules/settings/payment-plans/index',[\n 'angular',\n 'underscore',\n 'equals',\n '../../resources/payment-plan',\n '../../resources/payment-plan-template',\n '../../resources/company-setting',\n '../../resources/sms-template',\n '../../resources/email-template',\n '../../resources/twilio-settings',\n '../settings-list-ctrl'\n], function (angular, _, equals) {\n 'use strict';\n\n return angular.module('bl.settings.payment-plans', [\n 'bl.resources.payment-plan',\n 'bl.resources.payment-plan-template',\n 'bl.resources.company-setting',\n 'bl.resources.sms-template',\n 'bl.resources.email-template',\n 'bl.resources.twilio-settings',\n 'bl.settings.settings-list-ctrl'\n ])\n\n .config(function ($stateProvider, Permissions, Enums) {\n var template = function (name) {\n return 'js/modules/settings/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('settings.payment_plans', {\n url: '/payment_plans',\n permissions: Permissions.SETTINGS_POS,\n views: {\n content: {\n controller: 'SettingsPaymentPlansCtrl',\n templateUrl: template('payment-plans/main'),\n resolve: {\n settings: function (CompanySetting) {\n return CompanySetting.getPaymentPlanSettings();\n },\n smsTemplates: function (SMSTemplate) {\n return SMSTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n emailTemplates: function (EmailTemplate) {\n return EmailTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n twilioSettings: function (TwilioSettings) {\n return TwilioSettings.get();\n }\n }\n }\n }\n });\n })\n\n .controller('SettingsPaymentPlansCtrl', function ($scope, $controller, $translate, $locale,$q, $modal, alerts, AppLicense, MergeFields, PaymentPlan, PaymentPlanTemplate, CompanySetting, modalService, security, User, Enums, settings, twilioSettings, emailTemplates, smsTemplates) {\n /**\n * Init Payment Plan Settings\n */\n\n $scope.twilioSettings = twilioSettings;\n $scope.licenseHasDigitalSignatureFeature = AppLicense.get().plan['digitalSignature'];\n $scope.readonlyTemplates = !security.hasPermission('EDIT_PAYMENT_PLAN_TEMPLATES');\n\n buildTemplates(emailTemplates, smsTemplates);\n\n // storage to know when user changes data\n var cache = {\n setting1: null,\n setting2: null,\n setting3: null,\n setting4: null,\n setting5: null,\n setting6: null,\n setting7: null,\n setting8: null,\n setting9: null,\n set: function (setting1, setting2, setting3, setting4, setting5, setting6, setting7, setting8, setting9) {\n cache.setting1 = angular.copy(setting1);\n cache.setting2 = angular.copy(setting2);\n cache.setting3 = angular.copy(setting3);\n cache.setting4 = angular.copy(setting4);\n cache.setting5 = angular.copy(setting5);\n cache.setting6 = angular.copy(setting6);\n cache.setting7 = angular.copy(setting7);\n cache.setting8 = angular.copy(setting8);\n cache.setting9 = angular.copy(setting9);\n },\n isDifferent: function () {\n return !equals(cache.setting1, $scope.setting1)\n || !equals(cache.setting2, $scope.setting2)\n || !equals(cache.setting3, $scope.setting3)\n || !equals(cache.setting4, $scope.setting4)\n || !equals(cache.setting5, $scope.setting5)\n || !equals(cache.setting6, $scope.setting6)\n || !equals(cache.setting7, $scope.setting7)\n || !equals(cache.setting8, $scope.setting8)\n || !equals(cache.setting9, $scope.setting9);\n }\n };\n\n function populateScopeWithSettings($scope, settings) {\n _.each(settings, function (setting) {\n if (setting.type === CompanySetting.POS_PP_NUMBER_OF_RETRIES) {\n $scope.setting1 = setting;\n } else if (setting.type === CompanySetting.POS_PP_SUCCESS_EMAIL_TEMPLATE) {\n $scope.setting2 = setting;\n $scope.setting2.value = Number($scope.setting2.value);\n } else if (setting.type === CompanySetting.POS_PP_SUCCESS_SMS_TEMPLATE) {\n $scope.setting3 = setting;\n $scope.setting3.value = Number($scope.setting3.value);\n } else if (setting.type === CompanySetting.POS_PP_ERROR_EMAIL_TEMPLATE) {\n $scope.setting4 = setting;\n $scope.setting4.value = Number($scope.setting4.value);\n } else if (setting.type === CompanySetting.POS_PP_ERROR_SMS_TEMPLATE) {\n $scope.setting5 = setting;\n $scope.setting5.value = Number($scope.setting5.value);\n }\n else if (setting.type === CompanySetting.POS_PP_REMINDER_EMAIL_TEMPLATE) {\n $scope.setting6 = setting;\n $scope.setting6.value = Number($scope.setting6.value);\n }\n else if (setting.type === CompanySetting.POS_PP_REMINDER_SMS_TEMPLATE) {\n $scope.setting7 = setting;\n $scope.setting7.value = Number($scope.setting7.value);\n }\n else if (setting.type === CompanySetting.POS_PP_DAYS_BEFORE_REMINDER) {\n $scope.setting8 = setting;\n $scope.setting8.value = Number($scope.setting8.value);\n }\n else if (setting.type === CompanySetting.POS_PP_REQUIRE_SIG) {\n $scope.setting9 = setting;\n\n if(!$scope.licenseHasDigitalSignatureFeature) {\n $scope.setting19.value = 'N';\n }\n }\n });\n\n if (!$scope.setting1) {\n $scope.setting1 = createSetting(CompanySetting.POS_PP_NUMBER_OF_RETRIES, '3');\n }\n\n if (!$scope.setting2) {\n $scope.setting2 = createSetting(CompanySetting.POS_PP_SUCCESS_EMAIL_TEMPLATE, '');\n }\n\n if (!$scope.setting3) {\n $scope.setting3 = createSetting(CompanySetting.POS_PP_SUCCESS_SMS_TEMPLATE, '');\n }\n\n if (!$scope.setting4) {\n $scope.setting4 = createSetting(CompanySetting.POS_PP_ERROR_EMAIL_TEMPLATE, '');\n }\n\n if (!$scope.setting5) {\n $scope.setting5 = createSetting(CompanySetting.POS_PP_ERROR_SMS_TEMPLATE, '');\n }\n\n if (!$scope.setting6) {\n $scope.setting6 = createSetting(CompanySetting.POS_PP_REMINDER_EMAIL_TEMPLATE, '');\n }\n\n if (!$scope.setting7) {\n $scope.setting7 = createSetting(CompanySetting.POS_PP_REMINDER_SMS_TEMPLATE, '');\n }\n\n if (!$scope.setting8) {\n $scope.setting8 = createSetting(CompanySetting.POS_PP_DAYS_BEFORE_REMINDER, '3');\n }\n\n if (!$scope.setting9) {\n $scope.setting9 = createSetting(CompanySetting.POS_PP_REQUIRE_SIG, 'N');\n }\n\n cache.set($scope.setting1, $scope.setting2, $scope.setting3, $scope.setting4, $scope.setting5, $scope.setting6, $scope.setting7, $scope.setting8, $scope.setting9);\n }\n\n function createSetting(type, defaultValue) {\n return {\n type: type,\n description: 'Payment Plan Setting',\n value: defaultValue,\n createdByUser: User.username,\n createdDate: new Date()\n };\n }\n\n populateScopeWithSettings($scope, settings);\n\n /**\n * Init Payment Plan Template Modal\n */\n\n var modalTemplate = 'js/modules/settings/payment-plans/modal.tpl.html';\n\n function initNewTemplate() {\n return angular.extend(new PaymentPlanTemplate(), {\n description: '',\n autoPay: true,\n installmentNumber: 6,\n downPaymentPercentage: 50,\n frequencyType: 'M',\n interestRatePercentage: 0,\n successEmailTemplateId: $scope.setting2 && $scope.setting2.value,\n successSMSTemplateId: $scope.setting3 && $scope.setting3.value,\n errorEmailTemplateId: $scope.setting4 && $scope.setting4.value,\n errorSMSTemplateId: $scope.setting5 && $scope.setting5.value,\n reminderEmailTemplateId: $scope.setting6 && $scope.setting6.value,\n reminderSMSTemplateId: $scope.setting7 && $scope.setting7.value,\n daysBeforeReminder: $scope.setting8 && $scope.setting8.value ? $scope.setting8.value : 3,\n requiresSignature: $scope.setting9 && $scope.setting9.value,\n status: Enums.statuses.active.value\n });\n }\n\n var model = initNewTemplate();\n\n var modalScope = {\n defaultModel: model,\n twilioSettings: $scope.twilioSettings,\n emailTemplates: $scope.emailTemplates,\n smsTemplates: $scope.smsTemplates,\n frequencies: Enums.paymentPlanFrequencies,\n showDescription: true,\n showFirstInstallmentDate: false,\n currencySymbol: $locale.NUMBER_FORMATS.CURRENCY_SYM,\n showStatus: true,\n readonlyTemplates: false\n };\n\n var obj = { $scope: $scope, Resource: PaymentPlanTemplate, modelName: $translate.instant('settings.payment-plans.payment-plan-template'), modalTemplate: modalTemplate, modalScope: modalScope };\n angular.extend(this, $controller('SettingsListCtrl', obj));\n\n\n /**\n * Methods\n */\n\n $scope.onAddClick = function (e) {\n if(e) {\n e.preventDefault();\n }\n\n modalScope.defaultModel = initNewTemplate();\n\n return $modal.open({\n templateUrl: obj.modalTemplate,\n controller: function ($scope) {\n $scope.modelName = obj.modelName;\n\n //allow for prefilled resources\n if(!modalScope.defaultModel) {\n $scope.model = new PaymentPlanTemplate();\n } else {\n $scope.model = angular.copy(modalScope.defaultModel, new PaymentPlanTemplate());\n }\n\n angular.extend($scope, modalScope);\n }\n }).result.then(function (r) {\n var deferred = $q.defer();\n\n r.status = Enums.statuses.active.value;\n r.createdByUser = User.username;\n r.$save(function (newResource) {\n $scope.resources.push(newResource);\n deferred.resolve(newResource);\n });\n\n return deferred.promise;\n });\n };\n\n $scope.onSaveClick = function (callback) {\n if($scope.preferencesForm.$invalid) {\n return modalService.showInvalidFormError('Payment Plan Settings');\n }\n\n if($scope.onSaveClick.isInProgress) {\n // quit if save is already going on\n return;\n }\n\n $scope.onSaveClick.isInProgress = true;\n\n CompanySetting.batchUpdate([$scope.setting1, $scope.setting2, $scope.setting3, $scope.setting4, $scope.setting5, $scope.setting6, $scope.setting7, $scope.setting8, $scope.setting9], function(updatedModels) {\n populateScopeWithSettings($scope, updatedModels);\n alerts.push('alerts.settings.successfully-saved-payment-plan-settings');\n callback && callback();\n }, null).finally(function () {\n $scope.onSaveClick.isInProgress = false;\n $scope.preferencesForm.$setPristine();\n });\n };\n $scope.onSaveClick.isInProgress = false;\n\n $scope.onSettingChanged = function (setting) {\n setting.modifiedByUser = User.username;\n setting.modifiedDate = new Date();\n };\n\n /**\n * Helpers\n */\n\n function buildTemplates(emailTemps, smsTemps) {\n $scope.emailTemplates = [];\n $scope.smsTemplates = [];\n\n var planMergeFields = _.union(_.where(MergeFields, { paymentPlanRequired: true }), _.where(MergeFields, { billingRequest: true }));\n\n if(emailTemps && emailTemps.length) {\n _.each(planMergeFields, function (mergeField) {\n _.each(emailTemps, function (template) {\n if (template && template.body) {\n if(includesValue(template.body, mergeField.key)) {\n if($scope.emailTemplates.indexOf(template) === -1) {\n $scope.emailTemplates.push(template);\n }\n }\n }\n });\n });\n }\n\n if(smsTemps && smsTemps.length) {\n _.each(planMergeFields, function (mergeField) {\n _.each(smsTemps, function (template) {\n if (template && template.body) {\n if(includesValue(template.body, mergeField.key)) {\n if($scope.smsTemplates.indexOf(template) === -1) {\n $scope.smsTemplates.push(template);\n }\n }\n }\n });\n });\n }\n }\n\n function includesValue(container, value) {\n var returnValue = false;\n var pos = container.indexOf(value);\n if (pos >= 0) {\n returnValue = true;\n }\n return returnValue;\n }\n\n\n /**\n * Watches\n */\n\n var ignoreRedirectWarning = false;\n\n $scope.$on('$stateChangeStart', function (event, toState, toParams) {\n var continueNavigation = function () {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, { location: 'replace' });\n };\n\n if (!ignoreRedirectWarning && cache.isDifferent()) {\n event.preventDefault();\n\n modalService.showStaticConfirmation('item.message.do-you-want-changes-to-be-saved?',\n function () {\n $scope.onSaveClick(continueNavigation);\n },\n continueNavigation\n );\n }\n });\n\n });\n});\n\n","define('modules/settings/commissions/index',[\n 'angular',\n 'equals',\n 'underscore',\n 'moment',\n './add-sales-per-hour-commission-modal-ctrl',\n '../../alerts/index',\n '../../modal/modal-service',\n '../../resources/company-setting',\n '../../resources/sales-per-hour-commission',\n '../../security/user-service'\n], function(angular, equals, _, moment) {\n 'use strict';\n\n return angular.module('bl.settings.commissions', [\n 'bl.alerts',\n 'bl.modal',\n 'bl.resources.company-setting',\n 'bl.security.user',\n 'bl.resources.sales-per-hour-commission',\n 'bl.settings.commissions.sales-per-hour-modal-ctrl'\n ])\n\n .config(function($stateProvider, Permissions, Enums) {\n var template = function(name) {\n return 'js/modules/settings/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('settings.commissions', {\n url: '/commissions',\n permissions: Permissions.SETTINGS_POS,\n views: {\n content: {\n controller: 'SettingsCommissionsCtrl',\n templateUrl: template('commissions/main'),\n resolve: {\n settings: function(CompanySetting) {\n return CompanySetting.getCommissionSettings();\n },\n employees: function(Employee) {\n return Employee.query({sortField: 'firstName', filterObject:{status:Enums.statuses.active.value}});\n },\n departments: function(Department) {\n return Department.query({sortField: 'name', filterObject:{status:Enums.statuses.active.value}});\n },\n salesPerHourCommissions: function(SalesPerHourCommission) {\n return SalesPerHourCommission.query({ sortField: 'minSalesPerHour' });\n }\n }\n }\n }\n });\n })\n\n .controller('SettingsCommissionsCtrl', function($scope, $state, $window, $translate, UtilsHelper, settings, departments, employees, Enums, CompanySetting, alerts, modalService, User, security, salesPerHourCommissions, $modal) {\n\n $scope.commissionEvalTypes = Enums.commissionEvalTypes;\n $scope.evaluationTypes = Enums.evaluationTypes;\n $scope.commissionDetails = null;\n\n var salesPerHour;\n // storage to know when user changes data\n var cache = {\n setting1: null,\n setting2: null,\n setting3:null,\n setting4: null,\n setting5: null,\n employees: null,\n departments: null,\n set: function(setting1, setting2, setting3, setting4, setting5) {\n cache.setting1 = angular.copy(setting1);\n cache.setting2 = angular.copy(setting2);\n cache.setting3 = angular.copy(setting3);\n cache.setting4 = angular.copy(setting4);\n cache.setting5 = angular.copy(setting5);\n },\n isDifferent: function() {\n return !equals(cache.setting1, $scope.setting1)\n || !equals(cache.setting2, $scope.setting2)\n || !equals(cache.setting3, $scope.setting3)\n || !equals(cache.setting4, $scope.setting4)\n || !equals(cache.employees, $scope.employees)\n || !equals(cache.departments, $scope.departments)\n || !equals(cache.setting5, $scope.setting5);\n }\n };\n\n populateScopeWithSettings($scope, settings);\n $scope.departments = departments.result;\n $scope.employees = employees;\n $scope.salesPerHourCommissions = salesPerHourCommissions && salesPerHourCommissions.result;\n salesPerHour = angular.copy($scope.salesPerHourCommissions);\n populateCommissionDetails([settings, $scope.salesPerHourCommissions]);\n\n cache.departments = $scope.departments;\n cache.employees = $scope.employees;\n\n function populateScopeWithSettings($scope, settings) {\n _.each(settings, function(setting) {\n if(setting.type === CompanySetting.COMM_PAID_WHEN) {\n $scope.setting1 = setting;\n } else if(setting.type === CompanySetting.COMM_BASED_ON) {\n $scope.setting2 = setting;\n } else if(setting.type === CompanySetting.COMM_LEVELS) {\n $scope.setting3 = setting;\n } else if(setting.type === CompanySetting.COMM_STATUS) {\n $scope.setting4 = setting;\n } else if(setting.type === CompanySetting.SPH_DEFAULT) {\n $scope.setting5 = setting;\n }\n });\n\n if(!$scope.setting1) {\n $scope.setting1 = createSetting(CompanySetting.COMM_PAID_WHEN, '0');\n }\n\n if(!$scope.setting2) {\n $scope.setting2 = createSetting(CompanySetting.COMM_BASED_ON, 'D');\n }\n\n if(!$scope.setting3) {\n $scope.setting3 = createSetting(CompanySetting.COMM_LEVELS, 'N');\n }\n\n if(!$scope.setting4) {\n $scope.setting4 = createSetting(CompanySetting.COMM_STATUS, 'N');\n }\n\n if(!$scope.setting5) {\n $scope.setting5 = createSetting(CompanySetting.SPH_DEFAULT, 'PB');\n }\n\n cache.set($scope.setting1, $scope.setting2, $scope.setting3, $scope.setting4, $scope.setting5);\n }\n\n function populateCommissionDetails(arrays, oldValues) {\n $scope.commissionDetails = UtilsHelper.buildCreatedAndModifiedObj(arrays, oldValues);\n }\n\n function createSetting(type, defaultValue) {\n return { type: type, description: 'Commission Setting', value: defaultValue, createdByUser: User.username, createdDate: new Date() };\n }\n\n /**\n * Methods\n */\n\n $scope.savePercentage = function(savePerHourCommission) {\n savePerHourCommission.$update(function(updatedCommission) {\n _(savePerHourCommission).extend(updatedCommission);\n alerts.push('alerts.settings.successfully-updated-commission-settings');\n });\n };\n\n $scope.savePercentageValidate = function(percent) {\n if(percent > 99.9 || percent < 0) {\n modalService.showMessage('alerts.commission.percentage-error-message', 'alerts.appointment.percentage-input');\n return false;\n } else {\n return true;\n }\n };\n\n $scope.addNewSPHCommission = function() {\n $modal.open({\n templateUrl: 'js/modules/settings/commissions/add-sales-per-hour-commission-modal.tpl.html',\n controller: 'SalesPerHourCommissionModalCtrl',\n resolve: {\n salesPerHourCommissions: function() {\n return $scope.salesPerHourCommissions;\n }\n }\n }).result.then(function(response) {\n if(response) {\n $scope.salesPerHourCommissions.push(response);\n $scope.salesPerHourCommissions = sortSalesPerHour($scope.salesPerHourCommissions);\n salesPerHour = angular.copy($scope.salesPerHourCommissions);\n }\n });\n };\n\n $scope.deleteSPHCommission = function(s) {\n var salesPerHour = angular.copy(s);\n salesPerHour.$remove( function() {\n $scope.salesPerHourCommissions = _($scope.salesPerHourCommissions).without(s);\n salesPerHour = angular.copy($scope.salesPerHourCommissions);\n alerts.push('alerts.settings.commissions.successfully-deleted-sales-per-hour-range');\n });\n };\n\n $scope.salesPerHourSave = function(salePerHour) {\n if(salePerHour.minSalesPerHour < 0 || salePerHour.minSalesPerHour === null || salePerHour.minSalesPerHour === undefined\n || salePerHour.maxSalesPerHour < 0 || salePerHour.maxSalesPerHour === null || salePerHour.maxSalesPerHour === undefined\n || salePerHour.maxSalesPerHour <= salePerHour.minSalesPerHour || salePerHour.commissionPercent === null || salePerHour.commissionPercent === undefined\n || salePerHour.commissionPercent < 0 || salePerHour.commissionPercent > 100) {\n modalService.showMessage('alerts.settings.commissions.generic-sph-error', 'modal-service.error');\n setToPreviousValues(salePerHour);\n return;\n }\n\n var errorMessage = '';\n\n _.each($scope.salesPerHourCommissions, function(commissionSetting) {\n if(!errorMessage && salePerHour.id !== commissionSetting.id) {\n if((commissionSetting.minSalesPerHour < +salePerHour.minSalesPerHour && commissionSetting.maxSalesPerHour > +salePerHour.maxSalesPerHour)\n || (salePerHour.maxSalesPerHour > +commissionSetting.minSalesPerHour && salePerHour.maxSalesPerHour <= +commissionSetting.maxSalesPerHour)\n || ( salePerHour.minSalesPerHour >= +commissionSetting.minSalesPerHour && salePerHour.minSalesPerHour < +commissionSetting.maxSalesPerHour )){\n errorMessage = 'alerts.settings.commissions.range-overlap';\n }\n }\n });\n\n if(errorMessage) {\n modalService.showMessage(errorMessage,'modal-service.error');\n setToPreviousValues(salePerHour);\n return;\n }\n\n if(salePerHour && salePerHour.id) {\n salePerHour.modifiedByUser = User.username;\n salePerHour.modifiedDate = new Date();\n } else {\n salePerHour.createdDate = new Date();\n salePerHour.createdByUser = User.username;\n }\n\n salePerHour.$saveOrUpdate(function(updatedCommission) {\n _(salePerHour).extend(updatedCommission);\n sortSalesPerHour($scope.salesPerHourCommissions);\n salesPerHour = angular.copy($scope.salesPerHourCommissions);\n alerts.push('alerts.settings.commissions.successfully-updated-sales-per-hour-range');\n populateCommissionDetails([$scope.salesPerHourCommissions], $scope.commissionDetails);\n });\n };\n\n $scope.salesPerHourValidate = function(salesPerHour) {\n if(salesPerHour < 0) {\n modalService.showMessage('alerts.commissions.percentage-error-message', 'alerts.appointment.percentage-input');\n return false;\n } else {\n return true;\n }\n };\n\n function setToPreviousValues(current) {\n var previous = _(salesPerHour).findWhere({ id: current && current.id });\n\n if(previous && previous.id) {\n current.minSalesPerHour = previous.minSalesPerHour;\n current.maxSalesPerHour = previous.maxSalesPerHour;\n current.commissionPercent = previous.commissionPercent;\n }\n }\n\n $scope.onSaveClick = function() {\n if($scope.preferencesForm.$invalid) {\n return modalService.showInvalidFormError('Commissions Settings');\n }\n\n if($scope.onSaveClick.isInProgress) {\n // quit if save is already going on\n return;\n }\n\n $scope.onSaveClick.isInProgress = true;\n\n CompanySetting.batchUpdate([$scope.setting1, $scope.setting2, $scope.setting3, $scope.setting4, $scope.setting5], function(updatedModels) {\n populateScopeWithSettings($scope, updatedModels);\n populateCommissionDetails([updatedModels], $scope.commissionDetails);\n alerts.push('alerts.settings.successfully-saved-commission');\n }, null).finally(function() {\n $scope.onSaveClick.isInProgress = false;\n $scope.preferencesForm.$setPristine();\n });\n };\n\n $scope.onSaveClick.isInProgress = false;\n\n $scope.onSettingChanged = function(setting) {\n setting.modifiedByUser = User.username;\n setting.modifiedDate = new Date();\n };\n\n function sortSalesPerHour(salePerHours) {\n return _.sortBy(salePerHours, function(sale) {\n return sale.minSalesPerHour;\n });\n }\n\n /**\n * Watches\n */\n\n var ignoreRedirectWarning = false;\n\n $scope.$on('$stateChangeStart', function(event, toState, toParams) {\n var continueNavigation = function() {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, { location: 'replace' });\n };\n\n if(!ignoreRedirectWarning && cache.isDifferent()) {\n event.preventDefault();\n\n modalService.showStaticConfirmation(\n 'item.message.do-you-want-changes-to-be-saved?',\n function() {\n $scope.onSaveClick(continueNavigation);\n },\n continueNavigation\n );\n }\n });\n\n });\n});\n\n","define('modules/settings/quickbooks/qbo-helper-service',['angular', 'underscore'], function (angular, _) {\n 'use strict';\n\n angular.module('bl.qbo.helper', [])\n\n .service('QBOHelper', function (QBConstants) {\n String.prototype.capitalize = function() {\n return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();\n };\n\n function grpAccounts(subAccounts, sortedArray, indentCount, retryCount) {\n var temps = sortedArray;\n sortedArray = [];\n _.each(temps, function(parent) {\n sortedArray.push(parent);\n\n _.each(subAccounts, function(sub) {\n if(sub && sub.parent && sub.parent.id === parent.id) {\n sub.label = buildSubLabel(indentCount) + sub.name;\n sortedArray.push(sub);\n subAccounts = _(subAccounts).without(sub);\n }\n });\n });\n\n if(retryCount > 4) {\n sortedArray.push({ id: null, type: null, label: '------- Sub Accounts with no parent -------', name: '------- Sub Accounts with no parent -------' });\n sortedArray = sortedArray.concat(subAccounts);\n return sortedArray;\n }\n\n indentCount += 4;\n retryCount++;\n\n if(subAccounts && subAccounts.length) {\n return grpAccounts(subAccounts, sortedArray, indentCount, retryCount);\n } else {\n return sortedArray;\n }\n }\n\n function buildSubLabel(indentCount) {\n if(!indentCount) {\n indentCount = 4;\n }\n\n var label = \"\";\n\n for(var i = 0; i < indentCount; i++) {\n label += \" \";\n }\n\n return label + \"-- \";\n }\n\n function formatAccountType(account) {\n var type = account.accountType;\n type = type.replace(/_/g, ' ');\n type = type.capitalize();\n type = \"(\" + type + \")\";\n return type;\n }\n\n function hasAccountType(acc, sort) {\n return !!(acc && acc.accountType && acc.accountType.length > 1 && (!sort || (sort && acc.subAccount)));\n }\n\n function isSubAccount(acc, sort) {\n return sort && !!(acc && acc.subAccount);\n }\n\n function buildLabel(account, sort, subLabel) {\n return isSubAccount(account, sort) ? subLabel + account.name : account.name;\n }\n\n function buildType(account, sort) {\n return hasAccountType(account, sort) ? formatAccountType(account) : '';\n }\n\n function buildParent(account) {\n var parent = null;\n\n if(account.parentRef && account.parentRef.value) {\n parent = { id: account.parentRef.value };\n }\n\n return parent;\n }\n\n var service = {\n mapQuickBooksAccounts: function(accounts, sort) {\n var subLabel = buildSubLabel();\n\n var qbAccounts = _.map(accounts, function(account) {\n return {\n id: account.id,\n name: account.name,\n label: buildLabel(account, sort, subLabel),\n type: buildType(account, sort),\n subAccount: account.subAccount,\n parent: buildParent(account)\n };\n });\n\n if(sort) {\n var subAccounts = _(qbAccounts).where({ subAccount: true });\n var parentAccounts = _(qbAccounts).where({ subAccount: false });\n var sortedArray = [];\n\n _.each(parentAccounts, function(parent) {\n sortedArray.push(parent);\n\n _.each(subAccounts, function(sub) {\n if(sub && sub.parent && sub.parent.id === parent.id) {\n sortedArray.push(sub);\n subAccounts = _(subAccounts).without(sub);\n }\n });\n });\n\n if(subAccounts && subAccounts.length) {\n sortedArray = grpAccounts(subAccounts, sortedArray, 8, 1);\n }\n\n return sortedArray;\n }\n\n return qbAccounts;\n },\n mapBridalLiveVendors: function(result) {\n var vendors = [];\n var linkedVendors = [];\n\n _.each(result.blVendors, function (blVendor) {\n var isVendorLinked = false;\n\n _.each(result.qbVendors, function (qbVendor) {\n if (blVendor.qbListId === qbVendor.id) {\n isVendorLinked = true;\n }\n });\n\n if(!isVendorLinked) {\n blVendor.qbListId = null;\n vendors.push(blVendor);\n }\n\n if(isVendorLinked) {\n linkedVendors.push(blVendor);\n }\n });\n\n if(linkedVendors.length) {\n _.each(linkedVendors, function (linkedVendor) {\n vendors.push(linkedVendor);\n });\n }\n\n return vendors;\n },\n mapBridalLiveAccounts: function(result) {\n var accounts = [];\n var linkedAccounts = [];\n\n _.each(result.blAccounts, function(blAccount) {\n var isAccountLinked = false;\n\n var linkedEntity;\n\n if(blAccount.blAccountName !== 'Sales Tax Vendor Name') {\n linkedEntity = _(result.qbAccounts).findWhere({ id: blAccount.qbListId });\n } else {\n linkedEntity = _(result.qbVendors).findWhere({ id: blAccount.qbListId });\n }\n\n if(linkedEntity) {\n isAccountLinked = true;\n }\n\n if(!isAccountLinked) {\n blAccount.qbListId = null;\n blAccount.qbAccountName = null;\n accounts.push(blAccount);\n }\n\n if(isAccountLinked) {\n linkedAccounts.push(blAccount);\n }\n });\n\n if(linkedAccounts.length) {\n _.each(linkedAccounts, function(linkedAccount) {\n accounts.push(linkedAccount);\n });\n }\n\n return accounts;\n },\n mapBridalLiveDepartments: function(result) {\n var departments = [];\n var linkedDepartments = [];\n\n _.each(result.blDepartments, function (blDept) {\n var isDeptLinked = 0;\n var hasIncomeSalesId = false;\n var hasIncomeSpecialOrderId = false;\n var hasCogsSalesId = false;\n var hasCogsSpecialOrderId = false;\n\n _.each(result.qbAccounts, function (qbAccount) {\n if (blDept.qbIncomeSaleId === qbAccount.id && qbAccount.accountType === QBConstants.INCOME_ACC_TYPE) {\n hasIncomeSalesId = true;\n isDeptLinked++;\n }\n\n if (blDept.qbIncomeSpecialOrderId === qbAccount.id && qbAccount.accountType === QBConstants.INCOME_ACC_TYPE) {\n hasIncomeSpecialOrderId = true;\n isDeptLinked++;\n }\n\n if (blDept.qbCogsSaleId === qbAccount.id && qbAccount.accountType === QBConstants.COGS_ACC_TYPE) {\n hasCogsSalesId = true;\n isDeptLinked++;\n }\n\n if (blDept.qbCogsSpecialOrderId === qbAccount.id && qbAccount.accountType === QBConstants.COGS_ACC_TYPE) {\n hasCogsSpecialOrderId = true;\n isDeptLinked++;\n }\n });\n\n if(isDeptLinked <= 3) {\n if(!hasIncomeSalesId) {\n blDept.qbIncomeSaleId = null;\n }\n\n if(!hasIncomeSpecialOrderId) {\n blDept.qbIncomeSpecialOrderId = null;\n }\n\n if(!hasCogsSalesId) {\n blDept.qbCogsSaleId = null;\n }\n\n if(!hasCogsSpecialOrderId) {\n blDept.qbCogsSpecialOrderId = null;\n }\n\n departments.push(blDept);\n }\n\n if(isDeptLinked > 3) {\n linkedDepartments.push(blDept);\n }\n });\n\n if(linkedDepartments.length) {\n _.each(linkedDepartments, function (linkedDept) {\n departments.push(linkedDept);\n });\n }\n\n return departments;\n },\n mapQBAccountsByType: function(result, accountType) {\n var accounts = [];\n\n _.each(result.qbAccounts, function (qbAccount) {\n if(qbAccount.accountType === accountType) {\n accounts.push(qbAccount);\n }\n });\n\n return service.mapQuickBooksAccounts(accounts, true);\n }\n };\n\n return service;\n });\n});\n\n","define('modules/settings/role/index',[\n 'angular',\n 'underscore',\n '../../common/enums-service',\n '../../modal/modal-service',\n '../../resources/role'\n], function (angular, _) {\n 'use strict';\n\n return angular.module('bl.settings.role', [\n 'bl.enums',\n 'bl.modal',\n 'bl.resources.role'\n ])\n\n .config(function ($stateProvider, Permissions) {\n var template = function (name) {\n return 'js/modules/settings/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('settings.role', {\n url: '/role',\n permissions: Permissions.USERS,\n views: {\n content: {\n controller: 'SettingsRoleCtrl',\n templateUrl: template('role/main')\n }\n }\n });\n })\n\n .controller('SettingsRoleCtrl', function ($scope, $modal, $rootScope, modalService, Enums, Role, Permissions, User, PERMISSIONS_ORDER, $locale) {\n\n Role.query(null, function (roles) {\n $scope.roles = roles;\n });\n\n $scope.isAdmin = function (role) {\n var isAdmin = false;\n\n if(role && role.description) {\n switch(role.description) {\n case'Administrator':\n // English\n isAdmin = true;\n break;\n case'Administrador':\n // Spanish\n isAdmin = true;\n break;\n case'Administrateur':\n // French\n isAdmin = true;\n break;\n case'Administrator':\n // German\n isAdmin = true;\n break;\n case'Beheerder':\n // Dutch\n isAdmin = true;\n break;\n case'Amministratore':\n // Italian\n isAdmin = true;\n break;\n }\n }\n\n return isAdmin;\n };\n\n $scope.onOpenModal = function (r) {\n var index = _.indexOf($scope.roles, r);\n $modal.open({\n templateUrl: 'js/modules/settings/role/modal.tpl.html',\n controller: function ($scope, role, AppLicense) {\n $scope.model = role;\n $scope.perms = angular.copy(Permissions.permissions);\n $scope.locale = $locale;\n\n //select the permissions that belong to this role\n if(role.permissions) {\n _.each(role.permissions, function (rolePerm){\n if($scope.perms[rolePerm.permissionKey]) {\n $scope.perms[rolePerm.permissionKey].selected = true;\n }\n });\n } else {\n role.permissions = [];\n }\n\n //TODO: Add back in the future after covid-19 is over.\n // if(!AppLicense.get()[\"clientPortalAccess\"]) {\n // $scope.perms = _($scope.perms).filter(function(perm) {\n // return perm.groupId !== Permissions.GROUP_CLIENT_PORTAL\n // && !perm.id.startsWith(\"CLIENT_PORTAL\");\n // });\n // }\n\n $scope.permGroups = _.groupBy($scope.perms, 'groupName');\n $scope.permGroups = initPermissionGroups($scope.permGroups);\n\n $scope.togglePermission = function(perm) {\n var role = $scope.model;\n var rolePerm = _.findWhere(role.permissions, {permissionKey: perm.id});\n\n if(perm.selected) {\n role.permissions.push({roleId: role.id, permissionKey:perm.id, createdByUser: User.username});\n\n if(!role.added) {\n role.added = [];\n }\n\n if(!_.contains(role.added, perm.id)) {\n role.added.push(perm.id);\n }\n\n if(_.contains(role.removed, perm.id)) {\n role.removed = _.filter(role.removed, function (val) {\n return val !== perm.id;\n });\n }\n\n } else {\n role.permissions = _.without(role.permissions, rolePerm);\n\n if(!role.removed) {\n role.removed = [];\n }\n\n if(!_.contains(role.removed, perm.id) ) {\n role.removed.push(perm.id);\n }\n\n if(_.contains(role.added, perm.id)) {\n role.added = _.filter(role.added, function (val) {\n return val !== perm.id;\n });\n }\n }\n };\n\n $scope.toggleAll = function (val) {\n _.each($scope.perms, function (perm) {\n perm.selected = val;\n });\n\n var role = $scope.model;\n\n if(!role.added) {\n role.added = [];\n }\n\n if(!role.removed) {\n role.removed = [];\n }\n\n if(val) {\n _.each($scope.perms, function (perm) {\n //just add the ones that aren't already on this role\n var rolePerm = _.findWhere(role.permissions, {permissionKey: perm.id});\n if(!rolePerm) {\n role.permissions.push({ roleId: role.id, permissionKey:perm.id, createdByUser: User.username });\n }\n });\n role.removed = [];\n role.added.push(\"All Permissions Added\");\n } else {\n role.added = [];\n role.removed.push(\"All Permissions Removed\");\n role.permissions = [];\n }\n };\n },\n resolve: {\n role: function () {\n if (r) {\n return Role.getById(r.id);\n } else {\n return new Role();\n }\n }\n }\n }).result.then(function (r) {\n if (r.id){\n r.modifiedByUser = User.username;\n r.modifiedDate = new Date();\n } else {\n r.status = Enums.statuses.active.value;\n r.createdByUser = User.username;\n }\n\n r.$saveOrUpdate(function (role) {\n if (r.id) {\n $scope.roles[index] = role;\n } else {\n $scope.roles.push(role);\n }\n });\n });\n };\n\n $scope.onRemoveClick = function (r) {\n modalService.showConfirmation('settings.role.would-like-to-delete?', function () {\n r.$remove(function () {\n $scope.roles = _($scope.roles).without(r);\n });\n });\n };\n\n function validPermission(validations) {\n return _(validations).filter(function(validation) {\n if(validation && validation.type === '$rootScope') {\n return $rootScope[validation.field];\n }\n\n return false;\n }).length;\n }\n\n function validateGroup(validate) {\n if(!validate || validate.length <= 0) {\n return true;\n }\n\n return validate.length === validPermission(validate);\n }\n\n function initPermissionGroups(unsortedGroups) {\n var sortedGroups = [];\n var filterLocalePerms = function(perm) {\n return !perm.locale || _.contains(perm.locale, $locale.id);\n };\n\n var filterValidatePerms = function(perm) {\n return !perm.validate || perm.validate.length === validPermission(perm.validate);\n };\n\n for(var i = 0; i < PERMISSIONS_ORDER.length; i++) {\n var key = PERMISSIONS_ORDER[i].label;\n var validate = PERMISSIONS_ORDER[i].validate;\n\n if(validateGroup(validate)) {\n var group = {};\n group.id = i;\n group.name = key;\n var filteredPerms = _(unsortedGroups[key]).filter(filterLocalePerms);\n filteredPerms = _(filteredPerms).filter(filterValidatePerms);\n group.perms = filteredPerms;\n sortedGroups.push(group);\n }\n }\n\n return sortedGroups;\n }\n });\n});\n\n","define('modules/settings/smart-flow/index',[\n 'angular',\n 'underscore',\n 'jquery',\n '../../modal/modal-service',\n '../../common/enums-service',\n '../../common/permissions-service',\n '../../common/resources-helper-service',\n '../../alerts/index',\n '../../resources/appointment-type',\n '../../resources/category',\n '../../resources/department',\n '../../resources/email-template',\n '../../resources/employee',\n '../../resources/mailchimp',\n '../../resources/smart-flow',\n '../../resources/sms-template',\n '../../resources/task-type',\n '../../security/user-service'\n], function (angular, _, $) {\n 'use strict';\n\n return angular.module('bl.settings.smart-flow', [\n 'bl.enums',\n 'bl.permissions',\n 'bl.modal',\n 'bl.alerts',\n 'bl.resources-helper',\n 'bl.resources.appointment-type',\n 'bl.resources.category',\n 'bl.resources.department',\n 'bl.resources.email-template',\n 'bl.resources.employee',\n 'bl.resources.mailchimp',\n 'bl.resources.smart-flow',\n 'bl.resources.sms-template',\n 'bl.resources.task-type',\n 'bl.security.user'\n ])\n\n .config(function ($stateProvider, Permissions) {\n var template = function (name) {\n return 'js/modules/settings/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('settings.smart_flow', {\n url: '/smart_flow',\n permissions: Permissions.SMART_FLOWS,\n views: {\n content: {\n controller: 'SettingsSmartFlowCtrl',\n templateUrl: template('smart-flow/main'),\n resolve: {\n flows: function (SmartFlow) {\n return SmartFlow.query();\n },\n departments: function (Enums, Department) {\n return Department.query({sortField: 'name', filterObject: {status: Enums.statuses.active.value}});\n },\n appointmentTypes: function (Enums, AppointmentType) {\n return AppointmentType.query({filterObject: {status: Enums.statuses.active.value}});\n }\n }\n }\n }\n });\n })\n\n .controller('SettingsSmartFlowCtrl', function (\n $scope,\n $timeout,\n $translate,\n $rootScope,\n Enums,\n flows,\n departments,\n appointmentTypes,\n SmartFlow,\n EmailTemplate,\n SMSTemplate,\n TaskType,\n Employee,\n Category,\n MailChimp,\n ResourcesHelper,\n alerts,\n User,\n $modal,\n modalService\n ) {\n\n var filter = { filterObject: { status: Enums.statuses.active.value }};\n\n /**\n * Init models\n */\n\n var triggers = _(Enums.smartFlowTriggers).values();\n\n if(!$rootScope.isQuoteEnabled) {\n triggers = _(triggers).without(Enums.smartFlowTriggers.quote_created);\n flows = _(flows).filter(removeQuoteFlows);\n }\n\n $scope.flows = flows;\n $scope.flow = $scope.selectedFlow = flows[0];\n $scope.appointmentTypes = appointmentTypes;\n $scope.triggers = triggers;\n $scope.emailTypeId = Enums.smartFlowActions.send_email.id;\n $scope.smsTypeId = Enums.smartFlowActions.send_sms.id;\n $scope.taskTypeId = Enums.smartFlowActions.create_task.id;\n $scope.actionTypeId = Enums.smartFlowActions.system_action.id;\n $scope.priorities = Enums.priorities;\n $scope.systemActions = Enums.smartFlowSystemActions;\n $scope.weddingPartyBehaviors = _(Enums.smartFlowWeddingPartyBehaviors).values();\n\n $scope.limitedSystemActions = _(Enums.smartFlowSystemActions).filter(function(action) {\n if(action.id == 3 || action.id == 6) {\n return true;\n }\n\n return false;\n });\n\n //add an All option to departments\n if(departments && departments.result) {\n $scope.departments = angular.copy(departments.result);\n $scope.departments.unshift({ id: 0, name: $translate.instant('common.all') });\n }\n\n EmailTemplate.query(filter, function (templates) {\n $scope.emailTemplates = templates;\n });\n\n SMSTemplate.query(filter, function (templates) {\n $scope.smsTemplates = templates;\n });\n\n TaskType.query(filter, function (taskTypes) {\n $scope.taskTypes = taskTypes;\n });\n\n Employee.query(angular.extend({ sortField: 'firstName' }, filter), function(emps) {\n if(emps) {\n $scope.default_employees = ResourcesHelper.adaptDefaultEmployeeListForSmartFlows(emps);\n emps = ResourcesHelper.adaptEmployeeListForSmartFlows(emps);\n }\n\n $scope.employees = emps;\n });\n\n Category.query(_({ sortField: 'description' }).extend(filter), function(cats) {\n $scope.categories = ResourcesHelper.adaptList('categories', cats);\n });\n\n MailChimp.lists(function(lists) {\n $scope.mcLists = lists;\n });\n\n /**\n * Methods\n */\n\n $scope.onAddClick = function() {\n $scope.flow = new SmartFlow();\n $scope.flow.createdByUser = User.username;\n $scope.flow.status = Enums.statuses.active.value;\n $scope.flow.triggerId = $scope.triggers[0].id;\n $scope.selectedFlow = null;\n };\n\n $scope.onSaveClick = function() {\n if(!$scope.flow.items || !$scope.flow.items.length) {\n modalService.showMessage('settings.smart-flow.message.add-at-least-one-step');\n return;\n }\n\n $scope.flow.$saveOrUpdate(function(savedFlow) {\n updateFlows(savedFlow);\n angular.extend($scope.flow, savedFlow);\n alerts.push('alerts.settings.successfully-saved-smart-flow');\n }, null);\n };\n\n $scope.onDeleteClick = function() {\n modalService.showConfirmation(\n 'settings.smart-flow.message.would-like-to-delete?',\n function() {\n $scope.flow.$remove(function() {\n $scope.flows = _($scope.flows).without(_($scope.flows).findWhere({ id: $scope.flow.id }));\n $scope.flow = null;\n alerts.push('alerts.settings.successfully-deleted-smart-flow');\n }, null);\n }\n );\n };\n\n $scope.onAddStep = function(actionTypeId) {\n $scope.flow.$saveOrUpdate(function(savedFlow) {\n updateFlows(savedFlow);\n angular.extend($scope.flow, savedFlow);\n addLineItem(actionTypeId);\n }, null);\n };\n\n $scope.onRemoveStep = function(fi) {\n modalService.showConfirmation(\n 'settings.smart-flow.message.would-like-to-delete-this-step?',\n function () {\n removeLineItem(fi);\n }\n );\n };\n\n $scope.getMilestonesForFlowItem = function(fi) {\n var apptDateIds = [\n Enums.smartFlowTriggers.appointment_added.id,\n Enums.smartFlowTriggers.appointment_completed.id,\n Enums.smartFlowTriggers.appointment_no_show.id,\n Enums.smartFlowTriggers.appointment_no_show.id,\n Enums.smartFlowTriggers.appointment_canceled.id\n ];\n\n var eventDateIds = [\n Enums.smartFlowTriggers.contact_added.id,\n Enums.smartFlowTriggers.transaction_created.id,\n Enums.smartFlowTriggers.sale_created.id,\n Enums.smartFlowTriggers.special_order_created.id,\n Enums.smartFlowTriggers.special_order_completed.id,\n Enums.smartFlowTriggers.layaway_created.id\n ];\n\n var milestones = [Enums.smartFlowMilestones.today];\n if(_.contains(apptDateIds, $scope.flow.triggerId)) {\n milestones.push(Enums.smartFlowMilestones.appointment_date);\n } else if(_.contains(eventDateIds, $scope.flow.triggerId)) {\n milestones.push(Enums.smartFlowMilestones.event_date);\n }\n\n return milestones;\n };\n\n $scope.onFlowSelected = function() {\n _.each($scope.appointmentTypes, function(type) {\n type.ticked = false;\n });\n\n if(!$scope.selectedFlow) {\n $scope.onAddClick();\n } else {\n $scope.flow = $scope.selectedFlow;\n\n //if the appointmentTypeId is populated, tick the appointment type\n if($scope.flow.appointmentTypeId) {\n var ids = $scope.flow.appointmentTypeId.split(',');\n _.each($scope.appointmentTypes, function(type) {\n if(ids.indexOf(\"\"+type.id) >= 0) {\n type.ticked = true;\n }\n });\n }\n }\n };\n\n $scope.onFlowSelected();\n\n $scope.onAppointmentTypeChanged = function(data) {\n var types = _.where($scope.appointmentTypes, { ticked: true });\n\n if(types && types.length) {\n var typeIds = _.pluck(types, \"id\");\n $scope.flow.appointmentTypeId = typeIds.join(\",\");\n } else {\n $scope.flow.appointmentTypeId = null;\n }\n };\n\n $scope.isRVOrPOTrigger = function(flow) {\n var triggers = [\n Enums.smartFlowTriggers.receiving_voucher_completed,\n Enums.smartFlowTriggers.purchase_order_confirmed\n ];\n\n var ids = _(triggers).pluck('id');\n return _.contains(ids, flow.triggerId);\n };\n\n $scope.isContactRegistrationTrigger = function(flow) {\n return _.contains([Enums.smartFlowTriggers.contact_registered.id], flow.triggerId);\n };\n\n /**\n * Helpers\n */\n\n function addLineItem(actionTypeId) {\n var seq = $scope.flow.items ? $scope.flow.items.length : 0;\n var item = { sequence: seq, actionTypeId: actionTypeId, createdByUser: User.username, triggerId: $scope.flow.triggerId };\n SmartFlow.addEmptyLineItem($scope.flow.id, item, function(savedFlow) {\n updateFlows(savedFlow);\n angular.extend($scope.flow, savedFlow);\n\n // Scroll to this\n $timeout(function() {\n $('.layout_cnt').animate({ scrollTop: $('.layout_cnt_inner').height() }, 500);\n });\n });\n }\n\n function removeLineItem(item) {\n SmartFlow.deleteLineItem($scope.flow.id, item, function(savedFlow) {\n updateFlows(savedFlow);\n angular.extend($scope.flow, savedFlow);\n });\n }\n\n function updateFlows(savedFlow) {\n var flow = _($scope.flows).findWhere({ id: savedFlow.id });\n if(flow) {\n angular.extend(flow, savedFlow);\n } else {\n $scope.flows.push(savedFlow);\n }\n\n $scope.selectedFlow = flow;\n }\n\n function removeQuoteFlows(flow) {\n return !!(flow && flow.triggerId !== Enums.smartFlowTriggers.quote_created.id);\n }\n \n /**\n * Watchers\n */\n\n $scope.$watch('flow.triggerId', function (triggerId) {\n if(triggerId) {\n var triggers = [\n Enums.smartFlowTriggers.appointment_added,\n Enums.smartFlowTriggers.appointment_completed,\n Enums.smartFlowTriggers.appointment_no_show,\n Enums.smartFlowTriggers.appointment_canceled];\n var ids = _(triggers).pluck('id');\n $scope.apptTypeVisible = _.contains(ids, triggerId);\n\n //if the smart flow's id is empty, this is a new flow so reset the appointment Ids\n if($scope.apptTypeVisible && $scope.flow && !$scope.flow.id) {\n _.each($scope.appointmentTypes, function(type) {\n type.ticked = false;\n });\n\n $scope.flow.appointmentTypeId = null;\n }\n\n triggers = [\n Enums.smartFlowTriggers.transaction_created,\n Enums.smartFlowTriggers.sale_created,\n Enums.smartFlowTriggers.special_order_created,\n Enums.smartFlowTriggers.special_order_completed,\n Enums.smartFlowTriggers.layaway_created,\n Enums.smartFlowTriggers.purchase_order_confirmed,\n Enums.smartFlowTriggers.receiving_voucher_completed\n ];\n\n if($rootScope.isQuoteEnabled) {\n triggers.push(Enums.smartFlowTriggers.quote_created);\n }\n\n ids = _(triggers).pluck('id');\n $scope.deptVisible = _.contains(ids, triggerId);\n\n //if the smart flow's id is empty, this is a new flow so select the first appt type\n if($scope.deptVisible && $scope.flow && !$scope.flow.id) {\n $scope.flow.departmentId = $scope.departments ? $scope.departments[0].id : null;\n }\n } else {\n $scope.apptTypeVisible = false;\n $scope.deptVisible = false;\n }\n });\n });\n});\n\n\n\n","define('modules/settings/fiskaly/index',[\n 'angular',\n 'equals',\n 'underscore',\n '../../alerts/index',\n '../../resources/fiskaly',\n '../../resources/fiskaly-settings',\n '../../resources/transaction',\n '../../resources/register',\n '../../security/user-service',\n '../../modal/modal-service',\n './fiskaly-info-popover/fiskaly-info-popover-directive',\n './closing-point/closing-point-ctrl'\n], function(angular, equals, _) {\n 'use strict';\n\n return angular.module('bl.settings.fiskaly', [\n 'bl.alerts',\n 'bl.resources.fiskaly',\n 'bl.resources.fiskaly-settings',\n 'bl.resources.transaction',\n 'bl.resources.registers',\n 'bl.fiskaly-info',\n 'bl.security.user',\n 'bl.settings.fiskaly.closing-point-ctrl'\n ])\n\n .config(function($stateProvider, Permissions) {\n var template = function(name) {\n return 'js/modules/settings/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('settings.fiskaly_settings', {\n url: '/fiskaly_settings',\n permissions: Permissions.FISKALY,\n views: {\n content: {\n controller: 'FiskalySettingsCtrl',\n templateUrl: template('fiskaly/main'),\n resolve: {\n settings: function(FiskalySettings) {\n return FiskalySettings.get();\n },\n entities: function(FiskalySettings) {\n return FiskalySettings.getFiskalyEntities();\n },\n registers: function (Register) {\n return Register.query();\n },\n company: function (Company) {\n return Company.get();\n }\n }\n }\n }\n });\n })\n\n .controller('FiskalySettingsCtrl', function($scope, $state, $locale, $translate, $modal, AppLicense, settings, entities, registers, company, FiskalySettings, Fiskaly, Company, Transaction, Enums, alerts, User, modalService) {\n\n $scope.locale = $locale;\n $scope.isFiskalyAvailable = !!($locale && ($locale.id === 'DE' || $locale.id === 'AUT'));\n\n $scope.FISKALY = {\n REGISTERED: 'REGISTERED',\n INITIALIZED: 'INITIALIZED',\n UNINITIALIZED: 'UNINITIALIZED',\n DISABLED: 'DISABLED',\n CREATED: 'CREATED',\n ACTIVE: 'ACTIVE',\n DEREGISTERED: 'DEREGISTERED',\n DECOMMISSIONED: 'DECOMMISSIONED'\n };\n\n $scope.entities = buildEntityList(entities, registers);\n $scope.registers = registers;\n\n $scope.show = { apiKey: false, secretKey: false };\n $scope.generate = { tssID: false, clientID: false };\n $scope.action = {\n createClosing: false,\n createClient: false,\n createTSS: false,\n deleteTSS: false,\n deleteClient: false,\n updateTSS: false,\n updateClient: false,\n action: false,\n updateType: '',\n idx: null\n };\n\n $scope.settingsEnabled = !!(settings && settings.apiKey && settings.apiSecret);\n\n if(settings && settings.id) {\n $scope.settings = settings;\n } else {\n $scope.settings = new FiskalySettings();\n $scope.settings.createdByUser = User.username;\n $scope.settings._version = '2';\n $scope.settings.locale = $locale.id;\n }\n\n if(!$scope.settings._version) {\n $scope.settings._version = '2';\n }\n\n $scope.isTest = !!($scope.settings && $scope.settings.apiKey && $scope.settings.apiKey.startsWith('test_'));\n var fiskalyAPIType = $scope.settings && $scope.settings.locale || 'DE';\n\n // storage to know when user changes data\n var cache = {\n settings: null,\n set: function(settings) {\n cache.settings = angular.copy(settings);\n },\n isDifferent: function() {\n return !equals(cache.settings, $scope.settings);\n }\n };\n\n cache.set($scope.settings);\n\n $scope.onSaveClick = function(callback) {\n if($scope.fiskalySettingsForm.$invalid || !$scope.isFiskalyAvailable) {\n return modalService.showInvalidFormError($translate.instant('settings.fiskaly.fiskaly-settings'));\n }\n\n if($scope.onSaveClick.isInProgress) {\n return;\n }\n\n $scope.onSaveClick.isInProgress = true;\n\n $scope.settings.$saveOrUpdate(function(settings) {\n $scope.settingsEnabled = !!(settings && settings.apiKey && settings.apiSecret);\n $scope.settings = settings;\n alerts.push('alerts.fiskaly.successfully-saved-fiskaly-settings');\n\n cache.set($scope.settings);\n callback && callback();\n }, null).finally(function() {\n $scope.onSaveClick.isInProgress = false;\n $scope.fiskalySettingsForm.$pristine = true;\n });\n };\n\n $scope.onSaveClick.isInProgress = false;\n\n $scope.onSettingChanged = function(setting) {\n setting.modifiedByUser = User.username;\n setting.modifiedDate = new Date();\n $scope.fiskalySettingsForm.$pristine = false;\n };\n\n $scope.showKey = function(type) {\n $scope.show[type] = !$scope.show[type];\n };\n\n $scope.testApiKey = function(e) {\n e.preventDefault();\n\n FiskalySettings.testApiKeyAuthentication($scope.settings, function() {\n alerts.pushSuccess('alerts.fiskaly.authentication-success');\n }, function() {\n alerts.pushError('alerts.fiskaly.authentication-error');\n });\n };\n\n /**\n * Version 1 Methods - SIGN DE API\n */\n\n $scope.generateNewTSS = function(e) {\n e.preventDefault();\n\n $scope.generate.tssID = true;\n FiskalySettings.generateNewTSS(function(settings) {\n alerts.pushSuccess({ key: 'alerts.fiskaly.tss-creation-success', params: { tssID: settings.tssID }});\n $scope.settingsEnabled = !!(settings && settings.apiKey && settings.apiSecret);\n $scope.settings = settings;\n cache.set($scope.settings);\n $scope.generate.tssID = false;\n }, function() {\n $scope.generate.tssID = false;\n alerts.pushError('alerts.fiskaly.tss-creation-failed');\n });\n };\n\n $scope.generateNewClient = function(e) {\n e.preventDefault();\n\n if(!$scope.settings.tssID) {\n modalService.showMessage('alerts.fiskaly.tss-required-before-generating-client-id');\n return;\n }\n\n $scope.generate.clientID = true;\n\n FiskalySettings.generateNewClient(function(settings) {\n alerts.pushSuccess({ key: 'alerts.fiskaly.client-creation-success', params: { clientID: settings.clientID }});\n $scope.settingsEnabled = !!(settings && settings.apiKey && settings.apiSecret);\n $scope.settings = settings;\n cache.set($scope.settings);\n $scope.generate.clientID = false;\n }, function() {\n alerts.pushError('alerts.fiskaly.client-creation-failed');\n $scope.generate.clientID = false;\n });\n };\n\n /**\n * Version 2 Methods - SIGN DE API v2\n */\n\n $scope.createTSS = function(e) {\n e.preventDefault();\n\n $scope.action.createTSS = true;\n $scope.action.action = true;\n\n FiskalySettings.createTSS(User.id, function(tss) {\n $scope.entities['DE'].push(tss);\n alerts.pushSuccess({ key: 'alerts.fiskaly.tss-creation-success', params: { tssID: tss.tssID }});\n $scope.action.createTSS = false;\n $scope.action.action = false;\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.tss-creation-failed');\n $scope.action.createTSS = false;\n $scope.action.action = false;\n });\n };\n\n $scope.createClient = function(e, tss, idx) {\n e.preventDefault();\n\n var defaultId = 0;\n var devices = [defaultId];\n\n var _clients = _.map(tss.clients, function(client) {\n return client.deviceId;\n });\n\n if(!_.contains(_clients, defaultId)) {\n createClient(tss, defaultId, idx, \"Default\", \"Default\");\n } else {\n if($scope.registers && $scope.registers.length) {\n devices = _.map($scope.registers, function(register) {\n return register.id;\n });\n\n devices.push(defaultId);\n }\n\n var clients = _.filter(tss.clients, function (obj) {\n return obj.state === 'REGISTERED';\n });\n\n clients = _.map(clients, function(client) {\n return client.deviceId;\n });\n\n _.each(devices, function (device) {\n if(_.contains(clients, device)) {\n devices = _(devices).without(device);\n }\n });\n\n if(devices && devices.length) {\n var filtered = _.filter($scope.registers, function (register) {\n return _.contains(devices, register.id);\n });\n\n if(filtered && filtered.length) {\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/set-register-modal.tpl.html',\n controller: function($scope, devices) {\n $scope.devices = devices;\n\n $scope.selectDevice = function (selectDevice) {\n if(selectDevice && selectDevice.id) {\n $scope.$close(selectDevice.id);\n }\n };\n },\n resolve: {\n devices: function () {\n return filtered;\n }\n }\n }).result.then(function(registerId) {\n if(!registerId) {\n return modalService.showMessage('alerts.register.please-set-register','settings.register.required-register');\n } else {\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/add-fiskaly-cash-register.tpl.html',\n controller: function($scope) {\n $scope.newRegister = {\n brand: null,\n model: null\n };\n\n $scope.addRegister = function() {\n $scope.$close($scope.newRegister);\n };\n }\n }).result.then(function(response) {\n if(!response || !response.brand || !response.model) {\n return modalService.showMessage('alerts.register.please-set-register', 'settings.register.required-register');\n }\n createClient(tss, registerId, idx, response.brand, response.model);\n });\n }\n });\n } else if(_.contains(devices, defaultId)) {\n createClient(tss, defaultId, idx, null, null);\n }\n } else {\n return modalService.showMessage('alerts.fiskaly.no-devices');\n }\n }\n };\n\n $scope.deleteClient = function (e, client, tss) {\n e.preventDefault();\n\n modalService.showStaticConfirmation('settings.fiskaly.messages.are-you-sure-want-to-delete-client?', function() {\n $scope.action.deleteClient = true;\n $scope.action.action = true;\n FiskalySettings.deleteClient(client, function () {\n tss.clients = _(tss.clients).without(client);\n $scope.action.deleteClient = false;\n $scope.action.action = false;\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.action.deleteClient = false;\n $scope.action.action = false;\n });\n });\n };\n\n $scope.deleteTSS = function (e, tss) {\n e.preventDefault();\n\n modalService.showStaticConfirmation('settings.fiskaly.messages.are-you-sure-want-to-delete-tss?', function() {\n $scope.action.deleteTSS = true;\n $scope.action.action = true;\n FiskalySettings.deleteTSS(tss, function () {\n $scope.entities['DE'] = _($scope.entities['DE']).without(tss);\n $scope.action.deleteTSS = false;\n $scope.action.action = false;\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.action.deleteTSS = false;\n $scope.action.action = false;\n });\n });\n };\n\n function createClient(tss, registerId, index, brand, model) {\n $scope.action.createClient = true;\n $scope.action.action = true;\n $scope.action.idx = index;\n\n tss.brand = brand;\n tss.model = model;\n\n FiskalySettings.createClient(tss, User.id, registerId, function(client) {\n client = getDeviceName(client, $scope.registers);\n\n tss.clients.push(client);\n alerts.pushSuccess({ key: 'alerts.fiskaly.client-creation-success', params: { clientID: client.clientID }});\n $scope.action.createClient = false;\n $scope.action.action = false;\n $scope.action.idx = null;\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.client-creation-failed');\n $scope.action.createClient = false;\n $scope.action.action = false;\n $scope.action.idx = null;\n });\n }\n\n function createCashDrawer(scu, registerId, index) {\n $scope.action.createClient = true;\n $scope.action.action = true;\n $scope.action.idx = index;\n\n FiskalySettings.createClient(scu, User.id, registerId, function(cashDrawer) {\n cashDrawer = getDeviceName(cashDrawer, $scope.registers);\n\n scu.clients.push(cashDrawer);\n alerts.pushSuccess({ key: 'alerts.fiskaly.cash-drawer-creation-success', params: { clientID: cashDrawer.clientID }});\n $scope.action.createClient = false;\n $scope.action.action = false;\n $scope.action.idx = null;\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.cash-drawer-creation-failed');\n $scope.action.createClient = false;\n $scope.action.action = false;\n $scope.action.idx = null;\n });\n }\n\n $scope.initTSS = function(e, tss, idx) {\n e.preventDefault();\n $scope.action.updateTSS = true;\n $scope.action.action = true;\n $scope.action.updateType = $scope.FISKALY.INITIALIZED;\n $scope.action.idx = idx;\n\n FiskalySettings.initTSS(tss, User.id, function(updatedTSS) {\n tss.state = updatedTSS.state;\n tss.version = updatedTSS.version;\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n alerts.pushSuccess('alerts.fiskaly.tss-init-success');\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.tss-init-failed');\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n });\n };\n\n $scope.disableTSS = function(e, tss, idx) {\n e.preventDefault();\n\n $scope.action.updateTSS = true;\n $scope.action.action = true;\n $scope.action.updateType = $scope.FISKALY.DISABLED;\n $scope.action.idx = idx;\n\n FiskalySettings.disableTSS(tss, User.id, function(updatedTSS) {\n tss.state = updatedTSS.state;\n tss.version = updatedTSS.version;\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n alerts.pushSuccess('alerts.fiskaly.tss-disabled-success');\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.tss-disabled-failed');\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n });\n };\n\n $scope.updateClient = function(e, client, state, idx) {\n e.preventDefault();\n\n $scope.action.updateClient = true;\n $scope.action.action = true;\n $scope.action.updateType = state;\n $scope.action.idx = idx;\n\n FiskalySettings.updateClient(client, state, User.id, function(updatedClient) {\n client.state = updatedClient.state;\n client.version = updatedClient.version;\n $scope.action.updateClient = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n alerts.pushSuccess('alerts.fiskaly.client-state-success');\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.client-state-failed');\n $scope.action.updateClient = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n });\n };\n\n $scope.showTSSCreateBtn = function(entities, locale) {\n if(!entities[locale] || !entities[locale].length) {\n return true;\n }\n\n var filtered = _.filter(entities[locale], function(entity) {\n return !!(entity && entity.state !== 'INITIALIZED');\n });\n\n return filtered.length;\n };\n\n $scope.getRegister = function(client) {\n if(client && client.deviceId) {\n return client.deviceId;\n }\n\n return '';\n };\n\n function buildEntityList(results, registers) {\n if(registers && registers.length) {\n _.each(results, function(tss) {\n _.each(tss.clients, function(client) {\n if(client.deviceId) {\n var register = _(registers).findWhere({ id: client.deviceId });\n\n if(register && register.name) {\n client.deviceName = register.name;\n }\n } else if(client.deviceId === 0) {\n client.deviceName = $translate.instant('common.default');\n }\n });\n });\n }\n\n return {\n DE: _(results).filter(function(result) {\n return !!(result && (!result.locale || result.locale === 'DE'));\n }),\n AUT: _(results).filter(function(result) {\n return !!(result && result.locale === 'AUT');\n }),\n };\n }\n\n function getDeviceName(client, registers) {\n if(client && client.deviceId) {\n var register = _(registers).findWhere({ id: client.deviceId });\n\n if (register && register.name) {\n client.deviceName = register.name;\n }\n } else if(client && client.deviceId === 0) {\n client.deviceName = $translate.instant('common.default');\n }\n\n return client;\n }\n\n /**\n * Fiskaly SIGN AT API\n */\n\n $scope.createSCU = function(e) {\n if(e) {\n e.preventDefault();\n }\n\n if(company && !company.vatNumber) {\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/set-vat-number.modal.tpl.html',\n controller: function($scope, Company) {\n $scope.vatNumber = '';\n $scope.updating = false;\n\n $scope.update = function(vatNumber) {\n if(!vatNumber) {\n return;\n }\n\n $scope.updating = true;\n Company.updateVATNumber(vatNumber, function(result) {\n $scope.updating = false;\n $scope.$close(result);\n }, function(data, status) {\n Company.defaultErrorHandler(data, status);\n $scope.updating = false;\n });\n };\n }\n }).result.then(function(response) {\n if(!response || !response.vatNumber) {\n return modalService.showMessage('settings.fiskaly.austria.please-provide-vat-number','settings.fiskaly.austria.vat-number-required');\n } else {\n company = response;\n createSCU();\n }\n });\n } else {\n createSCU();\n }\n };\n\n $scope.createCashDrawer = function(e, scu, idx) {\n e.preventDefault();\n\n var defaultId = 0;\n var devices = [defaultId];\n\n var _clients = _.map(scu.clients, function (client) {\n return client.deviceId;\n });\n\n if(!_.contains(_clients, defaultId)) {\n createCashDrawer(scu, defaultId, idx);\n } else {\n if($scope.registers && $scope.registers.length) {\n devices = _.map($scope.registers, function(register) {\n return register.id;\n });\n\n devices.push(defaultId);\n }\n\n var clients = _.filter(scu.clients, function (obj) {\n return obj.state === 'INITIALIZED';\n });\n\n clients = _.map(clients, function(client) {\n return client.deviceId;\n });\n\n _.each(devices, function (device) {\n if(_.contains(clients, device)) {\n devices = _(devices).without(device);\n }\n });\n\n if(devices && devices.length) {\n var filtered = _.filter($scope.registers, function (register) {\n return _.contains(devices, register.id);\n });\n\n if(filtered && filtered.length) {\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/set-register-modal.tpl.html',\n controller: function($scope, devices) {\n $scope.devices = devices;\n\n $scope.selectDevice = function (selectDevice) {\n if(selectDevice && selectDevice.id) {\n $scope.$close(selectDevice.id);\n }\n };\n },\n resolve: {\n devices: function () {\n return filtered;\n }\n }\n }).result.then(function(registerId) {\n if(!registerId) {\n return modalService.showMessage('alerts.register.please-set-register','settings.register.required-register');\n } else {\n createCashDrawer(scu, registerId, idx);\n }\n });\n } else if(_.contains(devices, defaultId)) {\n createCashDrawer(scu, defaultId, idx);\n }\n } else {\n return modalService.showMessage('alerts.fiskaly.no-devices');\n }\n }\n };\n\n $scope.deleteCashDrawer = function (e, cashDrawer, scu) {\n e.preventDefault();\n\n modalService.showStaticConfirmation('settings.fiskaly.messages.are-you-sure-want-to-delete-cash-drawer?', function() {\n $scope.action.deleteClient = true;\n $scope.action.action = true;\n FiskalySettings.deleteClient(cashDrawer, function () {\n scu.clients = _(scu.clients).without(cashDrawer);\n $scope.action.deleteClient = false;\n $scope.action.action = false;\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.action.deleteClient = false;\n $scope.action.action = false;\n });\n });\n };\n\n $scope.deleteSCU = function (e, scu) {\n e.preventDefault();\n\n modalService.showStaticConfirmation('settings.fiskaly.messages.are-you-sure-want-to-delete-scu?', function() {\n $scope.action.deleteTSS = true;\n $scope.action.action = true;\n FiskalySettings.deleteTSS(scu, function () {\n $scope.entities['AUT'] = _($scope.entities['AUT']).without(scu);\n $scope.action.deleteTSS = false;\n $scope.action.action = false;\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.action.deleteTSS = false;\n $scope.action.action = false;\n });\n });\n };\n\n $scope.initSCU = function(e, scu, idx) {\n e.preventDefault();\n $scope.action.updateTSS = true;\n $scope.action.action = true;\n $scope.action.updateType = $scope.FISKALY.INITIALIZED;\n $scope.action.idx = idx;\n\n FiskalySettings.initTSS(scu, User.id, function(updatedSCU) {\n scu.state = updatedSCU.state;\n scu.version = updatedSCU.version;\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n alerts.pushSuccess('alerts.fiskaly.scu-init-success');\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.scu-init-failed');\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n });\n };\n\n $scope.disableSCU = function(e, scu, idx) {\n e.preventDefault();\n\n $scope.action.updateTSS = true;\n $scope.action.action = true;\n $scope.action.updateType = $scope.FISKALY.DISABLED;\n $scope.action.idx = idx;\n\n FiskalySettings.disableTSS(scu, User.id, function(updatedSCU) {\n scu.state = updatedSCU.state;\n scu.version = updatedSCU.version;\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n alerts.pushSuccess('alerts.fiskaly.scu-disabled-success');\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.scu-disabled-failed');\n $scope.action.updateTSS = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n });\n };\n\n $scope.updateCashDrawer = function(e, cashDrawer, state, idx) {\n e.preventDefault();\n\n $scope.action.updateClient = true;\n $scope.action.action = true;\n $scope.action.updateType = state;\n $scope.action.idx = idx;\n\n FiskalySettings.updateClient(cashDrawer, state, User.id, function(updatedCashDrawer) {\n cashDrawer.state = updatedCashDrawer.state;\n cashDrawer.version = updatedCashDrawer.version;\n $scope.action.updateClient = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n alerts.pushSuccess('alerts.fiskaly.cash-drawer-state-success');\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.cash-drawer-state-failed');\n $scope.action.updateClient = false;\n $scope.action.action = false;\n $scope.action.updateType = '';\n $scope.action.idx = null;\n });\n };\n\n $scope.authorizeFON = function(e) {\n e.preventDefault();\n\n if(!$scope.settings || !$scope.settings.apiKey || !$scope.settings.apiSecret) {\n return;\n }\n\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/fon.modal.tpl.html',\n controller: function($scope, credentials, FiskalySettings) {\n $scope.fon = {\n fonParticipantId: credentials.fonParticipantId,\n fonUserId: credentials.fonUserId,\n fonUserPin: credentials.fonUserPin\n };\n\n $scope.authorizing = false;\n $scope.authorizeFON = function(fon) {\n if(!fon || !fon.fonParticipantId || !fon.fonUserId || !fon.fonUserPin) {\n return;\n }\n\n $scope.authorizing = true;\n FiskalySettings.validateFON(fon, function(response) {\n alerts.pushSuccess('alerts.fiskaly.fon-authentication-success');\n $scope.authorizing = false;\n $scope.$close(response);\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.authorizing = false;\n alerts.pushError('alerts.fiskaly.fon-authentication-failed');\n });\n };\n },\n resolve: {\n credentials: function() {\n return $scope.settings;\n }\n }\n }).result.then(function(response) {\n if(validateFONCredentials(response)) {\n $scope.settings = response;\n cache.set($scope.settings);\n }\n });\n };\n\n $scope.testFON = function(e) {\n e.preventDefault();\n $scope.testingFON = true;\n FiskalySettings.testFON(function(response) {\n var message = 'FinanzOnline (FON) ';\n message += 'Status: ' + response.authentication_status + ' ';\n message += 'Participant ID: ' + response.fon_participant_id + ' ';\n message += 'User ID: ' + response.fon_user_id + ' ';\n message += ' ';\n modalService.showMessage(message, 'FinanzOnline (FON)');\n $scope.testingFON = false;\n }, function(data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.testingFON = false;\n });\n };\n\n $scope.validateFONCredentials = function(settings) {\n return validateFONCredentials(settings);\n };\n\n function createSCU() {\n $scope.action.createTSS = true;\n $scope.action.action = true;\n\n FiskalySettings.createTSS(User.id, function (scu) {\n $scope.entities['AUT'].push(scu);\n alerts.pushSuccess({ key: 'alerts.fiskaly.scu-creation-success', params: { tssID: scu.tssID }});\n $scope.action.createTSS = false;\n $scope.action.action = false;\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n alerts.pushError('alerts.fiskaly.scu-creation-failed');\n $scope.action.createTSS = false;\n $scope.action.action = false;\n });\n }\n\n function validateFONCredentials(settings) {\n return !!(settings && settings.fonParticipantId && settings.fonUserId && settings.fonUserPin);\n }\n\n $scope.onAPIChange = function(settings) {\n if($scope.locale.id !== settings.locale) {\n modalService.showMessage('settings.fiskaly.api-type-change-error', 'modal-service.error');\n settings.locale = $scope.locale.id;\n return;\n }\n\n modalService.showConfirmation({\n key: 'settings.fiskaly.messages.are-you-sure-want-to-change-api-type?',\n params: {\n API: $translate.instant(settings.locale === 'DE' ? 'settings.fiskaly.api-fiskaly-de' : 'settings.fiskaly.api-fiskaly-aut')\n }\n }, function () {\n fiskalyAPIType = settings.locale;\n $scope.onSettingChanged(settings);\n $scope.onSaveClick();\n }, function () {\n $scope.settings.locale = fiskalyAPIType;\n });\n };\n\n /**\n * DSFomV-k\n */\n\n $scope.createCashRegister = function (e, client, tss, idx) {\n if(!company) {\n return;\n }\n\n if(e) {\n e.preventDefault();\n }\n\n $scope.action.idx = idx;\n\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/add-fiskaly-cash-register.tpl.html',\n resolve: {\n registers: function () {\n return $scope.registers;\n }\n },\n controller: function ($scope, Company, registers) {\n var isDefaultClient = (client && !client.deviceId || client.deviceId === 0);\n\n $scope.newRegister = {\n brand: isDefaultClient ? \"Default\" : null,\n model: isDefaultClient ? \"Default\" : null,\n isDefaultClient: isDefaultClient\n };\n\n $scope.registers = registers;\n\n $scope.addRegister = function() {\n $scope.$close($scope.newRegister);\n };\n }\n }).result.then(function(response) {\n if(response) {\n client.brand = response.isDefaultClient ? \"Default\" : response.brand;\n client.model = response.isDefaultClient ? \"Default\" : response.model;\n\n FiskalySettings.createFiskalyCashRegister(client, User.id,function(cashRegister) {\n client.cashRegister = cashRegister;\n _.each(tss.clients, function (c) {\n if(c && c.id === client.id) {\n client = c;\n }\n });\n $scope.action.idx = null;\n }, function (data, status) {\n FiskalySettings.defaultErrorHandler(data, status);\n $scope.action.idx = null;\n });\n } else {\n $scope.action.idx = null;\n }\n });\n };\n\n /**\n * Closing Point\n */\n\n $scope.createClosingPoint = function(e) {\n if(e) {\n e.preventDefault();\n }\n\n $modal.open({\n templateUrl: 'js/modules/settings/fiskaly/closing-point/closing-point-modal.tpl.html',\n controller: 'ClosingPointCtrl'\n }).result.then(function(response) {\n if(response && response.success) {\n alerts.pushSuccess('alerts.fiskaly.closing-point.successfully-created-closing-point');\n } else {\n alerts.pushError('alerts.fiskaly.closing-point.failed-to-create-closing-point');\n }\n searchPoints();\n });\n };\n\n // $scope.viewPoint = function(point) {\n // if(!point || !point.closing_id) {\n // return;\n // }\n //\n // if(!point.expanded) {\n // $scope.loading = true;\n // Fiskaly.get(point.closing_id, function(response) {\n // if(response && response.closingData) {\n // try {\n // point.fiskalyClosingData = JSON.parse(response.closingData);\n // point.expanded = true;\n // } catch(e) {}\n // }\n //\n // $scope.loading = false;\n // }, function(data, status) {\n // Fiskaly.defaultErrorHandler(data, status);\n // $scope.loading = false;\n // point.expanded = false;\n // delete point.fiskalyClosingData;\n // });\n // } else {\n // point.expanded = false;\n // delete point.fiskalyClosingData;\n // }\n // };\n\n $scope.searchPoints = function() {\n searchPoints();\n };\n\n $scope.searchPoints();\n\n function searchPoints() {\n Fiskaly.listResultModel({}, function(points) {\n $scope.points = points.result;\n });\n }\n\n /**\n * Watches\n */\n\n var ignoreRedirectWarning = false;\n\n $scope.$on('$stateChangeStart', function(event, toState, toParams) {\n var continueNavigation = function() {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, {location: 'replace'});\n };\n\n if(!ignoreRedirectWarning && cache.isDifferent()) {\n event.preventDefault();\n\n modalService.showStaticConfirmation(\n 'item.message.do-you-want-changes-to-be-saved?',\n function() {\n $scope.onSaveClick(continueNavigation);\n },\n continueNavigation\n );\n }\n });\n });\n});\n\n","define('modules/settings/enhanced-web-forms/appointment-scheduler/index',[\n 'angular',\n 'underscore',\n '../../../modal/modal-service',\n '../../../form-builder/index',\n '../../../custom-form-builder/index',\n '../../../resources/appointment-scheduler-widget-settings',\n '../../../resources/scheduler-settings',\n '../../../resources/custom-field',\n '../../../resources/custom-field-option',\n '../../../resources/appointment-type',\n '../../../resources/custom-field-appointment-type',\n '../../../resources/advance-appointment-type',\n '../../../security/user-service',\n '../preview-builder-ctrl',\n '../advance-appt-type-ctrl'\n], function (angular, _) {\n 'use strict';\n\n return angular.module('bl.settings.enhanced-web-forms.appointment-scheduler', [\n 'bl.modal',\n 'bl.form-builder',\n 'bl.custom-form-builder',\n 'bl.resources.appointment-scheduler-widget-settings',\n 'bl.resources.scheduler-settings',\n 'bl.resources.appointment-type',\n 'bl.resources.advance-appointment-type',\n 'bl.resources.custom-field-appointment-type',\n 'bl.resources.custom-field',\n 'bl.resources.custom-field-option',\n 'bl.security.user',\n 'bl.settings.preview-builder-ctrl',\n 'bl.settings.advance-appt-type-ctrl'\n ])\n\n .config(function ($stateProvider, Permissions, Enums) {\n $stateProvider.state('settings.enhanced-appointment-scheduler', {\n url: '/custom-appointment-scheduler',\n permissions: Permissions.CUSTOM_FORMS,\n views: {\n content: {\n controller: 'SettingsEnhancedWebFormsAppointmentSchedulerCtrl',\n templateUrl: 'js/modules/settings/enhanced-web-forms/appointment-scheduler/main.tpl.html',\n resolve: {\n settings: function (SchedulerSettings) {\n return SchedulerSettings.getAllSettings();\n },\n appointmentTypes: function(AppointmentType) {\n return AppointmentType.query({ filterObject:{ status: Enums.statuses.active.value, bookOnline: true }});\n },\n advanceAppointmentTypes: function(AdvanceAppointmentType) {\n return AdvanceAppointmentType.query({ filterObject:{ source: 'scheduler' }});\n },\n mailchimpLists: function (MailChimp) {\n return MailChimp.lists();\n },\n employees: function (Employee) {\n return Employee.query({ sortField: 'firstName', filterObject:{ status: Enums.statuses.active.value }});\n }\n }\n }\n }\n });\n })\n\n .controller('SettingsEnhancedWebFormsAppointmentSchedulerCtrl', function ($scope, $state, $modal, $window, $translate, $timeout, $q, security, Permissions, AppLicense, AdvanceAppointmentType, CustomFieldAppointmentType, UtilsHelper, ColorThemeService, DEFAULTS, WEBFORM_COLOR_THEMES, FORM_BUILDER_CONSTANTS, WebFormHelper, Enums, User, settings, appointmentTypes, advanceAppointmentTypes, employees, mailchimpLists, alerts, AppointmentSchedulerWidgetSettings, CustomField, AppointmentType, SchedulerSettings, modalService) {\n $scope.source = 'scheduler';\n $scope.isDev = window.location.hostname === \"localhost\";\n $scope.currentTabId = 'lookAndFeel';\n var license = AppLicense.get();\n\n $scope.perms = {\n plan: !!(license && license.plan && license.plan.customWebForms),\n view: security.hasPermission(Permissions.CUSTOM_FORMS)\n };\n\n $scope.customWebForms = $scope.perms.plan && $scope.perms.view;\n\n $scope.formBuilderControls = { requiredFields: FORM_BUILDER_CONSTANTS.SCHEDULER_CUSTOMER_DETAILS_CONTROLS, additionalDetails: FORM_BUILDER_CONSTANTS.ADDITIONAL_DETAILS_CONTROLS };\n\n $scope.form = settings.enhancedSettings;\n $scope.settings = settings;\n\n var hasEmployee = false;\n\n if($scope.settings.defaultEmployeeId) {\n hasEmployee = true;\n }\n\n $scope.tabDisplay = {\n basic: false,\n required: false,\n custom: false,\n general: !hasEmployee,\n lookAndFeel: hasEmployee,\n advancedAppointmentType: false\n };\n\n if(!$scope.settings.defaultEmployeeId) {\n $scope.currentTabId = 'general';\n }\n\n $scope.colorThemes = Enums.webformColorThemes;\n $scope.maxDates = Enums.maxDateAllowedForBooking;\n $scope.fontTypes = Enums.fontTypes;\n $scope.appointmentTypes = appointmentTypes;\n $scope.advanceAppointmentTypes = advanceAppointmentTypes || [];\n $scope.mailchimpLists = mailchimpLists;\n $scope.employees = employees;\n $scope.days = Enums.appointmentBlockDays;\n $scope.customFieldsSaving = false;\n\n var defaultEmployee = _($scope.employees).findWhere({ id: $scope.settings.defaultEmployeeId });\n\n if(defaultEmployee && defaultEmployee.id) {\n $scope.settings.defaultEmployeeId = defaultEmployee.id;\n } else {\n $scope.settings.defaultEmployeeId = null;\n }\n\n var mailChimp = _($scope.mailchimpLists).findWhere({ id: $scope.settings.mailChimpListId });\n\n if(mailChimp && mailChimp.id) {\n $scope.settings.mailChimpListId = mailChimp.id;\n } else {\n $scope.settings.mailChimpListId = null;\n }\n\n setDefaultTheme($scope.form.theme);\n setFont($scope.form.theme.font);\n\n $scope.onSaveClick = function(callback, ignore) {\n if(!$scope.customWebForms) {\n return;\n }\n\n if(!$scope.settings.defaultEmployeeId) {\n return modalService.showMessage('settings.webforms.scheduler.default-employee-missing');\n }\n\n if($scope.schedulerEnhanceSettingsForm.$invalid) {\n return modalService.showInvalidFormError($translate.instant('enums.client-portal.themes.custom') + \" \" + $translate.instant('settings.header.appointment-scheduler'));\n }\n\n if($scope.onSaveClick.isInProgress) {\n return;\n }\n \n var settings = angular.copy($scope.settings);\n\n if(settings.id) {\n settings.modifiedDate = new Date();\n settings.modifiedByUser = User.username;\n } else {\n settings.createdByUser = User.username;\n settings.createdDate = new Date();\n }\n\n var schedulerSettings = angular.copy($scope.form);\n\n schedulerSettings.layoutType = 'steps';\n\n try {\n schedulerSettings.theme.primaryColorDarken = ColorThemeService.darken(schedulerSettings.theme.primaryColor, 15);\n schedulerSettings.theme.secondaryColorDarken = ColorThemeService.darken(schedulerSettings.theme.secondaryColor, 15);\n schedulerSettings.theme.selectedColorDarken = ColorThemeService.darken(schedulerSettings.theme.selectedColor, 15);\n schedulerSettings.theme.availableColorDarken = ColorThemeService.darken(schedulerSettings.theme.availableColor, 15);\n schedulerSettings.theme.timeSlotColorDarken = ColorThemeService.darken(schedulerSettings.theme.timeSlotColor, 15);\n } catch (e) {\n console.log('Theme error:', e);\n }\n\n if(schedulerSettings._id) {\n schedulerSettings.modifiedDate = new Date();\n schedulerSettings.modifiedByUser = User.username;\n } else {\n schedulerSettings.createdByUser = User.username;\n schedulerSettings.createdDate = new Date();\n }\n\n schedulerSettings.parsed = false;\n settings.enhancedSettings = schedulerSettings;\n\n SchedulerSettings.saveMultiple(settings, function(updatedSettings) {\n $scope.settings = updatedSettings;\n $scope.form = updatedSettings.enhancedSettings;\n $scope.saveInProgress = false;\n\n if(!ignore) {\n alerts.pushSuccess('settings.webforms.scheduler.custom-fields.successfully-updated-scheduler-settings');\n }\n\n if(callback) {\n callback();\n }\n }, function (data, status) {\n SchedulerSettings.defaultErrorHandler(data, status);\n $scope.saveInProgress = false;\n }).finally(function() {\n $scope.onSaveClick.isInProgress = false;\n $scope.onPreviewClick.isInProgress = false;\n $scope.schedulerEnhanceSettingsForm.$setPristine();\n });\n };\n\n $scope.onSaveClick.isInProgress = false;\n\n $scope.onPreviewClick = function() {\n if($scope.onSaveClick.isInProgress || $scope.onPreviewClick.isInProgress) {\n return;\n }\n\n $scope.onPreviewClick.isInProgress = true;\n var newWin = $window.open($scope.form.formURL);\n\n if(!newWin || newWin.closed || typeof newWin.closed=='undefined') {\n modalService.showErrors([{ message: $translate.instant('alerts.settings.widgets.popup-blocker-enabled') }]);\n }\n $scope.onPreviewClick.isInProgress = false;\n };\n\n $scope.onPreviewClick.isInProgress = false;\n\n $scope.onFontPackChanged = function(font) {\n if(font) {\n setFont(font);\n }\n };\n\n $scope.onPaletteChanged = function (palette) {\n if(palette) {\n setColors(palette);\n }\n };\n\n function setColors(themeId) {\n var theme;\n\n if(themeId) {\n theme = _(WEBFORM_COLOR_THEMES).findWhere({ id: themeId });\n }\n\n if(theme) {\n // Set themes\n $scope.form.theme.backgroundColor = theme.backgroundColor;\n $scope.form.theme.textColor = theme.textColor;\n\n $scope.form.theme.secondaryColor = theme.secondaryColor;\n $scope.form.theme.secondaryTextColor = theme.secondaryTextColor;\n\n $scope.form.theme.availableColor = theme.availableColor;\n $scope.form.theme.availableTextColor = theme.availableTextColor;\n\n $scope.form.theme.timeSlotColor = theme.timeSlotColor;\n $scope.form.theme.timeSlotTextColor = theme.timeSlotTextColor;\n\n $scope.form.theme.selectedColor = theme.selectedColor;\n $scope.form.theme.selectedTextColor = theme.selectedTextColor;\n\n $scope.form.theme.primaryColor = theme.primaryColor;\n $scope.form.theme.primaryTextColor = theme.primaryTextColor;\n\n $scope.form.theme.inputBorderColor = theme.inputBorderColor;\n\n $scope.form.theme.transparent = theme.transparent;\n }\n }\n\n function setFont(font) {\n ColorThemeService.initFontExampleWebForm(font);\n }\n\n function setDefaultTheme() {\n if(!$scope.form.theme.primaryColor) {\n $scope.form.theme.primaryColor = '#bfadd1';\n }\n\n if(!$scope.form.theme.secondaryColor) {\n $scope.form.theme.secondaryColor = '#e8e8e8';\n }\n\n if(!$scope.form.theme.textColor) {\n $scope.form.theme.textColor = '#222222';\n }\n\n if(!$scope.form.theme.selectedColor) {\n $scope.form.theme.selectedColor = '#2C6CB0';\n }\n\n if(!$scope.form.theme.availableColor) {\n $scope.form.theme.availableColor = '#dff0d8';\n }\n\n if(!$scope.form.theme.timeSlot) {\n $scope.form.theme.timeSlot = '#dff0d8';\n }\n\n if(!$scope.form.theme.inputBorderColor) {\n $scope.form.theme.inputBorderColor = '#e2e8f0';\n }\n\n if(!$scope.form.theme.primaryTextColor) {\n $scope.form.theme.primaryTextColor = '#222222';\n }\n\n if(!$scope.form.theme.secondaryTextColor) {\n $scope.form.theme.secondaryTextColor = '#222222';\n }\n\n if(!$scope.form.theme.selectedTextColor) {\n $scope.form.theme.selectedTextColor = '#222222';\n }\n\n if(!$scope.form.theme.availableTextColor) {\n $scope.form.theme.availableTextColor = '#222222';\n }\n\n if(!$scope.form.theme.timeSlotTextColor) {\n $scope.form.themetimeSlotTextColor = '#222222';\n }\n }\n\n $scope.changeTab = function (tabId) {\n if($scope.perms[tabId]) {\n $scope.tabDisplay[$scope.currentTabId] = false;\n $scope.tabDisplay[tabId] = true;\n $scope.currentTabId = tabId;\n } else {\n $scope.tabDisplay[$scope.currentTabId] = true;\n }\n };\n\n $scope.removeAdvanceAppointmentType = function(e, advanceAppointmentType) {\n if(e) {\n e.preventDefault();\n }\n\n modalService.showConfirmation({\n key: 'settings.message.would-like-to-delete-model?',\n params: { model: $translate.instant('settings.webforms.advance-appointment-types.advance-appointment-type') }\n }, function () {\n advanceAppointmentType.$remove(function () {\n $scope.advanceAppointmentTypes = _($scope.advanceAppointmentTypes).without(advanceAppointmentType);\n });\n });\n };\n\n $scope.editAdvanceAppointmentType = function(e, advanceAppointmentType) {\n if(e) {\n e.preventDefault();\n }\n\n var index = _.indexOf($scope.advanceAppointmentTypes, advanceAppointmentType);\n $modal.open({\n templateUrl: 'js/modules/settings/enhanced-web-forms/advance-appt-type.tpl.html',\n controller: 'AdvanceAppointmentTypeCtrl',\n resolve: {\n appt_types: function() {\n return $scope.appointmentTypes;\n },\n advanceAppointmentType: function () {\n return AdvanceAppointmentType.getById(advanceAppointmentType.id);\n },\n source: function () {\n return 'scheduler';\n }\n },\n backdrop: 'static'\n }).result.then(function (r) {\n r.modifiedByUser = User.username;\n r.modifiedDate = new Date();\n if(r.appointmentTypeDescription) {\n var desc = r.appointmentTypeDescription;\n }\n r.$update(function (updatedResource) {\n updatedResource.appointmentTypeDescription = desc;\n $scope.advanceAppointmentTypes[index] = updatedResource;\n });\n });\n };\n\n $scope.addAdvanceAppointmentType = function(e) {\n if(e) {\n e.preventDefault();\n }\n\n $modal.open({\n templateUrl: 'js/modules/settings/enhanced-web-forms/advance-appt-type.tpl.html',\n controller: 'AdvanceAppointmentTypeCtrl',\n resolve: {\n appt_types: function() {\n return $scope.appointmentTypes;\n },\n advanceAppointmentType: function () {\n return null;\n },\n source: function () {\n return 'scheduler';\n }\n },\n backdrop: 'static'\n }).result.then(function (r) {\n var deferred = $q.defer();\n r.status = Enums.statuses.active.value;\n r.createdByUser = User.username;\n r.$save(function (newResource) {\n $scope.advanceAppointmentTypes.push(newResource);\n deferred.resolve(newResource);\n });\n\n return deferred.promise;\n });\n };\n\n $scope.sortableApptTypeGroupsOptions = {\n tolerance: 'pointer',\n revert: 100,\n distance: 5,\n forcePlaceholderSize: true,\n handle: \".handle\",\n helper: function(e, ui) {\n ui.children().each(function() {\n $(this).width($(this).width());\n });\n\n return ui;\n },\n stop: function() {\n _($scope.form.typeGroups).each(function(group, i) {\n group.sequence = i;\n });\n }\n };\n\n $scope.onAddGroupClick = function(e) {\n if(e) {\n e.preventDefault();\n }\n\n $modal.open({\n templateUrl: 'js/modules/settings/enhanced-web-forms/appointment-scheduler/type-group.tpl.html',\n keyboard: false,\n backdrop: 'static',\n controller: function($scope, group, appt_types) {\n $scope.model = angular.copy({\n sequence: group.sequence,\n ids: [],\n types: [],\n groupName: ''\n });\n\n $scope.types = angular.copy(appt_types);\n\n $scope.onClose = function(model) {\n $scope.error = false;\n model.ids = _(model.types).pluck('id');\n model.types = [];\n\n if(!model.groupName) {\n $scope.error = true;\n return;\n }\n\n $scope.$close(model);\n };\n },\n resolve: {\n appt_types: function() {\n return $scope.appointmentTypes;\n },\n group: function () {\n return {\n sequence: $scope.form.typeGroups.length,\n groupName: '',\n ids: [],\n types: []\n };\n }\n }\n }).result.then(function(response) {\n if(response) {\n response._id = Date.now() + '';\n response.createdByUser = User.username;\n response.createdDate = new Date();\n $scope.form.typeGroups.push(response);\n $scope.schedulerEnhanceSettingsForm.$setDirty();\n }\n });\n };\n\n $scope.onEditGroupClick = function(group, index) {\n $modal.open({\n templateUrl: 'js/modules/settings/enhanced-web-forms/appointment-scheduler/type-group.tpl.html',\n keyboard: false,\n backdrop: 'static',\n controller: function($scope, group, appt_types) {\n $scope.model = group;\n $scope.types = angular.copy(appt_types);\n\n group.types = _($scope.types).filter(function(type) {\n return _.contains(group.ids, type.id);\n }).map(function(type) {\n type.ticked = true;\n return type;\n });\n\n $scope.onClose = function(model) {\n $scope.error = false;\n model.ids = _(model.types).pluck('id');\n model.types = [];\n\n if(!model._id) {\n model._id = Date.now() + '';\n }\n\n if(!model.groupName) {\n $scope.error = true;\n return;\n }\n\n $scope.$close(model);\n };\n },\n resolve: {\n appt_types: function() {\n return $scope.appointmentTypes;\n },\n group: function () {\n return group;\n }\n }\n }).result.then(function(response) {\n if(response) {\n response.modifiedByUser = User.username;\n response.modifiedDate = new Date();\n $scope.form.typeGroups[index] = response;\n $scope.schedulerEnhanceSettingsForm.$setDirty();\n }\n });\n };\n\n $scope.openPreviewBuilder = function () {\n\n $modal.open({\n templateUrl: 'js/modules/settings/enhanced-web-forms/preview-builder.tpl.html',\n controller: 'PreviewBuilderCtrl',\n resolve: {\n appt_types: function() {\n return $scope.appointmentTypes;\n },\n url: function () {\n return $scope.form.baseURL;\n },\n formType: function () {\n return 'scheduler';\n },\n fields: function () {\n return $scope.formBuilderControls.requiredFields;\n }\n },\n backdrop: 'static'\n });\n };\n\n $scope.onRemoveGroupClick = function(group) {\n $scope.form.typeGroups = _($scope.form.typeGroups).without(group);\n $scope.schedulerEnhanceSettingsForm.$setDirty();\n };\n\n $scope.setFormDirty = function() {\n $scope.schedulerEnhanceSettingsForm.$setDirty();\n };\n\n $scope.customFormAction = function(action) {\n $scope.customFieldsSaving = true;\n $scope.onSaveClick(function() {\n var message = $translate.instant('settings.webforms.scheduler.custom-fields.successfully-updated-scheduler-settings');\n\n if(action === 'add') {\n message += $translate.instant('settings.webforms.scheduler.custom-fields.and-add-custom-field');\n } else if(action === 'remove') {\n message += $translate.instant('settings.webforms.scheduler.custom-fields.and-remove-custom-field');\n } else if(action === 'update') {\n message += $translate.instant('settings.webforms.scheduler.custom-fields.and-updated-custom-field');\n }\n\n $scope.customFieldsSaving = false;\n alerts.pushSuccess(message);\n }, true);\n };\n\n $scope.getTitle = function (step) {\n switch (step.id) {\n case 'appointmentType' :\n return 'settings.webforms.scheduler.steps.select-appointment-type';\n case 'associate':\n return 'settings.webforms.scheduler.steps.select-associate';\n case 'dateAndTime':\n return 'settings.webforms.scheduler.steps.select-date-and-time';\n case 'requiredFields':\n return 'settings.webforms.scheduler.steps.contact-information';\n case 'additionalDetails':\n return 'settings.webforms.scheduler.steps.additional-details';\n }\n };\n\n /**\n * Watches\n */\n\n var ignoreRedirectWarning = false;\n\n $scope.$on('$stateChangeStart', function (event, toState, toParams) {\n var continueNavigation = function () {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, { location: 'replace' });\n };\n\n if(!ignoreRedirectWarning && $scope.schedulerEnhanceSettingsForm && $scope.schedulerEnhanceSettingsForm.$dirty) {\n event.preventDefault();\n\n modalService.showStaticConfirmation(\n 'item.message.do-you-want-changes-to-be-saved?',\n function () {\n $scope.onSaveClick(continueNavigation);\n },\n continueNavigation\n );\n }\n });\n });\n});\n\n","define('modules/settings/enhanced-web-forms/enhanced-lead/index',[\n 'angular',\n 'equals',\n '../../../modal/modal-service',\n '../../../form-builder/index',\n '../../../custom-form-builder/index',\n '../../../resources/lead-widget-settings',\n '../../../security/user-service',\n], function (angular, equals) {\n 'use strict';\n\n return angular.module('bl.settings.enhanced-lead', [\n 'bl.modal',\n 'bl.form-builder',\n 'bl.custom-form-builder',\n 'bl.resources.lead-widget-settings',\n 'bl.security.user'\n ])\n\n .config(function ($stateProvider, Permissions) {\n $stateProvider.state('settings.enhanced-lead', {\n url: '/enhanced-lead',\n permissions: Permissions.WEBSITE_FORMS,\n views: {\n 'content': {\n controller: 'SettingsEnhancedWebFormsLeadCtrl',\n templateUrl: 'js/modules/settings/enhanced-web-forms/enhanced-lead/main.tpl.html',\n resolve: {\n enhancedSettings: function (LeadWidgetSettings) {\n return LeadWidgetSettings.getEnhancedSettings();\n }\n }\n }\n }\n });\n })\n\n .controller('SettingsEnhancedWebFormsLeadCtrl', function ($scope, $state, DEFAULTS, User, alerts, LeadWidgetSettings, enhancedSettings, modalService) {\n if(enhancedSettings && enhancedSettings._id) {\n $scope.leadForm = enhancedSettings;\n } else {\n $scope.leadForm = DEFAULTS.LEADS;\n }\n\n // storage to know when user changes data\n var cache = {\n settings: null,\n set: function (settings) {\n cache.settings = angular.copy(settings);\n },\n isDifferent: function() {\n return !equals(cache.settings, $scope.leadForm);\n }\n };\n\n cache.set($scope.leadForm);\n\n $scope.save = function() {\n $scope.saveInProgress = true;\n var leadSettings = angular.copy($scope.leadForm);\n\n if(leadSettings._id) {\n leadSettings.modifiedDate = new Date();\n leadSettings.modifiedByUser = User.username;\n } else {\n leadSettings.createdByUser = User.username;\n leadSettings.createdDate = new Date();\n }\n\n LeadWidgetSettings.updateEnhancedSettings(leadSettings, function(updatedSettings) {\n $scope.leadForm = updatedSettings;\n $scope.saveInProgress = false;\n alerts.pushSuccess('Successfully Updated Enhanced Lead Settings');\n }, function (data, status) {\n AppointmentSchedulerWidgetSettings.defaultErrorHandler(data, status);\n $scope.saveInProgress = false;\n });\n };\n\n /**\n * Watches\n */\n\n var ignoreRedirectWarning = false;\n\n $scope.$on('$stateChangeStart', function (event, toState, toParams) {\n var continueNavigation = function () {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, { location: 'replace' });\n };\n\n if(!ignoreRedirectWarning && cache.isDifferent()) {\n event.preventDefault();\n\n modalService.showStaticConfirmation(\n 'item.message.do-you-want-changes-to-be-saved?',\n function () {\n $scope.onSaveClick(continueNavigation);\n },\n continueNavigation\n );\n }\n });\n\n\n });\n\n});\n\n","define('modules/task/module',[\n 'angular',\n 'ui-router',\n './task-modal-service',\n './search/task-search-directive',\n '../common/enums-service',\n '../common/permissions-service',\n '../common/modal-once-service',\n '../modal/modal-service',\n '../common/search-ctrl',\n '../resources/employee',\n '../resources/item',\n '../resources/task',\n '../resources/task-type',\n '../common/resources-helper-service'\n], function (angular) {\n\n return angular.module('bl.task', [\n 'ui.bootstrap',\n 'ui.router',\n 'bl.enums',\n 'bl.permissions',\n 'bl.modal',\n 'bl.modal-once',\n 'bl.common.search-ctrl',\n 'bl.resources.task',\n 'bl.resources.employee',\n 'bl.resources.item',\n 'bl.resources.task-type',\n 'bl.task.search',\n 'bl.task.modal',\n 'bl.resources-helper'\n ])\n\n .config(function ($stateProvider, Enums, Permissions) {\n var template = function (name) {\n return 'js/modules/task/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('task', {\n url: '/task'\n })\n .state('tasks', {\n url: '/tasks',\n permissions: Permissions.TASKS,\n views: {\n 'main@': {\n controller: 'TasksCtrl',\n templateUrl: template('tasks')\n }\n }\n })\n .state('tasks.past-due', {\n url: '/past-due',\n permissions: Permissions.TASKS,\n views: {\n 'main@': {\n controller: 'PastDueTasksCtrl',\n templateUrl: template('past-due-tasks')\n }\n }\n });\n });\n\n});\n\n","define('modules/transaction/details/modals/sign-sales-agreement-ctrl',['../module', 'angular', 'signature-pad', 'underscore'], function (module, angular, SignaturePad, _) {\n 'use strict';\n\n module.controller('SignSalesAgreementCtrl', function\n ($scope,\n $modalInstance,\n $timeout,\n $window,\n $stateParams,\n CompanySetting,\n posSettings,\n agreement,\n customSalesAgreements,\n transactionType,\n awsPolicyAndSignature,\n signatures,\n Signature,\n User,\n Upload,\n modalService,\n security) {\n\n var signaturePad;\n $scope.signatureError = false;\n var transactionTypeSettingMap = {\n 'Sale': 'POS_SALE_TC_TYPE',\n 'Special Order': 'POS_SALES_ORDER_TC_TYPE',\n 'Layaway': 'POS_LAYAWAY_TC_TYPE',\n 'Return': 'POS_RETURN_TC_TYPE'\n };\n\n // Tune up the modal\n $timeout(function () {\n angular.element('.modal-backdrop:has(+ .modal.__protected)').css('opacity', 0.98);\n });\n\n signatures = _(signatures).filter( function (sig) {\n return !sig.paymentPlanId && !sig.preAuthPaymentId && !sig.paymentId ;\n });\n\n $scope.textareaHeight = window.innerHeight - 400;\n\n $scope.agreement = agreement;\n if (!agreement) {\n $scope.agreement = _(posSettings).findWhere({type: CompanySetting[transactionTypeSettingMap[transactionType]]});\n }\n\n if (signatures && signatures.length) {\n _(signatures).sortBy('createdDate').reverse();\n $scope.lastSignature = _(signatures).findWhere({salesAgreementId: $scope.agreement.id});\n if (!$scope.lastSignature) {\n $scope.lastSignature = _(signatures).findWhere({salesAgreementId: null});\n }\n }\n\n var initSignaturePad = function () {\n var canvas = angular.element('.signature_pad')[0];\n\n var ratio = Math.max($window.devicePixelRatio || 1, 1);\n canvas.width = canvas.offsetWidth * ratio;\n canvas.height = canvas.offsetHeight * ratio;\n canvas.getContext(\"2d\").scale(ratio, ratio);\n\n signaturePad = new SignaturePad(canvas, {\n minWidth: 0.7,\n penColor: '#9686b1'\n });\n };\n\n if (!$scope.lastSignature) {\n $timeout(initSignaturePad);\n }\n\n $scope.deleteSignature = function () {\n modalService.showConfirmation('transaction.message.would-like-to-delete-signature?', function () {\n if (!security.hasPermission('CAN_EDIT_DIGITAL_SIGNATURES')) {\n modalService.showMessage('transaction.message.cannot-delete-signature');\n return;\n }\n\n $scope.lastSignature.$remove(function () {\n $scope.lastSignature = null;\n $timeout(initSignaturePad);\n });\n });\n };\n\n $scope.clearSignature = function () {\n signaturePad.clear();\n };\n\n $scope.disabledSignAndAccept = function () {\n return signaturePad && signaturePad.isEmpty();\n };\n\n $scope.signAndAccept = function () {\n $scope.signatureError = false;\n\n if (signaturePad && signaturePad.isEmpty()) {\n $scope.signatureError = true;\n return;\n }\n\n $scope.signAndAccept.isInProgress = true;\n\n signaturePad._canvas.toBlob(function (blob) {\n Upload.signatureUpload(blob, awsPolicyAndSignature, function (imageUrl) {\n if ($scope.lastSignature) {\n $scope.lastSignature.imageUrl = imageUrl;\n $scope.lastSignature.createdDate = Date.now();\n $scope.lastSignature.createdByUser = User.username;\n\n $scope.lastSignature.$update($scope.$close);\n } else {\n var signature = new Signature({\n transactionId: $stateParams.id,\n imageUrl: imageUrl,\n salesAgreementId: $scope.agreement.id,\n captureMethod: 2,\n createdByUser: User.username\n });\n\n signature.$save($scope.$close);\n }\n });\n });\n };\n });\n});\n\n","define('modules/transaction/details/modals/request-signature-ctrl',['../../../contact/module', 'angular', 'underscore', 'moment'], function (module, angular, _, moment) {\n 'use strict';\n\n module.controller('RequestSignatureCtrl', function ($scope, $q, $modalInstance, $timeout, $state, $window, $stateParams, $translate, $interval, AppLicense, SMSTemplate, EmailTemplate, Account, TransactionPaymentsModalService, transactionType, transaction, SMS, Enums, Email, User, MergeFields, CompanySetting, alerts, modalService, companyProfile, emailTemplates, smsTemplates, twilioSettings, customSalesAgreements, posSettings, contact) {\n\n $scope.transaction = transaction;\n $scope.twilioSettings = twilioSettings;\n $scope.request = { sendEmail: false, sendSms: false, allowedToSendEmail: true };\n $scope.email = {};\n $scope.sms = {};\n\n $scope.hasAllStripeSettings = TransactionPaymentsModalService.hasAllStripeSettings(posSettings);\n $scope.hasAllChargeItProSettings = TransactionPaymentsModalService.hasAllChargeItProSettings(posSettings);\n $scope.hasAllFullSteamSettings = TransactionPaymentsModalService.hasAllFullSteamSettings(posSettings);\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n var licenseStatus = AppLicense.get()[\"status\"];\n $scope.request.allowedToSendEmail = licenseStatus === 'A';\n\n $scope.onlinePaymentAccess = licenseHasPortalAccess || $scope.hasAllFullSteamSettings || $scope.hasAllChargeItProSettings || $scope.hasAllStripeSettings;\n\n $scope.sent = {\n email: false,\n sms: false\n };\n\n var transactionTypeSettingMap = {\n 'Sale': 'POS_SALE_TC_TYPE',\n 'Special Order': 'POS_SALES_ORDER_TC_TYPE',\n 'Layaway': 'POS_LAYAWAY_TC_TYPE',\n 'Return': 'POS_RETURN_TC_TYPE'\n };\n\n var setting = _(posSettings).findWhere({type: 'USE_DIG_SIGN'});\n $scope.digitalSignatureEnabled = setting && setting.value === 'Y';\n\n var signatureRequestMergeFields = _.where(MergeFields, { paymentRequest: false, signatureRequest: true, signatureRequestType: 'TRX' });\n\n buildTemplates(emailTemplates, smsTemplates, true);\n\n $scope.state = {\n isContactChosen: false\n };\n\n $scope.existingContact = {\n contactName: null\n };\n\n var chosenContact;\n\n $scope.chooseContact = function (contact) {\n if (contact) {\n $scope.state.isContactChosen = true;\n $scope.contact = {};\n\n $scope.existingContact.contactName = contact.firstName + \" \" + (contact.lastName || '');\n\n angular.extend($scope.contact, contact);\n\n chosenContact = angular.copy($scope.contact);\n }\n };\n\n $scope.unselectContact = function () {\n chosenContact = undefined;\n delete $scope.contact;\n\n $scope.existingContact.contactName = null;\n\n $scope.state.isContactChosen = false;\n $scope.request.sendEmail = false;\n $scope.request.sendSms = false;\n };\n\n $scope.chooseContact(contact);\n\n populateScopeWithSettings($scope, posSettings);\n\n $scope.senders = [\n {\n email: companyProfile.emailAddress,\n name: companyProfile.name,\n id: companyProfile.id\n },\n {\n email: User.emailAddress,\n name: User.firstName + ' ' + User.lastName,\n id: User.id\n }\n ];\n\n $scope.email.sender = $scope.senders[0];\n\n $scope.selectEmailTemplate = function (template) {\n $scope.selectedEmailTemplate = template ? template : null;\n $scope.email.message = template && template.body ? template.body : '';\n $scope.email.subject = template && template.subject ? template.subject : '';\n $scope.email.contentType = template && template.contentType ? template.contentType : 'text/html';\n };\n\n $scope.selectSmsTemplate = function (template) {\n $scope.selectedSmsTemplate = template ? template : null;\n $scope.sms.message = template && template.body ? template.body : '';\n };\n\n $scope.onAgreementChange = function (agreement) {\n $scope.selectedAgreement = agreement ? agreement : null;\n };\n\n $scope.requestForSignature = function () {\n if(licenseStatus !== 'A') {\n $scope.request.sendEmail = false;\n }\n\n if (!$scope.contact) {\n // Error message, contact required...\n modalService.showMessage('alerts.request-payment.contact-required', 'modal-service.error');\n return false;\n }\n\n if (!$scope.request.sendEmail && !$scope.request.sendSms) {\n // Error message, Delivery Method Required\n modalService.showMessage('alerts.request-payment.delivery-method-required', 'modal-service.error');\n return false;\n }\n\n if (!$scope.selectedAgreement) {\n // Error message to please select a sales agreement...\n modalService.showMessage('alerts.request-payment.sales-agreement-required', 'modal-service.error');\n return false;\n }\n\n if ($scope.request.sendEmail && !$scope.selectedEmailTemplate) {\n // Error message to please select a email template...\n modalService.showMessage('alerts.request-payment.email-template-required', 'modal-service.error');\n return false;\n }\n\n if ($scope.request.sendSms && !$scope.selectedSmsTemplate) {\n // Error message to please select a sms template...\n modalService.showMessage('alerts.request-payment.sms-template-required', 'modal-service.error');\n return false;\n }\n\n var email;\n if ($scope.request.sendEmail) {\n\n email = {\n recipientId: $scope.contact.id,\n recipientType: Email.CONTACT_TYPE,\n subject: $scope.email.subject,\n message: $scope.email.message,\n cc: $scope.email.cc,\n contentType: $scope.email.contentType,\n senderEmail: $scope.email.sender.email,\n senderEmployeeId: User.id,\n senderName: $scope.email.sender.name,\n recipientEmail: $scope.contact.emailAddress,\n recipientName: $scope.contact.firstName + ' ' + $scope.contact.lastName,\n status: Enums.statuses.sent.value,\n createdByUser: User.username,\n transactionId: $scope.transaction ? $scope.transaction.id : null,\n salesAgreementId: $scope.selectedAgreement ? $scope.selectedAgreement.id : null,\n };\n }\n\n var sms;\n var smss = [];\n if ($scope.request.sendSms) {\n\n sms = {\n message: $scope.sms.message,\n toPhoneNumber: $scope.contact.mobilePhoneNumber,\n employeeId: User.id,\n fromPhoneNumber: twilioSettings.phoneNumber,\n contactId: $scope.contact.id,\n deliverOn: new Date(),\n status: Enums.statuses.sent.value,\n createdByUser: User.username,\n transactionId: $scope.transaction ? $scope.transaction.id : null,\n salesAgreementId: $scope.selectedAgreement ? $scope.selectedAgreement.id : null\n };\n\n smss.push(sms);\n }\n\n if ($scope.request.sendEmail && $scope.request.sendSms) {\n $scope.sending = true;\n\n // send email and sms, close modal\n $q.all([\n Email.send(email),\n SMS.sendMultiple(smss)\n ]).then(function (responses) {\n $scope.sending = false;\n\n if (responses[0]) {\n $scope.sent.email = true;\n $scope.request.sendEmail = false;\n alerts.pushSuccess('alerts.contact.success-your-email-is-being-sent');\n }\n\n if (responses[1].errors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: {message: responses[1].errors[0].message}\n });\n } else if (responses[1].optedErrors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: {message: responses[1].optedErrors[0].message}\n });\n } else {\n $scope.sent.sms = true;\n $scope.request.sendSms = false;\n alerts.pushSuccess({\n key: 'alerts.contact.successfully-sent-sms',\n params: {number: responses[1].successes.length}\n });\n }\n\n\n if ($scope.sent.email && $scope.sent.sms) {\n $scope.$close();\n }\n\n }).catch(function (error) {\n console.error(error);\n\n $scope.sending = false;\n });\n } else if ($scope.request.sendEmail) {\n $scope.sending = true;\n\n // send email, close modal\n Email.send(email, function (emailResult) {\n $scope.sending = false;\n alerts.pushSuccess('alerts.contact.success-your-email-is-being-sent');\n $scope.$close();\n\n }, function (err) {\n Email.defaultErrorHandler(err);\n $scope.sending = false;\n });\n } else if ($scope.request.sendSms) {\n $scope.sending = true;\n\n // send, close, pass result\n var sendSMS = SMS.sendMultiple(smss);\n\n sendSMS.then(function (twilioResult) {\n $scope.sending = false;\n\n if (twilioResult.errors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: { message: twilioResult.errors[0].message }\n });\n } else if (twilioResult.optedErrors.length) {\n alerts.pushError({\n key: 'alerts.contact.failed-sent-sms',\n params: { message: twilioResult.optedErrors[0].message }\n });\n } else {\n alerts.pushSuccess({\n key: 'alerts.contact.successfully-sent-sms',\n params: { number: twilioResult.successes.length }\n });\n\n $scope.$close();\n }\n }, function (err) {\n SMS.defaultErrorHandler(err);\n $scope.sending = false;\n });\n }\n\n };\n\n $scope.installPaymentTemplates = function() {\n Account.installPaymentTemplates(function() {\n var emailTemplatePromise = EmailTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n var smsTemplatePromise = SMSTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n\n $q.all([emailTemplatePromise, smsTemplatePromise]).then(function(resources) {\n if(resources && resources.length) {\n buildTemplates(resources[0], resources[1]);\n alerts.push('alerts.settings.account.successfully-installed-payment-templates');\n }\n });\n });\n };\n\n function populateScopeWithSettings($scope, posSettings) {\n $scope.agreements = [];\n\n var agreement = _(posSettings).findWhere({ type: CompanySetting[transactionTypeSettingMap[transactionType]] });\n if(agreement) {\n $scope.agreements.push(new CompanySetting(agreement));\n }\n\n _.each(customSalesAgreements, function (customSalesAgreement) {\n $scope.agreements.push(customSalesAgreement);\n });\n\n //pre-select the first agreement.\n $scope.selectedAgreement = agreement;\n }\n\n function buildTemplates(emailTemps, smsTemps) {\n $scope.emailTemplates = [];\n $scope.smsTemplates = [];\n\n if (emailTemps && emailTemps.length) {\n _.each(signatureRequestMergeFields, function (mergeField) {\n _.each(emailTemps, function (template) {\n if (template && template.body) {\n if (includesValue(template.body, mergeField.key)) {\n if ($scope.emailTemplates.indexOf(template) === -1) {\n $scope.emailTemplates.push(template);\n }\n }\n }\n });\n });\n }\n\n if (smsTemps && smsTemps.length) {\n _.each(signatureRequestMergeFields, function (mergeField) {\n _.each(smsTemps, function (template) {\n if (template && template.body) {\n if (includesValue(template.body, mergeField.key)) {\n if ($scope.smsTemplates.indexOf(template) === -1) {\n $scope.smsTemplates.push(template);\n }\n }\n }\n });\n });\n }\n }\n\n function includesValue(container, value) {\n var returnValue = false;\n var pos = container.indexOf(value);\n if (pos >= 0) {\n returnValue = true;\n }\n return returnValue;\n }\n\n $scope.gotoContact = function () {\n $scope.$close();\n $state.go('contact.personal_info', {id: $scope.contact.id});\n };\n\n });\n});\n\n","define('modules/transaction/details/transaction-details-ctrl',['./module', 'angular', 'underscore', 'moment'], function(module, angular, _, moment) {\n 'use strict';\n\n module.controller('TransactionDetailsCtrl', function(\n $compile,\n $modal,\n $scope,\n $rootScope,\n $state,\n $stateParams,\n $timeout,\n $window,\n $locale,\n $location,\n $translate,\n $filter,\n $q,\n AppLicense,\n alerts,\n eventsBus,\n Contact,\n EmailService,\n Sms,\n employees,\n Enums,\n Item,\n ItemModal,\n modalService,\n PromRegistryModal,\n PaymentPlanModal,\n ResourcesHelper,\n posSettings,\n clientPortalSettings,\n customSalesAgreements,\n customTerminalContracts,\n commSettings,\n registers,\n taxCodes,\n transaction,\n Signature,\n Transaction,\n TransactionItem,\n Quote,\n ItemAddOn,\n ItemHelper,\n Address,\n PaymentPlan,\n CommissionItem,\n POSCancel,\n POSCancelType,\n TransactionPaymentsModalService,\n TaxJarSettings,\n SmartFlow,\n User,\n security,\n Permissions,\n EventMember,\n itemSizesModal,\n EventItem,\n FullSteamHelper,\n FullSteam,\n NF525,\n FULLSTEAM) {\n var signatures;\n\n var transactionTypeSettingMap = {\n 'Sale': 'POS_SALE_TC',\n 'Special Order': 'POS_SALES_ORDER_TC',\n 'Layaway': 'POS_LAYAWAY_TC',\n 'Return': 'POS_RETURN_TC'\n };\n\n var license = AppLicense.get();\n $scope.security = security;\n $scope.registers = registers;\n $scope.isSale = transaction.typeId === Enums.trxTypes.sale.value;\n $scope.isSpecialOrder = transaction.typeId === Enums.trxTypes.special_order.value;\n $scope.isLayaway = transaction.typeId === Enums.trxTypes.layaway.value;\n $scope.isReturn = transaction.typeId === Enums.trxTypes.return_trx.value;\n $scope.isSmartFlowsEnabled = security.hasPlanPermission('smartFlows');\n $scope.employees = ResourcesHelper.adaptList('employees', employees);\n\n $scope.taxCodes = _.filter(taxCodes, function(taxCode) {\n return taxCode && taxCode.description !== 'BLeCommerceTax';\n });\n\n $scope.transaction = transaction;\n $scope.showDiscountPercent = $scope.transaction.addtlDiscountType === 1;\n $scope.locale = $locale;\n $scope.isNF525 = $locale.isNF525;\n $scope.isNF525AndNotTrialing = $locale.isNF525 === true && license.status !== 'T';\n $scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;\n $scope.defaultSalesAgreement = _(posSettings).findWhere({ type: transactionTypeSettingMap[getTransactionType()] });\n $scope.hasAllFullSteamSettings = TransactionPaymentsModalService.hasAllFullSteamSettings(posSettings);\n $scope.hasAllChargeItProSettings = TransactionPaymentsModalService.hasAllChargeItProSettings(posSettings);\n $scope.hasAllStripeSettings = TransactionPaymentsModalService.hasAllStripeSettings(posSettings);\n $scope.agreements = customSalesAgreements;\n $scope.contracts = customTerminalContracts;\n $scope.canUpdateCompletedDate = security.hasPermission(Permissions.UPDATE_TRX_COMPLETED_DATE);\n $scope.canUpdateZeroOutDate = security.hasPermission(Permissions.UPDATE_TRX_ZERO_OUT_DATE);\n $scope.itemAddOnsPerm = {\n add: security.hasPermission(Permissions.ADD_ITEM_ADD_ON),\n set: security.hasPermission(Permissions.SELECT_ITEM_ADD_ON),\n editNotes: security.hasPermission(Permissions.EDIT_ITEM_ADD_ON_NOTE),\n editDesc: security.hasPermission(Permissions.EDIT_ITEM_ADD_ON_DESC),\n editPrice: security.hasPermission(Permissions.EDIT_ITEM_ADD_ON_PRICE),\n remove: security.hasPermission(Permissions.REMOVE_ITEM_ADD_ON),\n addOnRequired: security.hasPermission(Permissions.TRX_ITEM_ADD_ON_REQUIRED)\n };\n\n $scope.commissionPerm = {\n assignEmployee: security.hasPermission(Permissions.EDIT_COMMISSION_EMPLOYEE),\n viewAssignEmployee: security.hasPermission(Permissions.VIEW_COMMISSION_EMPLOYEE),\n editStatus: security.hasPermission(Permissions.EDIT_COMMISSION_STATUS),\n viewStatus: security.hasPermission(Permissions.VIEW_COMMISSION_STATUS)\n };\n\n $scope.editCompletedDate = false;\n $scope.editZeroOutDate = false;\n $scope.datepickerOpened = false;\n $scope.savingInProgress = false;\n\n $scope.lastPaymentDate = getLastPaymentDate($scope.transaction);\n\n $scope.paymentPlan = {\n access: false,\n plan: null,\n hasPlan: false,\n completedPlans: []\n };\n\n $scope.reason = {\n access: false,\n model: null,\n reasons: []\n };\n\n var previousCompletedDate = $scope.transaction.completedDate;\n var previousZeroOutDate = $scope.transaction.balanceDueZeroDate;\n\n // Commission Settings\n var commSetting = _(commSettings).findWhere({ type: 'COMM_LEVELS' });\n $scope.showCommissionLevels = commSetting && commSetting.value === 'Y';\n\n commSetting = _(commSettings).findWhere({ type: 'COMM_STATUS' });\n $scope.showCommissionStatus = commSetting && commSetting.value === 'Y';\n\n // POS Settings\n var setting = _(posSettings).findWhere({ type: 'POS_ALT_PRINT' });\n $scope.doShowDescription = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_MANUAL_TAX_CALC' });\n $scope.enableManualTaxCalculation = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_COLOR_SIZE_VALID' });\n $scope.colorOrSizeAllowCustomVal = setting && setting.value === 'N';\n\n setting = _(posSettings).findWhere({ type: 'POS_COLOR_SIZE_REQUIRED' });\n $scope.colorAndSizeRequired = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_COLOR_SIZE_BLANK' });\n var colorAndSizeBlank = $scope.isSpecialOrder && setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'USE_DIG_SIGN' });\n $scope.digitalSignatureEnabled = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_TRX_DISCOUNT' });\n $scope.trxDiscountEnabled = setting && setting.value === 'Y';\n\n setting = _(posSettings).findWhere({ type: 'POS_ENABLED_ZERO_DUE_DATE' });\n $scope.showZeroOutDate = setting && setting.value === 'Y';\n $scope.isUS = $locale.isUS;\n\n var licenseHasDigitalSignatureFeature = license.plan['digitalSignature'];\n if(!licenseHasDigitalSignatureFeature) {\n $scope.digitalSignatureEnabled = false;\n }\n\n // determine visibility of client portal feature\n var portalFeature = _(clientPortalSettings).findWhere({ type: 'CP_FEATURE_DIGITAL_SIGNATURES' });\n var portalFeatureEnabled = portalFeature ? portalFeature.value === 'Y' : true;\n var userHasPermission = security.hasPermission(\"REQUEST_SIGNATURES\");\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n\n var onlinePaymentsEnabled = $scope.hasAllFullSteamSettings || $scope.hasAllChargeItProSettings || $scope.hasAllStripeSettings;\n $scope.requestSignatureAvailable = (onlinePaymentsEnabled && userHasPermission) || (licenseHasPortalAccess && portalFeatureEnabled && userHasPermission);\n\n $scope.voidedStatusValue = Enums.statuses.voided.value;\n $scope.completedStatusValue = Enums.statuses.complete.value;\n $scope.pendingStatusValue = Enums.statuses.pending.value;\n $scope.readonly = $scope.transaction.status === Enums.statuses.complete.value\n || $scope.transaction.status === Enums.statuses.voided.value\n || !security.hasPermission(\"EDIT_TRANSACTIONS\");\n\n $scope.trxItemStatuses = [Enums.trxItemStatuses.on_order, Enums.trxItemStatuses.awaiting_pickup,\n Enums.trxItemStatuses.picked_up, Enums.trxItemStatuses.in_alterations];\n\n if($scope.transaction && $scope.transaction.lineItems && $scope.transaction.lineItems.length) {\n var hasComputedTax = false;\n _($scope.transaction.lineItems).each(function(item) {\n if(item.taxCodeIsComputed && item.taxCodeDescription !== 'BLeCommerceTax') {\n hasComputedTax = true;\n }\n });\n\n if(hasComputedTax) {\n Transaction.checkForTaxChange($scope.transaction, function(result) {\n if(result && result.hasTaxChanged) {\n modalService.showConfirmation('settings.tax-jar.message.would-you-like-to-recalculate?', function() {\n\n $scope.queriesInProgress += 1;\n $scope.recalculatingTax = true;\n $scope.savingInProgress = true;\n\n Transaction.updateLineItemsAndCalculateTaxes($scope.transaction, false, function(response) {\n if(response && response.transaction) {\n\n var savedTransaction = response.transaction;\n\n if(savedTransaction.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'fee',\n 'notes'\n ));\n\n updateLineItemsAfterUpdatingShippingInfo(savedTransaction);\n }\n\n $scope.savingInProgress = false;\n }).finally(function() {\n // without timeout saving state will be \"jumpy\"\n $timeout(function() {\n $scope.queriesInProgress -= 1;\n }, 1000);\n\n });\n });\n }\n }, function() {\n // do nothing\n $scope.savingInProgress = false;\n });\n }\n }\n\n var onAddLineItem = function(item, searchField, ignoreAddOns, addOnItemCallback) {\n var transactionItem = itemToTransactionItem(item);\n transactionItem.checkForUpCharge = !!(colorAndSizeBlank);\n\n $scope.savingInProgress = true;\n Transaction.addLineItem(transactionItem, function(transaction) {\n $scope.savingInProgress = false;\n $scope.isNewTransaction = false;\n angular.extend($scope.transaction, _(transaction).omit('lineItems'));\n _($scope.transaction.lineItems).each(function(trxItem, index) {\n trxItem.version = transaction.lineItems[index].version;\n\n _(trxItem.lineItemAddOns).each(function(addOn, i) {\n if(transaction.lineItems[index].lineItemAddOns && transaction.lineItems[index].lineItemAddOns.length && transaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = transaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n\n if(transaction.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n var addedItem = _(transaction.lineItems).last();\n $scope.transaction.lineItems.push(addedItem);\n $scope.transaction.modifiedDate = Date.now();\n\n // Check if smart flows have been triggered if we don't already have a trigger date/time\n if(!$scope.transaction.smartFlowsTriggeredDateTime && security.hasPlanPermission('smartFlows')) {\n var req = {triggerId: $scope.getSmartFlowTriggerId()};\n req.loggedInEmployee = User;\n req.transaction = $scope.transaction;\n\n SmartFlow.getTriggeredFlowsForDisplay(req, function(flows) {\n if(flows && flows.length > 0) {\n $scope.transaction.smartFlowsTriggeredDateTime = new Date().valueOf();\n }\n });\n }\n\n if(!$scope.isReturn && !ignoreAddOns) {\n $scope.getAvailableAddOnsAndOpenModal(addedItem, item, {\n showModalOnlyIfAddOnsAvailable: true,\n fetchItemAddOns: $scope.itemAddOnsPerm.set\n });\n }\n\n if(!$scope.isReturn && item.promptToRegister && security.hasPermission(Permissions.REGISTRY)) {\n PromRegistryModal.registerToProm(transactionItem, $scope.transaction, false, updateContactEventDateOnTransaction);\n }\n\n if($scope.isSpecialOrder && item.status === Enums.statuses.discontinued.value && item.quantityOnHand > 0) {\n //item is discontinued, but there is still some in stock.\n modalService.showMessage('transaction.message.item-added-to-discontinued-order');\n }\n\n validateTransactionItems();\n\n if(addOnItemCallback) {\n addOnItemCallback();\n }\n });\n };\n\n $scope.itemSelectorOptions = {\n addNewItemSaveButtonLabel: $translate.instant('transaction.save-and-add-to') + getTransactionTypeDescription(),\n onAddNewItem: getAllItemValues,\n onChooseItem: getAllItemValues\n };\n\n function getAllItemValues(item, field) {\n ItemAddOn.listByItemId(item.id, function(itemAddOns) {\n item.itemAddOns = itemAddOns;\n onAddLineItem(item, field);\n });\n }\n\n if($scope.isSpecialOrder) {\n $scope.itemSelectorOptions.additionalFilterObject = {\n canAddToSalesOrder: true\n };\n } else if($scope.isSale || $scope.isLayaway) {\n $scope.itemSelectorOptions.additionalFilterObject = {\n canAddToSale: true,\n trxId: transaction.typeId\n };\n }\n\n if(isNF525France() && ($scope.isReturn || $scope.transaction.status === 'V')) {\n initCancellationReasons($scope.transaction);\n }\n\n $scope.canCreatePlan = security.hasPlanPermission('paymentPlanAccess') && security.hasPermission('CREATE_PAYMENT_PLAN');\n $scope.canViewPlan = security.hasPlanPermission('paymentPlanAccess') && security.hasPermission('VIEW_PAYMENT_PLAN');\n\n function checkForPaymentPlan() {\n if(security.hasPlanPermission('paymentPlanAccess') && ($scope.hasAllFullSteamSettings || $scope.hasAllChargeItProSettings || $scope.hasAllStripeSettings)) {\n PaymentPlan.checkForPaymentPlanOnTransaction($scope.transaction.id, function(response) {\n var plan = response.currentPlan;\n if(plan && plan.id) {\n $scope.paymentPlan.plan = plan;\n $scope.paymentPlan.hasPlan = true;\n }\n\n if(response.completedPlans && response.completedPlans.length > 1) {\n $scope.paymentPlan.hasPlan = true;\n }\n\n $scope.paymentPlan.completedPlans = response.completedPlans;\n $scope.paymentPlan.access = true;\n });\n }\n }\n\n checkForPaymentPlan();\n\n function checkForQuote() {\n Quote.findByTransactionId($scope.transaction.id, function(response) {\n var quote = response;\n if(quote && quote.convertedDate && quote.convertedBy) {\n $scope.transaction.convertedDate = quote.convertedDate;\n $scope.transaction.convertedBy = quote.convertedBy;\n }\n });\n }\n\n checkForQuote();\n\n function isNF525France() {\n return $scope.isNF525 && !!($locale && $locale.id === 'FR');\n }\n\n function initCancellationReasons(trx) {\n try {\n if(!$scope.reason.fetched && isNF525France() && ($scope.isReturn || trx.status === 'V')) {\n $q.all([\n POSCancelType.query({ filterObject: { status: Enums.statuses.active.value }}),\n POSCancel.findByTransactionId(trx.id)\n ]).then(function(responses) {\n $scope.reason.fetched = true;\n if(responses && responses.length) {\n $scope.reason.reasons = responses[0];\n var reason = responses[1];\n\n if(!reason || _.isEmpty(reason) || !reason.id) {\n $scope.reason.model = initReasonModel(trx);\n } else {\n $scope.reason.model = reason;\n }\n\n if($scope.reason.reasons && $scope.reason.reasons.length) {\n $scope.reason.access = true;\n }\n }\n }).catch(function(error) {\n $scope.reason.fetched = true;\n console.log('Failed to load transaction cancel reasons', error);\n });\n }\n } catch(e) {\n $scope.reason.fetched = true;\n console.log('Failed to load transaction cancel reasons', e);\n }\n }\n\n function initReasonModel(trx) {\n return _.extend(new POSCancel(), {\n createdByUser: User.username,\n transactionId: trx && trx.id,\n description: ''\n });\n }\n\n var ignoreRedirectWarning = false;\n\n /**\n * Methods\n */\n\n $scope.queriesInProgress = 0;\n\n $scope.saveTransactionItem = function(item, callback) {\n $scope.savingInProgress = true;\n $scope.queriesInProgress += 1;\n item.modifiedDate = Date.now();\n item.modifiedByUser = User.username;\n\n Transaction.updateLineItem(item, function(savedTransaction) {\n $scope.savingInProgress = false;\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'shippingAndHandling',\n 'fee',\n 'notes'\n ));\n\n if(savedTransaction.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n var savedTransactionItem = _(savedTransaction.lineItems).findWhere({ id: item.id });\n\n // Update special field of current line item\n item.adjustedPrice = savedTransactionItem.adjustedPrice;\n item.discountPercent = savedTransactionItem.discountPercent;\n\n item.shippingTax = savedTransactionItem.shippingTax;\n item.taxAmount = savedTransactionItem.taxAmount;\n\n // Update version of all line items\n _($scope.transaction.lineItems).each(function(transactionItem, index) {\n transactionItem.version = savedTransaction.lineItems[index].version;\n transactionItem.modifiedDate = savedTransaction.lineItems[index].modifiedDate;\n transactionItem.modifiedByUser = savedTransaction.lineItems[index].modifiedByUser;\n transactionItem.lineItemAddOns = savedTransaction.lineItems[index].lineItemAddOns;\n\n _(transactionItem.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n addOn.modifiedDate = savedTransaction.lineItems[index].lineItemAddOns[i].modifiedDate;\n addOn.modifiedByUser = savedTransaction.lineItems[index].lineItemAddOns[i].modifiedByUser;\n }\n });\n });\n }, function(data, status) {\n Transaction.defaultErrorHandler(data, status);\n $scope.savingInProgress = false;\n }).finally(function() {\n // without timeout saving state will be \"jumpy\"\n $timeout(function() {\n $scope.queriesInProgress -= 1;\n }, 1000);\n\n if(callback) {\n callback();\n }\n });\n };\n\n $scope.saveTransaction = $scope.transaction.save = _.debounce(function(callback) {\n $scope.queriesInProgress += 1;\n $scope.savingInProgress = true;\n $scope.transaction.modifiedDate = Date.now();\n $scope.transaction.modifiedByUser = User.username;\n\n //make sure the employeeName is set properly\n if($scope.transaction.employeeId >= 0 && $scope.employees) {\n var employee = _($scope.employees).findWhere({ id: $scope.transaction.employeeId });\n if(employee) {\n $scope.transaction.employeeName = employee.fullName;\n }\n }\n\n Transaction.put(_($scope.transaction).omit('save'), function(savedTransaction) {\n $scope.savingInProgress = false;\n $scope.isNewTransaction = false;\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'shippingAndHandling',\n 'fee',\n 'notes'\n ));\n _($scope.transaction.lineItems).each(function(item, index) {\n item.quantitySold = savedTransaction.lineItems[index].quantitySold;\n item.version = savedTransaction.lineItems[index].version;\n\n _(item.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n\n $scope.readonly = $scope.transaction.status === Enums.statuses.complete.value || $scope.transaction.status === Enums.statuses.voided.value;\n\n if(callback) {\n callback(savedTransaction);\n }\n }, function(data, status) {\n Transaction.defaultErrorHandler(data, status);\n $scope.savingInProgress = false;\n }).finally(function() {\n $scope.queriesInProgress -= 1;\n });\n }, 1000);\n\n $scope.loadSignatures = function() {\n setting = _(posSettings).findWhere({ type: 'SALES_AGREEMENT_REQ' });\n var isSignatureRequiredForThisType = setting && setting.value && setting.value.split(',').indexOf(transaction.typeId.toString()) !== -1;\n\n var removeSignatureNotAssociatedWithTrxType = function(signatures) {\n var foundAgreementFromSignature = false;\n\n _(posSettings).each(function(setting) {\n _(signatures).each(function(signature) {\n if(signature && setting && signature.salesAgreementId && signature.salesAgreementId === setting.id && !foundAgreementFromSignature) {\n foundAgreementFromSignature = true;\n var trxAgreement = _(posSettings).findWhere({\n id: signature.salesAgreementId,\n type: transactionTypeSettingMap[getTransactionType()]\n });\n if(!trxAgreement) {\n signatures = _(signatures).without(signature);\n }\n }\n });\n });\n\n return signatures;\n };\n\n //rework maybe?\n var removePaymentSignatures = function(signatures) {\n return _(signatures).filter(function(signature) {\n return !signature.paymentPlanId && !signature.preAuthPaymentId && !signature.paymentId;\n });\n };\n\n Signature.listAllForTransaction(transaction.id, function(loadedSignatures) {\n signatures = removeSignatureNotAssociatedWithTrxType(loadedSignatures);\n var salesAgreementSignatures = removePaymentSignatures(signatures);\n\n $scope.markSignSalesAgreementGreen = _(salesAgreementSignatures).some(function(signature) {\n return Boolean(signature.transactionId);\n });\n\n $scope.markSignSalesAgreementRed = !$scope.markSignSalesAgreementGreen && isSignatureRequiredForThisType;\n\n //now mark each sales agreement as signed, if we have a signature for it\n _(salesAgreementSignatures).each(function(signature) {\n var agreement;\n if(signature.salesAgreementId && signature.transactionId) {\n agreement = _(posSettings).findWhere({\n id: signature.salesAgreementId,\n type: transactionTypeSettingMap[getTransactionType()]\n });\n if(!agreement) {\n agreement = _(customSalesAgreements).findWhere({ id: signature.salesAgreementId });\n }\n\n if(!agreement) {\n agreement = _(customTerminalContracts).findWhere({ id: signature.salesAgreementId });\n }\n } else if(signature.transactionId) {\n agreement = _(posSettings).findWhere({ type: transactionTypeSettingMap[getTransactionType()] });\n }\n\n if(agreement) {\n agreement.signed = true;\n }\n\n });\n\n });\n };\n\n $scope.hideOnPortal = function(transaction) {\n $scope.transaction.hideOnPortal = !transaction.hideOnPortal;\n };\n\n $scope.trxOriginalCompleteDateTooltip = function(date) {\n if(!date) {\n return '';\n }\n\n return $translate.instant('common.original-date') + ': ' + $filter('date')(date, 'mediumDate');\n };\n\n $scope.trxBalanceDueZeroDateTooltip = function(date) {\n if(!date) {\n return '';\n }\n\n return $translate.instant('common.balance-due-zero-date') + ': ' + $filter('date')(date, 'mediumDate');\n };\n\n var today = moment().startOf('day').format();\n $scope.onCompletedDateChanged = function(date) {\n if(!date) {\n $scope.transaction.completedDate = previousCompletedDate;\n return;\n }\n\n if(previousCompletedDate && moment(previousCompletedDate).isSame(date, 'day')) {\n $scope.transaction.completedDate = previousCompletedDate;\n return;\n }\n\n if(moment(date).isAfter(today, 'day')) {\n modalService.showMessage('transaction.message.please-select-a-date-in-the-past');\n $scope.transaction.completedDate = previousCompletedDate ? previousCompletedDate : $scope.today;\n return;\n }\n\n $scope.updatingDate = true;\n modalService.showConfirmationWithPromptedAction('transaction.message.would-like-to-update-completed-date?',\n $translate.instant('common.modal.update').toUpperCase(),\n 'transaction.update-completed-date',\n 'warning.text.transaction.update-completed-date',\n function() {\n if(!$scope.transaction.originalCompletedDate && previousCompletedDate) {\n $scope.transaction.originalCompletedDate = previousCompletedDate;\n }\n\n $scope.transaction.completedDate = date;\n $scope.saveTransaction(function() {\n alerts.pushSuccess('alerts.transaction.successfully-updated-completed-date');\n previousCompletedDate = $scope.transaction.completedDate;\n $scope.editCompletedDate = false;\n $scope.updatingDate = false;\n });\n }, function() {\n $scope.transaction.completedDate = previousCompletedDate;\n $scope.editCompletedDate = false;\n $scope.updatingDate = false;\n });\n };\n\n $scope.onZeroOutDateChanged = function(date) {\n if(!date) {\n $scope.transaction.balanceDueZeroDate = previousZeroOutDate;\n return;\n }\n\n if(previousZeroOutDate && moment(previousZeroOutDate).isSame(date, 'day')) {\n $scope.transaction.balanceDueZeroDate = previousZeroOutDate;\n return;\n }\n\n if(moment(date).isAfter(today, 'day')) {\n modalService.showMessage('transaction.message.please-select-a-date-in-the-past');\n $scope.transaction.balanceDueZeroDate = previousZeroOutDate ? previousZeroOutDate : $scope.today;\n return;\n }\n\n if($scope.transaction.completedDate && moment(date).isAfter(moment($scope.transaction.completedDate).startOf('day').format(), 'day')) {\n modalService.showMessage('transaction.message.balance-due-zero-date-after-comp');\n $scope.transaction.balanceDueZeroDate = previousZeroOutDate;\n return;\n }\n\n if($scope.transaction.orderDate && moment(date).isBefore(moment($scope.transaction.orderDate).startOf('day').format(), 'day')) {\n modalService.showMessage('transaction.message.balance-due-zero-date-before-trx-order');\n $scope.transaction.balanceDueZeroDate = previousZeroOutDate;\n return;\n }\n\n $scope.updatingZeroDate = true;\n modalService.showStaticAndNoKeyboardConfirmation('transaction.message.would-like-to-update-zero-out-date?',\n function() {\n $scope.transaction.balanceDueZeroDate = date;\n $scope.saveTransaction(function() {\n alerts.pushSuccess('alerts.transaction.successfully-updated-zero-out-date');\n previousZeroOutDate = $scope.transaction.balanceDueZeroDate;\n $scope.editZeroOutDate = false;\n $scope.updatingZeroDate = false;\n });\n }, function() {\n $scope.transaction.balanceDueZeroDate = previousZeroOutDate;\n $scope.editZeroOutDate = false;\n $scope.updatingZeroDate = false;\n });\n };\n\n var modalPromise;\n $scope.openDatepicker = function($event) {\n $event.stopPropagation();\n\n // close popover if it's opened\n if(modalPromise) {\n modalPromise.close();\n modalPromise = null;\n }\n\n $scope.datepickerOpened = !$scope.datepickerOpened;\n };\n\n $scope.toggleCompletedDateEdit = function() {\n $scope.editCompletedDate = !$scope.editCompletedDate;\n };\n\n $scope.toggleZeroOutDateEdit = function() {\n $scope.editZeroOutDate = !$scope.editZeroOutDate;\n };\n\n $scope.openPurchaseOrdersModal = function() {\n if(!$scope.transaction.contactId) {\n modalService.showMessage('transaction.message.assign-contact-first');\n } else {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/purchase-orders.tpl.html',\n controller: 'PurchaseOrdersCtrl',\n resolve: {\n transaction: function() {\n return $scope.transaction;\n },\n readonly: function() {\n return $scope.readonly;\n },\n saveTransaction: function() {\n return $scope.saveTransaction;\n },\n posSettings: function() {\n return posSettings;\n }\n },\n windowClass: '__xlarge',\n backdrop: 'static'\n });\n }\n };\n\n $scope.openPickupSlipsModal = function() {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/pickup-slips.tpl.html',\n controller: 'PickupSlipsCtrl',\n resolve: {\n transaction: function() {\n return $scope.transaction;\n },\n settings: function(CompanySetting) {\n return CompanySetting.getPurchasingSettings();\n },\n dymoLabelConfigs: function(Item) {\n return Item.getDymoLabelConfigs();\n }\n },\n windowClass: '__large'\n });\n };\n\n $scope.openCommissionsModal = function() {\n if(!$scope.transaction.employeeId) {\n return modalService.showMessage('transaction.message.assign-associate-before-editing-commissions');\n } else {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/commissions.tpl.html',\n controller: 'CommissionsCtrl',\n resolve: {\n transaction: function() {\n return $scope.transaction;\n },\n employees: function() {\n return $scope.employees;\n },\n employeeCommissions: function(EmployeeCommission) {\n return EmployeeCommission.query({ filterObject: {transactionId: $scope.transaction.id} });\n },\n settings: function(CompanySetting) {\n return CompanySetting.getCommissionSettings();\n }\n },\n windowClass: '__large'\n }).result.then(function(commissionDisabled) {\n\n if(commissionDisabled != $scope.transaction.commissionDisabled) {\n $scope.transaction.commissionDisabled = commissionDisabled;\n\n $scope.saveTransaction(function() {\n var messageKey = $scope.transaction.commissionDisabled ? \"transaction.commission-disabled\" : \"transaction.commission-enabled\";\n alerts.pushSuccess(messageKey);\n });\n }\n });\n }\n };\n\n $scope.openPaymentsModal = function() {\n if(!$scope.transaction.employeeId) {\n return modalService.showMessage('transaction.message.assign-associate-before-adding-payments');\n } else if(!$scope.transaction.contactName) {\n return modalService.showMessage('transaction.message.assign-contact-before-adding-payments');\n }\n\n TransactionPaymentsModalService.open($scope.transaction, signatures, $scope.readonly, null, null, $scope.paymentPlan);\n };\n\n $scope.openAddPaymentPlanModal = function() {\n PaymentPlanModal.openAddPaymentPlan({\n type: 'transaction',\n entity: $scope.transaction\n }, function(response) {\n if(response && response.id) {\n $scope.paymentPlan.plan = response;\n $scope.paymentPlan.access = true;\n }\n }, 'transaction');\n };\n\n $scope.openViewPaymentPlanModal = function(plan) {\n PaymentPlanModal.openViewPaymentPlan(plan.id, plan.contactId, function(response) {\n if(response && response.type === 'delete') {\n if(response.plan && response.plan.transactionId === $scope.transaction.id) {\n $scope.paymentPlan.plan = null;\n\n if(!$scope.paymentPlan.completedPlans || $scope.paymentPlan.completedPlans.length === 0) {\n $scope.paymentPlan.hasPlan = false;\n }\n }\n }\n });\n };\n\n function updateLineItemsAfterUpdatingShippingInfo(savedTransaction) {\n // Update version of all line items after updating shipping information.\n _($scope.transaction.lineItems).each(function(transactionItem, index) {\n transactionItem.version = savedTransaction.lineItems[index].version;\n transactionItem.modifiedDate = savedTransaction.lineItems[index].modifiedDate;\n transactionItem.modifiedByUser = savedTransaction.lineItems[index].modifiedByUser;\n transactionItem.addressId = savedTransaction.lineItems[index].addressId;\n transactionItem.taxAmount = savedTransaction.lineItems[index].taxAmount;\n transactionItem.shippingTax = savedTransaction.lineItems[index].shippingTax;\n transactionItem.shippingCost = savedTransaction.lineItems[index].shippingCost;\n transactionItem.shippingTaxPercent = savedTransaction.lineItems[index].shippingTaxPercent;\n transactionItem.shippingTaxCodeId = savedTransaction.lineItems[index].shippingTaxCodeId;\n transactionItem.shippingTaxable = savedTransaction.lineItems[index].shippingTaxable;\n transactionItem.taxCodeId = savedTransaction.lineItems[index].taxCodeId;\n transactionItem.taxCodeDescription = savedTransaction.lineItems[index].taxCodeDescription;\n transactionItem.taxCodeIsComputed = savedTransaction.lineItems[index].taxCodeIsComputed;\n\n _(transactionItem.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n\n $rootScope.trxHasShippingAddress = trxHasShippingAddress($scope.transaction.lineItems);\n }\n\n $scope.openShipToCustomerModal = function() {\n if($rootScope.shippingModalOpen || $scope.savingInProgress) {\n return;\n }\n\n if(!$scope.transaction.contactId) {\n return modalService.showMessage('transaction.ship-to-customer.please-assign-contact');\n }\n\n $rootScope.shippingModalOpen = true;\n\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/ship-to-customer-modal.tpl.html',\n controller: 'ShipToCustomerModalCtrl',\n resolve: {\n taxCodes: function(TaxCode) {\n return TaxCode.query({ filterObject: {status: Enums.statuses.active.value} });\n },\n transaction: function() {\n return Transaction.getById($scope.transaction.id);\n },\n addresses: function() {\n return Address.listAllForContact($scope.transaction);\n },\n eventMembers: function() {\n if($scope.transaction && $scope.transaction.eventId) {\n return Transaction.listEventMemberContactInformation($scope.transaction.eventId, $scope.transaction.eventContactId);\n } else {\n return null;\n }\n },\n settings: function() {\n return TaxJarSettings.get();\n }\n },\n windowClass: '__large',\n backdrop: 'static',\n keyboard: false\n }).result.then(function(response) {\n $rootScope.shippingModalOpen = false;\n\n if(response && response.transaction) {\n\n var savedTransaction = response.transaction;\n\n if(savedTransaction.failedToRecalculateTax) {\n alerts.pushError('settings.tax-jar.message.failed-to-calculate-tax');\n }\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'fee',\n 'notes'\n ));\n\n updateLineItemsAfterUpdatingShippingInfo(savedTransaction);\n }\n });\n };\n\n $scope.getAvailableAddOnsAndOpenModal = function(transactionItem, item, options, callback) {\n var addOnRequest = {\n leadTimes: $scope.isSpecialOrder,\n itemAddOns: !$scope.isReturn && options.fetchItemAddOns,\n transaction: $scope.transaction,\n transactionItem: transactionItem,\n item: item\n };\n\n TransactionItem.getAvailableAddOns(addOnRequest, function(addOnsResult) {\n //if we asked to load lead times and we have some, show the modal\n if(addOnsResult.leadTimes === null) {\n addOnsResult.leadTimes = [];\n }\n\n if(addOnsResult.itemAddOns === null) {\n addOnsResult.itemAddOns = [];\n }\n\n var lineItemAddOns = _(transactionItem.lineItemAddOns).filter(function(addOn) {\n return addOn.addOnId !== 0 && addOn.addOnId !== null;\n });\n\n if(options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnRequest.itemAddOns && addOnsResult.itemAddOns.length > 0 && lineItemAddOns.length !== addOnsResult.itemAddOns.length) {\n openAddOnModal(transactionItem, addOnsResult, { userSelected: transactionItem.lineItemAddOns && transactionItem.lineItemAddOns.length });\n }\n\n if(!options.showModalOnlyIfAddOnsAvailable || (options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnsResult.leadTimes.length > 0)) {\n openLeadTimesModal(transactionItem, addOnsResult, callback);\n } else {\n if(callback) {\n callback();\n }\n }\n });\n };\n\n function getAvailableAddItemsOnsAndOpenModal(transactionItem, item, options) {\n if(!$scope.isReturn && options.fetchItemAddOns) {\n var addOnRequest = {\n leadTimes: false,\n itemAddOns: true,\n transaction: $scope.transaction,\n transactionItem: transactionItem,\n item: item\n };\n\n TransactionItem.getAvailableAddOns(addOnRequest, function(addOnsResult) {\n //if we asked to load lead times and we have some, show the modal\n if(addOnsResult.itemAddOns === null) {\n addOnsResult.itemAddOns = [];\n }\n\n if(!options.showModalOnlyIfAddOnsAvailable || (options.showModalOnlyIfAddOnsAvailable && addOnRequest && addOnsResult.itemAddOns.length > 0)) {\n openAddOnModal(transactionItem, addOnsResult, options);\n }\n });\n }\n }\n\n function openAddOnModal(transactionItem, addOnsResult, options) {\n var allowedToClose = !$scope.itemAddOnsPerm.addOnRequired;\n\n if(options && options.userSelected) {\n allowedToClose = true;\n }\n\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/add-item-add-ons.tpl.html',\n controller: 'AddItemAddOnsCtrl',\n windowClass: '__large',\n keyboard: allowedToClose,\n backdrop: 'static',\n resolve: {\n entityType: function() {\n return 'transaction';\n },\n entityItem: function() {\n return transactionItem;\n },\n addOnsResult: function() {\n return addOnsResult;\n },\n item: function() {\n return addOnsResult.item;\n },\n allowedToClose: function() {\n return allowedToClose;\n }\n }\n })\n .result\n .then(function(addOnsModalResult) {\n var trxItemModifications = addOnsModalResult && addOnsModalResult.trxItemModifications;\n if(trxItemModifications && trxItemModifications.lineItemAddOns && trxItemModifications.lineItemAddOns.length) {\n $scope.saveTransactionItem(_.extend(transactionItem, trxItemModifications));\n }\n });\n }\n\n function openLeadTimesModal(transactionItem, addOnsResult, callback) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/add-ons.tpl.html',\n controller: 'AddOnsCtrl',\n windowClass: '__large',\n resolve: {\n transactionItem: function() {\n return transactionItem;\n },\n addOnsResult: function() {\n return addOnsResult;\n }\n }\n })\n .result\n .then(function(addOnsModalResult) {\n if(!addOnsModalResult) {\n if(callback) {\n callback();\n }\n return;\n }\n\n var trxItemModifications = addOnsModalResult.trxItemModifications;\n var addOnItem = addOnsModalResult.addOnItem;\n\n if(trxItemModifications) {\n var clonedTransactionItem = angular.copy(transactionItem);\n clonedTransactionItem - _.extend(clonedTransactionItem, trxItemModifications);\n\n $scope.saveTransactionItem(clonedTransactionItem, function() {\n transactionItem.estimatedDeliveryDate = clonedTransactionItem.estimatedDeliveryDate;\n if(addOnItem) {\n onAddLineItem(addOnItem, null, true, callback);\n }\n });\n } else if(addOnItem) {\n onAddLineItem(addOnItem, null, true, callback);\n }\n });\n }\n\n $scope.signTerminalContract = function(customContract) {\n if(!$window.localStorage.fullsteamTerminalId) {\n openSetTerminalModal(customContract, null, false);\n } else {\n openTerminalContractModal(customContract, $scope.transaction.id);\n }\n };\n\n function openTerminalContractModal(contract, transactionId, tempTerminal) {\n if((contract && contract.signed) || (tempTerminal && tempTerminal.terminalId) || isDeviceTypeValid($window.localStorage.fullsteamTerminalType, contract)) {\n $modal\n .open({\n templateUrl: 'js/modules/transaction/details/modals/sign-terminal-contract.tpl.html',\n controller: 'SignTerminalContractCtrl',\n windowClass: '__protected __large',\n backdrop: 'static',\n resolve: {\n agreement: function() {\n return contract;\n },\n signatures: function(Signature) {\n return Signature.query({ filterObject: { transactionId: transaction.id } });\n },\n tempTerminal: function() {\n return tempTerminal;\n },\n paymentPlan: function() {\n return null;\n }\n }\n })\n .result\n .then(function(savedSignature) {\n var agreement = $scope.defaultSalesAgreement;\n if(contract) {\n agreement = contract;\n }\n\n if(savedSignature) {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n\n //mark the sales agreement as signed\n if(agreement) {\n agreement.signed = true;\n }\n } else {\n //if the signature was deleted, reset the state of the signature button\n //mark the sales agreement as not signed\n if(agreement) {\n agreement.signed = false;\n }\n\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = false;\n\n //if we don't have any signed sales agreements, turn the button red now\n setting = _(posSettings).findWhere({ type: 'SALES_AGREEMENT_REQ' });\n var isSignatureRequiredForThisType = setting && setting.value && setting.value.split(',').indexOf(transaction.typeId.toString()) !== -1;\n\n if(!$scope.defaultSalesAgreement.signed) {\n var otherSignedSalesAgreement = _($scope.agreements).findWhere({ signed: true });\n\n if(!otherSignedSalesAgreement && $scope.contracts) {\n otherSignedSalesAgreement = _($scope.contracts).findWhere({ signed: true });\n }\n\n if(!otherSignedSalesAgreement) {\n $scope.markSignSalesAgreementRed = isSignatureRequiredForThisType;\n $scope.markSignSalesAgreementGreen = false;\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n }\n });\n }\n }\n\n function isDeviceTypeValid(device, agreement) {\n if(!device) {\n return true;\n }\n\n if(FullSteamHelper.isDeviceTypeNotContractEligible(device)) {\n FullSteam.getTerminals(function (result) {\n var terminals = _((result && result.terminals)).filter(FullSteamHelper.isContractTerminal);\n\n if(terminals && terminals.length) {\n openSetTerminalModal(agreement, terminals, true);\n } else {\n modalService.showMessage('settings.custom-sales-agreement.devices-eligible');\n }\n\n }, function () {\n modalService.showMessage('settings.custom-sales-agreement.devices-eligible');\n });\n\n return false;\n }\n\n return true;\n }\n\n function openSetTerminalModal(agreement, terminals, selectingContractTerminal) {\n return $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/set-terminal-modal.tpl.html',\n controller: 'SetTerminalModalCtrl',\n resolve: {\n terminalResult: function () {\n if(terminals && terminals.length) {\n return { terminals: terminals };\n } else {\n return FullSteam.getTerminals();\n }\n },\n selectingContractTerminal: function () {\n return selectingContractTerminal;\n }\n }\n }).result.then(function(response) {\n if(response) {\n openTerminalContractModal(agreement, $scope.transaction.id, response);\n } else {\n return modalService.showMessage('alerts.merchant-account.please-set-terminal-capture','settings.merchant-account.required-terminal');\n }\n });\n }\n\n $scope.signSalesAgreementModal = function(salesAgreement) {\n $modal\n .open({\n templateUrl: 'js/modules/transaction/details/modals/sign-sales-agreement.tpl.html',\n controller: 'SignSalesAgreementCtrl',\n windowClass: '__protected __large',\n backdrop: 'static',\n keyboard: false,\n resolve: {\n signatures: function(Signature) {\n return Signature.query({ filterObject: {transactionId: transaction.id} });\n },\n posSettings: function() {\n return posSettings;\n },\n awsPolicyAndSignature: function(Signature) {\n return Signature.getS3Policy();\n },\n customSalesAgreements: function() {\n return $scope.agreements;\n },\n agreement: function() {\n return salesAgreement;\n },\n transactionType: getTransactionType\n }\n })\n .result\n .then(function(savedSignature) {\n var agreement = $scope.defaultSalesAgreement;\n if(salesAgreement) {\n agreement = salesAgreement;\n }\n\n if(savedSignature) {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n\n //mark the sales agreement as signed\n if(agreement) {\n agreement.signed = true;\n }\n } else {\n //if the signature was deleted, reset the state of the signature button\n //mark the sales agreement as not signed\n if(agreement) {\n agreement.signed = false;\n }\n\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = false;\n\n //if we don't have any signed sales agreements, turn the button red now\n setting = _(posSettings).findWhere({ type: 'SALES_AGREEMENT_REQ' });\n var isSignatureRequiredForThisType = setting && setting.value && setting.value.split(',').indexOf(transaction.typeId.toString()) !== -1;\n\n if(!$scope.defaultSalesAgreement.signed) {\n var otherSignedSalesAgreement = _($scope.agreements).findWhere({ signed: true });\n\n if(!otherSignedSalesAgreement && $scope.contracts) {\n otherSignedSalesAgreement = _($scope.contracts).findWhere({ signed: true });\n }\n\n if(!otherSignedSalesAgreement) {\n $scope.markSignSalesAgreementRed = isSignatureRequiredForThisType;\n $scope.markSignSalesAgreementGreen = false;\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n } else {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n }\n }\n });\n };\n\n $scope.requestSignature = function() {\n if(!$scope.transaction.contactId) {\n modalService.showMessage('transaction.message.assign-contact-first');\n } else {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/request-signature.tpl.html',\n controller: 'RequestSignatureCtrl',\n windowClass: '__small',\n resolve: {\n companyProfile: function(Company) {\n return Company.get();\n },\n posSettings: function(CompanySetting) {\n return CompanySetting.getPOSSettings();\n },\n customSalesAgreements: function(CompanySetting) {\n return CompanySetting.getCustomSalesAgreements();\n },\n smsTemplates: function(SMSTemplate) {\n return SMSTemplate.query({ filterObject: {status: Enums.statuses.active.value} });\n },\n emailTemplates: function(EmailTemplate) {\n return EmailTemplate.query({ filterObject: {status: Enums.statuses.active.value} });\n },\n twilioSettings: function(TwilioSettings) {\n return TwilioSettings.get();\n },\n contact: function(Contact) {\n return Contact.getById($scope.transaction.contactId);\n },\n transaction: function() {\n return $scope.transaction;\n },\n transactionType: getTransactionType\n }\n });\n }\n };\n\n var getSignaturesForItems = function() {\n return _(signatures)\n .chain()\n .filter(function(signature) {\n return Boolean(signature.transactionItemId);\n })\n .pluck('transactionItemId')\n .uniq()\n .value();\n };\n\n var getSignatureForItemsObj = function() {\n return _(signatures)\n .chain()\n .filter(function(signature) {\n return Boolean(signature.transactionItemId);\n })\n .uniq()\n .value();\n };\n\n $scope.allItemsAreSigned = function() {\n if(!$scope.transaction.lineItems.length) {\n return;\n }\n\n var result = true;\n var signaturesForItems = getSignaturesForItems();\n\n _($scope.transaction.lineItems).each(function(item) {\n if(result && signaturesForItems.indexOf(item.id) !== -1) {\n return;\n } else {\n result = false;\n }\n });\n\n return result;\n };\n\n $scope.signPickupSlipModal = function() {\n $modal\n .open({\n templateUrl: 'js/modules/transaction/details/modals/sign-pickup-slip.tpl.html',\n controller: 'SignPickupSlipCtrl',\n windowClass: '__protected',\n backdrop: 'static',\n keyboard: false,\n resolve: {\n hasAllFullSteamSettings: function() {\n return $scope.hasAllFullSteamSettings;\n },\n purchasingSettings: function(CompanySetting) {\n return CompanySetting.getPurchasingSettings();\n },\n settings: function(CompanySetting) {\n return CompanySetting.getPurchasingSettings();\n },\n awsPolicyAndSignature: function(Signature) {\n return Signature.getS3Policy();\n },\n dymoLabelConfigs: function(Item) {\n return Item.getDymoLabelConfigs();\n },\n transaction: function() {\n return $scope.transaction;\n },\n signaturesForItems: getSignaturesForItems,\n signatures: function() {\n return _(signatures).filter(function(signature) {\n return Boolean(signature.transactionItemId);\n });\n }\n }\n })\n .result\n .then(function(response) {\n if(response && response.removedSignatures && response.removedSignatures.length) {\n _(response.removedSignatures).each(function(sig) {\n var signature = _(signatures).findWhere({ transactionItemId: sig.transactionItemId });\n\n if(signature && signature.id) {\n signatures = _(signatures).without(signature);\n }\n });\n }\n\n if(response && response.savedSignatures && response.savedSignatures.length) {\n signatures.push.apply(signatures, response.savedSignatures);\n }\n });\n };\n\n $scope.completeOrder = function() {\n if(!$scope.transaction.employeeId) {\n modalService.showMessage('transaction.message.assign-associate-before-completing-order');\n return;\n }\n\n if(($scope.isSale || $scope.isSpecialOrder || $scope.isLayaway) && $scope.transaction.balanceDue > 0) {\n modalService.showMessage('transaction.message.enter-payments-before-completing', 'transaction.amount-due');\n return;\n }\n\n if(!$scope.transaction.smartFlowsProcessedDateTime && $scope.transaction.smartFlowsTriggeredDateTime && security.hasPlanPermission('smartFlows')) {\n modalService.showMessage('transaction.message.process-smart-flow-before-completing', 'transaction.smart-flows');\n return;\n }\n\n if(($scope.isSale || $scope.isSpecialOrder || $scope.isReturn) && !$scope.transaction.contactName) {\n modalService.showMessage('transaction.message.assign-existing-contact', 'Assigned Contact');\n return;\n }\n\n if($scope.locale.isNF525 && isCustomerAddressInvalid($scope.transaction)) {\n openCustomerAddressModal($scope.transaction);\n return;\n }\n\n var confirmMessage = 'transaction.message.would-like-to-complete-order?';\n if($scope.isSpecialOrder && isOneLineItemNotLinkedToPO($scope.transaction)) {\n confirmMessage = 'transaction.message.would-like-to-complete-order-without-linking-to-po?';\n }\n\n modalService.showConfirmation(confirmMessage, function() {\n Transaction.complete($scope.transaction.id, User, function(savedTransaction) {\n alerts.pushSuccess('alerts.transaction.successfully-completed-order');\n angular.extend($scope.transaction, _(savedTransaction).omit('lineItems'));\n _($scope.transaction.lineItems).each(function(item, index) {\n item.quantitySold = savedTransaction.lineItems[index].quantitySold;\n item.itemStatus = savedTransaction.lineItems[index].itemStatus;\n item.status = savedTransaction.lineItems[index].status;\n item.version = savedTransaction.lineItems[index].version;\n _(item.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n $scope.readonly = true;\n\n //trigger the smart flows\n if(!$scope.transaction.contactId) {\n //if no contact is assigned, see if they have toggled on the Untracked Contact feature. If so, allow the flows to process with an empty contact\n if($scope.transaction.contactName) {\n eventsBus.publish($scope.getEventBusKey('completed'), [$scope.transaction, {}, {eventDate: null}]);\n }\n } else {\n Contact.getById($scope.transaction.contactId, function(contact) {\n if($scope.getEventBusKey('completed')) {\n eventsBus.publish($scope.getEventBusKey('completed'), [$scope.transaction, contact, {eventDate: contact.eventDate}]);\n }\n });\n }\n\n });\n });\n };\n\n $scope.uncompleteOrder = function() {\n if(!_.isUndefined($scope.transaction.lineItems) && $scope.transaction.lineItems.length && $scope.isSpecialOrder) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/set-item-status-before-uncompleted-modal.tpl.html',\n controller: 'SetItemStatusBeforeUncompletedModalCtrl',\n resolve: {\n lineItems: function() {\n return $scope.transaction.lineItems;\n }\n },\n backdrop: 'static'\n }).result.then(function(trxItemIds) {\n uncompleteOrder(trxItemIds);\n });\n } else {\n uncompleteOrder(null);\n }\n };\n\n $scope.createReturn = function() {\n modalService.showConfirmation('transaction.message.would-like-to-return-items-from-this-trx?', function() {\n openCreateReturnModal($scope.transaction, '?addLineItems=true');\n }, function(response) {\n if(response === 'cancel') {\n openCreateReturnModal($scope.transaction);\n }\n });\n };\n\n // $scope.registerTicket = function() {\n // NF525.registerTicket($scope.transaction.id, function(savedTransaction) {\n // angular.extend($scope.transaction, _(savedTransaction).omit('lineItems'));\n // _($scope.transaction.lineItems).each(function(item, index) {\n // item.version = savedTransaction.lineItems[index].version;\n // _(item.lineItemAddOns).each(function(addOn, i) {\n // if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n // addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n // }\n // });\n // });\n // });\n // };\n\n function openCreateReturnModal(trx, queryString) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/create-return-modal.tpl.html',\n controller: 'CreateReturnModalCtrl',\n resolve: {\n transaction: function() {\n return Transaction.createTrxPreview(trx, Enums.trxTypes.return_trx.value, User.id, queryString);\n },\n taxCodes: function() {\n return $scope.taxCodes;\n }\n },\n backdrop: 'static',\n windowClass: '__xlarge'\n }).result.then(function(response) {\n\n createTransactionFromPreview(response, true);\n });\n }\n\n function createTransactionFromPreview(previewTransaction, redirect) {\n Transaction.createTrxFromPreview(previewTransaction, User, function(trxCreatedFromPreview) {\n if(redirect) {\n var stateToRedirect;\n\n var pushMessage = '';\n switch (trxCreatedFromPreview.typeId) {\n case Enums.trxTypes.sale.value:\n stateToRedirect = 'transaction.sale';\n pushMessage = 'alerts.transaction.successfully-created-sale';\n break;\n case Enums.trxTypes.layaway.value:\n stateToRedirect = 'transaction.layaway';\n pushMessage = 'alerts.transaction.successfully-created-layaway';\n break;\n case Enums.trxTypes.special_order.value:\n stateToRedirect = 'transaction.specialOrder';\n pushMessage = 'alerts.transaction.successfully-created-so';\n break;\n case Enums.trxTypes.return_trx.value:\n stateToRedirect = 'transaction.return';\n pushMessage = 'alerts.transaction.successfully-created-return';\n break;\n }\n\n ignoreRedirectWarning = true;\n\n $state.go(stateToRedirect, {id: trxCreatedFromPreview.id, referrer: 'create-return'}, {location: 'replace' });\n }\n });\n }\n\n $scope.duplicateOrder = function() {\n modalService.showConfirmation('transaction.message.would-like-to-duplicate?', function() {\n Transaction.duplicate($scope.transaction, User, function(transaction) {\n var stateToRedirect;\n\n switch (transaction.typeId) {\n case Enums.trxTypes.sale.value:\n stateToRedirect = 'transaction.sale';\n break;\n case Enums.trxTypes.layaway.value:\n stateToRedirect = 'transaction.layaway';\n break;\n case Enums.trxTypes.special_order.value:\n stateToRedirect = 'transaction.specialOrder';\n break;\n case Enums.trxTypes.return_trx.value:\n stateToRedirect = 'transaction.return';\n break;\n }\n\n ignoreRedirectWarning = true;\n\n $state.go(stateToRedirect, transaction);\n alerts.pushSuccess('alerts.transaction.successfully-duplicated-order');\n });\n });\n };\n\n function openCustomerAddressModal(transaction) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/customer-address-modal.tpl.html',\n controller: 'CustomerAddressCtrl',\n resolve: {\n contact: function(Contact) {\n if(transaction.contactId) {\n return Contact.getById(transaction.contactId);\n } else {\n return null;\n }\n },\n transaction: function() {\n return transaction;\n }\n },\n windowClass: '__medium'\n }).result.then(function(updatedTransaction) {\n $scope.transaction = updatedTransaction;\n\n if(transaction && transaction.contactId) {\n // Update contact's address\n Contact.updateAddressFieldsFromTransaction(transaction, function(contact) {\n if(contact && contact.id) {\n $scope.transaction.contact = contact;\n }\n });\n }\n });\n }\n\n $scope.removeLineItem = function(transactionItem) {\n if($scope.readonly) {\n return;\n }\n\n modalService.showConfirmation('purchase-order.would-like-to-delete-item?', function() {\n Transaction.deleteLineItem(transactionItem, function(transaction) {\n if(!$scope.locale.isNF525) {\n $scope.transaction.lineItems = _($scope.transaction.lineItems).without(transactionItem);\n }\n\n $scope.transaction.modifiedDate = Date.now();\n $scope.transaction.modifiedByUser = User.username;\n\n angular.extend($scope.transaction, _(transaction).omit('lineItems'));\n\n _($scope.transaction.lineItems).each(function(item, index) {\n item.version = transaction.lineItems[index].version;\n item.adjustedPrice = transaction.lineItems[index].adjustedPrice;\n\n if($scope.locale.isNF525 && item && transactionItem && item.id === transactionItem.id) {\n item.quantity = 0;\n item.quantitySold = 0;\n item.price = 0;\n item.taxPercent = 0;\n item.taxAmount = 0;\n item.includedTaxAmount = 0;\n item.includedTaxPercent = 0;\n item.discountAmount = 0;\n item.discountPercent = 0;\n item.adjustedPrice = 0;\n item.eventItemId = 0;\n item.purchaseOrderItemId = 0;\n item.modifiedDate = transaction.lineItems[index].modifiedDate;\n }\n\n _(item.lineItemAddOns).each(function(addOn, i) {\n if(transaction.lineItems[index].lineItemAddOns && transaction.lineItems[index].lineItemAddOns.length && transaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = transaction.lineItems[index].lineItemAddOns[i].version;\n\n if($scope.locale.isNF525 && addOn && transactionItem && addOn.posTransactionItemId === transactionItem.id) {\n addOn.price = 0;\n addOn.cost = 0;\n }\n }\n });\n });\n\n alerts.pushSuccess('alerts.transaction.successfully-deleted-item');\n });\n });\n };\n\n $scope.removeAddOnItem = function(addOnItem, transactionItem) {\n if($scope.readonly) {\n return;\n }\n\n modalService.showConfirmation('purchase-order.would-like-to-delete-add-on-item?', function() {\n Transaction.deleteLineItemAddOn(addOnItem, function(transaction) {\n removeItemAddOn(addOnItem);\n $scope.transaction.modifiedDate = Date.now();\n $scope.transaction.modifiedByUser = User.username;\n\n var savedTransactionItem = _(transaction.lineItems).findWhere({ id: transactionItem.id });\n // Update special field of current line item\n transactionItem.adjustedPrice = savedTransactionItem.adjustedPrice;\n transactionItem.discountPercent = savedTransactionItem.discountPercent;\n transactionItem.shippingTax = savedTransactionItem.shippingTax;\n transactionItem.taxAmount = savedTransactionItem.taxAmount;\n transactionItem.price = savedTransactionItem.price;\n\n angular.extend($scope.transaction, _(transaction).omit('lineItems'));\n _($scope.transaction.lineItems).each(function(item, index) {\n item.version = transaction.lineItems[index].version;\n item.modifiedDate = transaction.lineItems[index].modifiedDate;\n item.modifiedByUser = transaction.lineItems[index].modifiedByUser;\n\n _(item.lineItemAddOns).each(function(addOn, i) {\n if(transaction.lineItems[index].lineItemAddOns && transaction.lineItems[index].lineItemAddOns.length && transaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = transaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n alerts.pushSuccess('alerts.add-ons.successfully-deleted-item-add-on');\n });\n });\n };\n\n function removeItemAddOn(addOnItem) {\n for (var i = 0; i < $scope.transaction.lineItems.length; i++) {\n for (var j = 0; j < $scope.transaction.lineItems[i].lineItemAddOns.length; j++) {\n var addOn = $scope.transaction.lineItems[i].lineItemAddOns[j];\n\n if(addOn && addOn.id === addOnItem.id) {\n $scope.transaction.lineItems[i].lineItemAddOns = _($scope.transaction.lineItems[i].lineItemAddOns).without(addOnItem);\n break;\n }\n }\n }\n }\n\n $scope.showSendEmailModal = function() {\n Contact.getById($scope.transaction.contactId, function(contact) {\n EmailService.showSendModal(contact, null, null, $scope.transaction, null, null, buildCustomEmailOptions());\n });\n };\n\n function buildCustomEmailOptions() {\n var agreements = [{\n ticked: true,\n icon: '',\n description: $translate.instant('modal-service.select-a-sales-agreement')\n }];\n var terminalIcon = ' ';\n var signedIcon = $translate.instant('common.modal.signed-pen-icon');\n\n var defaultAgreement = _(posSettings).findWhere({ type: transactionTypeSettingMap[getTransactionType()] });\n\n if(defaultAgreement) {\n agreements.push(_.extend(defaultAgreement, {\n icon: defaultAgreement.signed ? signedIcon : '',\n ticked: false\n }));\n }\n\n _($scope.agreements).each(function(agreement) {\n agreements.push(_.extend(agreement, {\n icon: agreement.signed ? signedIcon : '',\n ticked: false\n }));\n });\n\n _($scope.contracts).each(function(contract) {\n agreements.push(_.extend(contract, {\n icon: contract.signed ? terminalIcon + ' ' + signedIcon : terminalIcon,\n ticked: false\n }));\n });\n\n return {\n showOptions: true,\n showAgreements: true,\n agreements: agreements\n };\n }\n\n $scope.showSendTextMessageModal = function() {\n Contact.getById($scope.transaction.contactId, function(contact) {\n Sms.showSendModal(contact, null, $scope.transaction);\n });\n };\n\n $scope.registerToPromLineItem = function(item) {\n PromRegistryModal.registerToProm(item, $scope.transaction, $scope.readonly, updateContactEventDateOnTransaction);\n };\n\n function updateContactEventDateOnTransaction(updatedContact) {\n //Update assign contact area\n if(updatedContact && updatedContact.id && updatedContact.eventDate) {\n $scope.transaction.contact = updatedContact;\n $rootScope.onContactEventDateChangeFromEventLocation(updatedContact.eventDate);\n }\n }\n\n $scope.linkItemToWeddingRegistry = function(item) {\n if($scope.readonly) {\n return;\n }\n\n var eventId = $scope.transaction.eventId;\n var contactId = $scope.transaction.contactId;\n\n if(!eventId) {\n return modalService.showMessage('transaction.message.link-to-wedding-party');\n }\n\n EventMember.query({ filterObject: {eventId: eventId} }).then(function(members) {\n Contact.getById($scope.transaction.eventContactId, function(eventContact) {\n var isNotMemberOfWedding = _(members)\n .chain()\n .pluck('contactId')\n .indexOf(contactId)\n .value() === -1;\n\n if(isNotMemberOfWedding) {\n var message = $translate.instant('transaction.message.would-like-add-to-wedding-party?', {\n contactName: $scope.transaction.contactName,\n firstName: eventContact.firstName,\n lastName: eventContact.lastName\n });\n var contactName = $scope.transaction.contactName;\n\n return $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/add-member-to-wedding.tpl.html',\n controller: function($scope, Enums) {\n $scope.message = message;\n $scope.contactName = contactName;\n var translatedEventMemberTypes = {};\n\n // Translate event members' descriptions\n _(Enums.eventMemberTypes).each(function(value, key) {\n value.description = $translate.instant(value.description);\n translatedEventMemberTypes[key] = value;\n });\n\n $scope.eventMemberTypes = _(translatedEventMemberTypes).values();\n $scope.eventMemberType = translatedEventMemberTypes.bridesmaid;\n }\n })\n .result\n .then(function(eventMemberType) {\n // Add contact to event\n var contact = $scope.transaction.contact;\n var newEventMember = new EventMember({\n bestPhoneNumber: contact.bestPhoneNumber,\n bestPhoneNumberType: contact.bestPhoneNumberType,\n contactId: contact.id,\n emailAddress: contact.emailAddress,\n eventId: eventId,\n firstName: contact.firstName,\n lastName: contact.lastName,\n typeId: eventMemberType.value,\n roleDescription: eventMemberType.description,\n createdByUser: User.username,\n modifiedByUser: User.username\n });\n\n newEventMember.$save(function(savedEventMember) {\n // Register item\n var eventItemResource = new EventItem({\n createdByUser: User.username,\n modifiedByUser: User.username,\n eventMemberId: savedEventMember.id,\n inventoryItemId: item.inventoryItemId,\n itemColor: item.itemColor,\n itemColor2: item.itemColor2,\n itemSize: item.itemSize,\n itemName: item.itemName\n });\n\n eventItemResource.$save(function(savedEventItem) {\n TransactionItem.updateEventItemId(item.id, savedEventItem.id, function(updatedItem) {\n _(item).extend(updatedItem);\n alerts.pushSuccess('alerts.transaction.successfully-linked-to-wedding-registry');\n });\n });\n });\n });\n }\n\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/register-to-wedding.tpl.html',\n controller: 'RegisterToWeddingCtrl',\n resolve: {\n transaction: _.constant($scope.transaction),\n item: _.constant(item),\n eventId: _.constant(eventId),\n contactId: _.constant(contactId),\n eventContactName: _.constant(eventContact.firstName + ' ' + eventContact.lastName),\n contactName: _.constant($scope.transaction.contactName),\n members: _.constant(members)\n }\n });\n });\n });\n };\n\n $scope.unlinkItemFromWeddingRegistry = function(item) {\n if($scope.readonly) {\n return;\n }\n\n TransactionItem.updateEventItemId(item.id, 0, function(updatedItem) {\n item.version = updatedItem.version;\n item.eventItemId = 0;\n alerts.pushSuccess('alerts.transaction.successfully-unlinked-from-wedding-registry');\n });\n };\n\n $scope.addItemAddOn = function(trxItem) {\n if($scope.readonly || !$scope.itemAddOnsPerm.set) {\n return;\n }\n\n getAvailableAddItemsOnsAndOpenModal(trxItem, null, {\n fetchItemAddOns: true,\n showModalOnlyIfAddOnsAvailable: false,\n userSelected: true\n });\n };\n\n $scope.changeItemStatus = function(transactionItem, trxItemStatus) {\n var clonedTransactionItem = angular.copy(transactionItem);\n var newStatus = trxItemStatus ? trxItemStatus.value : null;\n\n if(clonedTransactionItem.itemStatus === newStatus) {\n return;\n }\n\n if($scope.isLayaway && newStatus === Enums.trxItemStatuses.on_order.value) {\n return;\n }\n\n clonedTransactionItem.itemStatus = newStatus;\n\n if($scope.transaction.status === Enums.statuses.pending.value) {\n if(newStatus === Enums.trxItemStatuses.picked_up.value) {\n clonedTransactionItem.quantitySold = clonedTransactionItem.quantity;\n } else if(!newStatus\n || newStatus === Enums.trxItemStatuses.awaiting_pickup.value\n || newStatus === Enums.trxItemStatuses.on_order.value\n || newStatus === Enums.trxItemStatuses.in_alterations.value) {\n clonedTransactionItem.quantitySold = 0;\n }\n }\n\n clonedTransactionItem.modifiedDate = Date.now();\n clonedTransactionItem.modifiedByUser = User.username;\n\n Transaction.updateLineItemStatus(clonedTransactionItem, function(savedTransaction) {\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'shippingAndHandling',\n 'fee',\n 'notes'\n ));\n\n var savedTransactionItem = _(savedTransaction.lineItems).findWhere({ id: transactionItem.id });\n\n if(savedTransactionItem) {\n transactionItem.adjustedPrice = savedTransactionItem.adjustedPrice;\n transactionItem.discountPercent = savedTransactionItem.discountPercent;\n transactionItem.itemStatus = savedTransactionItem.itemStatus;\n transactionItem.quantitySold = savedTransactionItem.quantitySold;\n transactionItem.modifiedDate = savedTransactionItem.modifiedDate;\n transactionItem.modifiedByUser = savedTransactionItem.modifiedByUser;\n }\n\n // Update version of all line items\n _($scope.transaction.lineItems).each(function(transactionItem, index) {\n transactionItem.version = savedTransaction.lineItems[index].version;\n\n _(transactionItem.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n\n alerts.pushSuccess('alerts.transaction.successfully-updated-item-status');\n });\n };\n\n $scope.changeCommissionStatus = function(transactionItem) {\n\n transactionItem.modifiedDate = Date.now();\n transactionItem.modifiedByUser = User.username;\n\n Transaction.updateLineItem(transactionItem, function(savedTransaction) {\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'shippingAndHandling',\n 'fee',\n 'notes'\n ));\n\n // Update version of all line items\n _($scope.transaction.lineItems).each(function(transactionItem, index) {\n transactionItem.version = savedTransaction.lineItems[index].version;\n transactionItem.modifiedDate = savedTransaction.lineItems[index].modifiedDate;\n transactionItem.modifiedByUser = savedTransaction.lineItems[index].modifiedByUser;\n\n _(transactionItem.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n\n alerts.pushSuccess('alerts.transaction.successfully-updated-commission-status');\n\n }, function() {\n //if we fail to save, revert the change\n transactionItem.commissionDisabled = transactionItem.commissionDisabled;\n });\n\n };\n\n $scope.getItemStatusClass = function(trxItem) {\n if(trxItem) {\n var status = _.findWhere(Enums.trxItemStatuses, {value: trxItem.itemStatus });\n if(status) {\n return 'glyphicon ' + status.icon;\n } else {\n return '';\n }\n }\n };\n\n //Items cannot be put into On Order or Picked Up if the Trx Complete\n //Show the current trxItemStatus as Purple\n //Layaways cannot be put into \"On Order\"\n $scope.getItemStatusMenuClass = function(trxItemStatus, trxItem) {\n //if the transaction is set to read only, the only thing that can be done is to move the line item to alterations status\n\n if(trxItem && trxItemStatus) {\n\n if($scope.isLayaway && trxItemStatus.value === Enums.trxItemStatuses.on_order.value) {\n return \"disabled\";\n }\n\n if(trxItem.status === Enums.statuses.complete.value) {\n if(trxItemStatus.value === Enums.trxItemStatuses.on_order.value) {\n return \"disabled\";\n }\n }\n\n if(trxItem.itemStatus == trxItemStatus.value) {\n return \"active italic\";\n }\n\n }\n\n return \"\";\n };\n\n $scope.updateCommissionItemStatus = function(item, status) {\n if(!$scope.commissionPerm.editStatus) {\n return;\n }\n\n var commissionItem = angular.extend(new CommissionItem(), item.commissionItem);\n\n if(commissionItem.status === status) {\n return;\n }\n\n commissionItem.modifiedByUser = User.username;\n commissionItem.modifiedDate = new Date();\n commissionItem.status = status;\n\n commissionItem.$update(function(updatedCommissionItem) {\n alerts.push('alerts.transaction.successfully-updated-commission-item-status');\n item.commissionItem = updatedCommissionItem;\n });\n };\n\n $scope.assignCommissionToEmployee = function(item, employee) {\n if(!$scope.commissionPerm.assignEmployee) {\n return;\n }\n\n if(item && item.commissionItem && item.commissionItem.status === 'P') {\n modalService.showMessage('transaction.message.cannot-change-associate-for-commission', 'common.message.wooops');\n return;\n }\n\n if(employee.id <= 0) {\n $scope.deleteCommissionItem(item, true);\n } else {\n //Assign to Employee\n var commissionItem = angular.extend(new CommissionItem(), item.commissionItem);\n if(commissionItem.id) {\n if(commissionItem.employeeId === employee.id) {\n return;\n }\n\n commissionItem.employeeId = employee.id;\n commissionItem.modifiedByUser = User.username;\n commissionItem.modifiedDate = new Date();\n\n commissionItem.$update(function(updatedCommissionItem) {\n alerts.push('alerts.transaction.successfully-re-assigned-commission-to-associate');\n item.commissionItem = updatedCommissionItem;\n });\n } else {\n commissionItem.employeeId = employee.id;\n commissionItem.transactionId = item.transactionId;\n commissionItem.posTransactionItemId = item.id;\n commissionItem.status = Enums.commissionStatusesObj.unpaid.value;\n commissionItem.createdByUser = User.username;\n commissionItem.createdDate = new Date();\n commissionItem.commissionPercent = 100;\n\n commissionItem.$save(function(updatedCommissionItem) {\n alerts.push('alerts.transaction.successfully-assigned-commission-to-associate');\n item.commissionItem = updatedCommissionItem;\n });\n }\n }\n };\n\n $scope.deleteCommissionItem = function(item, ignoreMessage) {\n var commissionItem = angular.extend(new CommissionItem(), item.commissionItem);\n\n if(!commissionItem || !commissionItem.id) {\n if(!ignoreMessage) {\n modalService.showMessage('transaction.message.error-commission-item-invalid', 'common.message.wooops');\n }\n\n item.commissionItem = null;\n return;\n }\n\n commissionItem.$remove(function() {\n alerts.push('alerts.transaction.successfully-removed-commission-from-line-item');\n item.commissionItem = null;\n });\n };\n\n $scope.print = function(agreement) {\n if($scope.locale.isNF525 && isCustomerAddressInvalid($scope.transaction)) {\n openCustomerAddressModal($scope.transaction);\n return;\n }\n\n Transaction.print($scope.transaction.id, agreement, User.id);\n };\n\n $scope.voidOrder = function() {\n modalService.showConfirmation('transaction.message.voiding-order-reverse-adjustments',\n function() {\n Transaction.void($scope.transaction.id, function(trx) {\n alerts.pushSuccess('alerts.transaction.successfully-voided-order');\n $scope.transaction.status = $scope.voidedStatusValue;\n $scope.readonly = true;\n initCancellationReasons($scope.transaction);\n });\n });\n };\n\n $scope.deleteOrder = function() {\n if($scope.transaction.payments.length || $scope.transaction.preAuthPayments.length) {\n return modalService.showMessage('transaction.message.cannot-be-deleted', 'transaction.message.cannot-delete');\n }\n\n modalService.showConfirmation('transaction.message.would-like-to-delete-order?',\n function() {\n Transaction.delete($scope.transaction).then(function() {\n ignoreRedirectWarning = true;\n alerts.pushSuccess('alerts.transaction.has-been-deleted-successfully');\n $state.go('transactions');\n });\n }\n );\n };\n\n $scope.convertSaleToSpecialOrder = function() {\n $scope.transaction.typeId = 2;\n\n Transaction.getNextTrxNumber($scope.transaction.typeId, function(nextTransactionNumber) {\n $scope.transaction.trxNumber = nextTransactionNumber;\n\n $scope.saveTransaction(function() {\n ignoreRedirectWarning = true;\n $state.go('transaction.specialOrder', {id: $scope.transaction.id, promptLeadTimes: true });\n });\n });\n };\n\n $scope.convertSaleToLayaway = function() {\n Transaction.convertToLayaway($scope.transaction.id, User.id, null, function() {\n ignoreRedirectWarning = true;\n $state.go('transaction.layaway', {id: $scope.transaction.id });\n });\n };\n\n $scope.convertSpecialOrderToLayaway = function() {\n var trxItemIds = [];\n if(!_.isUndefined($scope.transaction.lineItems) && $scope.transaction.lineItems.length) {\n _.each($scope.transaction.lineItems, function(item) {\n if(item.itemStatus !== \"W\") {\n trxItemIds.push(item.inventoryItemId);\n }\n });\n }\n\n Transaction.convertToLayaway($scope.transaction.id, User.id, trxItemIds, function() {\n ignoreRedirectWarning = true;\n $state.go('transaction.layaway', {id: $scope.transaction.id });\n });\n };\n\n $scope.convertLayawayToSpecialOrder = function() {\n\n if(!_.isUndefined($scope.transaction.lineItems) && $scope.transaction.lineItems.length) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/set-item-status-before-converting-modal.tpl.html',\n controller: 'SetItemStatusBeforeConvertingModalCtrl',\n resolve: {\n lineItems: function() {\n return $scope.transaction.lineItems;\n }\n },\n backdrop: 'static'\n }).result.then(function(trxItemIds) {\n convertLayawayToSO(trxItemIds);\n });\n } else {\n convertLayawayToSO(null);\n }\n };\n\n $scope.copyToQuote = function() {\n modalService.showConfirmation({\n key: 'transaction.message.copy-to-quote?',\n params: {type: getTransactionTypeDescription()}\n }, function() {\n Transaction.convertToQuote($scope.transaction, User.id, function(quote) {\n ignoreRedirectWarning = true;\n $state.go('quote.edit', {id: quote.id });\n });\n });\n };\n\n $scope.processSmartFlows = function() {\n var successCallback = function() {\n $scope.transaction.smartFlowsProcessedDateTime = new Date().valueOf();\n $scope.saveTransaction();\n };\n\n var failureCallback = function() {\n $scope.transaction.smartFlowsProcessedDateTime = new Date().valueOf();\n $scope.saveTransaction();\n };\n\n if(!$scope.transaction.contactId) {\n //if no contact is assigned, see if they have toggled on the Untracked Contact feature. If so, allow the flows to process with an empty contact\n if($scope.transaction.contactName) {\n eventsBus.publish($scope.getEventBusKey('created'), [$scope.transaction, {}, {eventDate: null}, successCallback, failureCallback]);\n } else {\n modalService.showMessage('transaction.message.assign-contact-first');\n }\n } else {\n Contact.getById($scope.transaction.contactId, function(contact) {\n eventsBus.publish($scope.getEventBusKey('created'), [$scope.transaction, contact, {eventDate: contact.eventDate}, successCallback, failureCallback]);\n });\n }\n };\n\n /**\n * Helpers\n */\n\n function getLastPaymentDate(transaction) {\n var payments = transaction.payments;\n\n if(payments && payments.length) {\n payments = _(payments).sortBy('date').reverse();\n var lastPaymentDate = payments[0].date;\n if(lastPaymentDate) {\n return lastPaymentDate;\n }\n }\n\n return moment().startOf('day').format();\n }\n\n function trxHasShippingAddress(items) {\n return _(items).filter(function(item) {\n return item.addressId;\n }).length;\n }\n\n function trxHasLineItems(items) {\n if($scope.isNF525) {\n return _(items).filter(function(item) {\n return item.quantity > 0;\n }).length;\n }\n\n return !!(items && items.length > 0);\n }\n\n function convertLayawayToSO(trxItemIds) {\n Transaction.convertToSpecialOrder($scope.transaction.id, User.id, trxItemIds, function() {\n ignoreRedirectWarning = true;\n $state.go('transaction.specialOrder', {id: $scope.transaction.id });\n });\n }\n\n function uncompleteOrder(trxItemIds) {\n Transaction.uncomplete($scope.transaction.id, User, trxItemIds, function(savedTransaction) {\n alerts.pushSuccess('alerts.transaction.successfully-uncompleted-order');\n angular.extend($scope.transaction, _(savedTransaction).omit('lineItems'));\n _($scope.transaction.lineItems).each(function(item, index) {\n item.quantitySold = savedTransaction.lineItems[index].quantitySold;\n item.itemStatus = savedTransaction.lineItems[index].itemStatus;\n item.status = savedTransaction.lineItems[index].status;\n item.version = savedTransaction.lineItems[index].version;\n\n _(item.lineItemAddOns).each(function(addOn, i) {\n if(savedTransaction.lineItems[index].lineItemAddOns && savedTransaction.lineItems[index].lineItemAddOns.length && savedTransaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = savedTransaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n $scope.readonly = false;\n });\n }\n\n function getTransactionType() {\n var transactionType;\n\n if($scope.isSale) {\n transactionType = 'Sale';\n } else if($scope.isSpecialOrder) {\n transactionType = 'Special Order';\n } else if($scope.isLayaway) {\n transactionType = 'Layaway';\n } else {\n transactionType = 'Return';\n }\n\n return transactionType;\n }\n\n function getTransactionTypeDescription() {\n var transactionType;\n\n if($scope.isSale) {\n transactionType = $translate.instant('enums.trx-types.sale');\n } else if($scope.isSpecialOrder) {\n transactionType = $translate.instant('enums.trx-types.Special-order');\n } else if($scope.isLayaway) {\n transactionType = $translate.instant('transaction.layaway');\n } else {\n transactionType = $translate.instant('transaction.return');\n }\n\n return transactionType;\n }\n\n function validateTransactionItems() {\n $timeout(function() {\n $scope.$broadcast('show-errors-check-validity');\n });\n }\n\n function isCustomerAddressInvalid(trx) {\n if(trx.contactId === 0) {\n if(!trx.nf525InvoiceZipCode) {\n return true;\n }\n } else {\n if(!trx.nf525InvoiceAddress || !trx.nf525InvoiceZipCode || !trx.nf525InvoiceCity || !trx.nf525InvoiceCountry) {\n return true;\n }\n }\n\n return false;\n }\n\n function isOneLineItemNotLinkedToPO(trx) {\n var itemNotLinked = false;\n\n if(trx && trx.lineItems.length) {\n _(trx.lineItems).each(function(item) {\n if(!itemNotLinked && item.isInventoryItem && item.itemStatus !== 'A' && item.itemStatus !== 'W' && item.itemStatus !== 'P' && !item.purchaseOrderId) {\n itemNotLinked = true;\n }\n });\n }\n\n return itemNotLinked;\n }\n\n /**\n * Convert Item to TransactionItem\n * @param {resource|object} item\n * @returns {object} transactionItem\n */\n function itemToTransactionItem(item) {\n var taxCode = _.findWhere($scope.taxCodes, {id: item.taxCodeId });\n var lineItemAddOns = ItemHelper.buildLineItemAddOnsTrx(item.itemAddOns, item, null, {default: true });\n var addOnTotalPrice = _.reduce(lineItemAddOns, function(memo, num) {\n return memo + num.price;\n }, 0);\n var addOnTotalCost = _.reduce(lineItemAddOns, function(memo, num) {\n return memo + num.cost;\n }, 0);\n\n var taxPercent = 0;\n var includedTaxPercent = 0;\n if(taxCode) {\n if(taxCode.inclusive) {\n includedTaxPercent = taxCode.percent || 0;\n } else {\n taxPercent = taxCode.percent || 0;\n }\n }\n\n var regularPrice = item.regularPrice + addOnTotalPrice;\n var adjustedPrice = regularPrice;\n var discountAmount;\n if(item.salePrice && item.salePrice > 0 && item.salePrice < item.regularPrice) {\n adjustedPrice = item.salePrice + addOnTotalPrice;\n discountAmount = regularPrice - adjustedPrice;\n discountAmount = $scope.isReturn ? 0 - discountAmount : discountAmount;\n }\n\n return {\n // converting\n inventoryItemId: item.id,\n price: regularPrice,\n taxCodeId: item.taxCodeId,\n taxCodeDescription: taxCode && taxCode.description,\n taxCodeIsComputed: taxCode && taxCode.computed,\n taxPercent: taxPercent,\n includedTaxPercent: includedTaxPercent,\n adjustedPrice: adjustedPrice,\n discountAmount: discountAmount,\n isInventoryItem: item.inventoryItem,\n isNonInventoryItem: item.nonInventoryItem,\n isRental: item.rental,\n isService: item.service,\n itemNumber: item.itemNumber,\n itemName: item.name,\n itemDescription: item.description,\n itemColor: colorAndSizeBlank ? '' : item.color,\n itemColor2: colorAndSizeBlank ? '' : item.color2,\n itemSize: colorAndSizeBlank ? '' : item.size,\n itemSalePrice: item.salePrice,\n itemDepartmentId: item.departmentId,\n itemVendorItemName: item.vendorItemName,\n itemOrderCost: item.orderCost + addOnTotalCost,\n doNotAllowDiscounts: item.doNotAllowDiscounts,\n lineItemAddOns: lineItemAddOns,\n\n // defaults\n quantity: $scope.isReturn ? -1 : 1,\n transactionId: $scope.transaction.id,\n sequenceNumber: $scope.transaction.lineItems.length,\n status: Enums.statuses.pending.value,\n createdByUser: User.username,\n createdDate: new Date().valueOf(),\n shippingCost: 0,\n shippingTax: 0,\n shippingTaxable: false\n };\n }\n\n /**\n * Handlers\n */\n\n $scope.$on('$stateChangeStart', function(event, toState, toParams) {\n if(ignoreRedirectWarning || $scope.readonly) {\n return;\n }\n\n if($scope.isNewTransaction) {\n event.preventDefault();\n\n modalService.showConfirmation('transaction.message.would-like-to-discard?', function() {\n $scope.transaction.$remove(function() {\n ignoreRedirectWarning = true;\n $state.go(toState, toParams, {location: 'replace' });\n });\n });\n } else if(!$scope.transaction.smartFlowsProcessedDateTime && $scope.transaction.smartFlowsTriggeredDateTime && $scope.transaction.status !== Enums.statuses.voided.value && security.hasPlanPermission('smartFlows')) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.process-smart-flow-before-leaving');\n\n } else if($scope.markSignSalesAgreementRed) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.obtain-signature-before-leaving');\n\n } else if(($scope.isSale || $scope.isReturn) && $scope.transaction.status === Enums.statuses.pending.value) {\n event.preventDefault();\n\n modalService.showMessage($scope.isSale ? 'transaction.message.condition-true-sale' : 'transaction.message.condition-false-return');\n } else if(($scope.isSpecialOrder || $scope.isLayaway) && !$scope.transaction.contactId) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.assign-contact-before-leaving');\n } else if(!$scope.transaction.employeeId) {\n event.preventDefault();\n\n modalService.showMessage('transaction.message.assign-associate-before-leaving');\n }\n });\n\n $rootScope.$on('payment-plan-installment-collected', function(event, data) {\n var transaction = data;\n\n if(transaction && transaction.id === $scope.transaction.id) {\n angular.extend($scope.transaction, _(transaction).omit('lineItems'));\n _($scope.transaction.lineItems).each(function(item, index) {\n item.version = transaction.lineItems[index].version;\n\n _(item.lineItemAddOns).each(function(addOn, i) {\n if(transaction.lineItems[index].lineItemAddOns && transaction.lineItems[index].lineItemAddOns.length && transaction.lineItems[index].lineItemAddOns[i]) {\n addOn.version = transaction.lineItems[index].lineItemAddOns[i].version;\n }\n });\n });\n analytics.trackEvent(\"Payment Added\");\n }\n });\n\n /**\n * Watchers\n */\n\n var ignoreFirstTime = true;\n\n $scope.$watchCollection('[transaction.contactId, transaction.contactName, transaction.contactPhone, transaction.contactPhoneType, transaction.eventDate, transaction.hideOnPortal]', function() {\n if(!ignoreFirstTime) {\n $scope.saveTransaction();\n }\n\n ignoreFirstTime = false;\n });\n\n $scope.itemsColorOptions = {};\n $scope.itemsColor2Options = {};\n $scope.itemsSizeOptions = {};\n $scope.$watchCollection('transaction.lineItems', function(items) {\n // Determine if any lineItems has a pos address assign to it.\n $rootScope.trxHasShippingAddress = trxHasShippingAddress(items);\n $scope.trxHasLineItems = trxHasLineItems(items);\n\n _(items).each(function(item) {\n $scope.itemsColorOptions[item.id] = item.colorString ? item.colorString.trim().split(',') : [];\n $scope.itemsColor2Options[item.id] = item.color2String ? item.color2String.trim().split(',') : [];\n $scope.itemsSizeOptions[item.id] = item.sizeGroup ? item.sizeGroup.sizes.map(function(size) {\n return size.size;\n }) : [];\n });\n });\n\n $scope.$watch('transaction.balanceDueZeroDate', function(newValue, oldValue) {\n if($scope.showZeroOutDate && oldValue && !newValue) {\n alerts.pushError({\n key: 'transaction.message.balance-due-zero-date-removed',\n params: {amount: $filter('currency')(0)}\n });\n $scope.editZeroOutDate = false;\n }\n });\n\n function updateShippingCostAndRecalculate() {\n $scope.queriesInProgress += 1;\n $scope.recalculatingTax = true;\n\n Transaction.updateLineItemsAndCalculateTaxes($scope.transaction, true, function(response) {\n if(response && response.transaction) {\n var savedTransaction = response.transaction;\n\n angular.extend($scope.transaction, _(savedTransaction).omit(\n 'lineItems',\n 'orderDate',\n 'employeeId',\n 'fee',\n 'notes'\n ));\n\n updateLineItemsAfterUpdatingShippingInfo(savedTransaction);\n }\n }).finally(function() {\n // without timeout saving state will be \"jumpy\"\n $timeout(function() {\n $scope.queriesInProgress -= 1;\n }, 1000);\n });\n }\n\n $scope.shippingFieldChange = _.debounce(function(value) {\n if($rootScope.shippingModalOpen || value < 0 || value === null || value === undefined) {\n return;\n }\n\n var changeInShippingTax = false;\n var itemCount = 0;\n var shippingSum = 0;\n\n _($scope.transaction.lineItems).each(function(item) {\n if(item.shippingCost > 0) {\n changeInShippingTax = true;\n }\n\n itemCount++;\n shippingSum += item.shippingCost.valueOf();\n });\n\n if((value === 0 && changeInShippingTax) && (itemCount > 1 || itemCount === 1)) {\n updateShippingCostAndRecalculate();\n } else if(changeInShippingTax && value !== shippingSum && itemCount === 1) {\n updateShippingCostAndRecalculate();\n } else if(changeInShippingTax && value !== shippingSum && itemCount > 1) {\n modalService.showStaticConfirmationCustomButtons({\n message: 'modal-service.shipping-change.shipping-has-changed',\n btn_primary_text: 'modal-service.shipping-change.distribute-manually',\n btn_secondary_text: 'modal-service.shipping-change.distribute-evenly'\n },\n function() {\n $scope.openShipToCustomerModal();\n },\n function() {\n updateShippingCostAndRecalculate();\n });\n }\n }, 2500);\n\n $scope.onCancellationChange = _.debounce(function(reason) {\n onCancellationChange(reason);\n }, 500);\n\n\n $scope.hasShippingTax = function() {\n return _($scope.transaction.lineItems).filter(function(item) {\n return item && item.shippingTax > 0;\n }).length;\n };\n\n /**\n * Init\n */\n\n validateTransactionItems();\n\n // Note popover\n (function() {\n var $notePopover;\n var notePopoverScope;\n\n var closeNotePopover = function(event) {\n if(!angular.element(event.target).parents('.popover').length && $notePopover) {\n $notePopover.popover('destroy');\n }\n };\n\n // get a Receiving Voucher item attached to passed DOM element by tracking parent tr element\n var getTransaction = function(element) {\n var index = parseInt(angular.element(element).parents('tr').attr('data-index'), 10);\n return $scope.transaction.lineItems[index];\n };\n\n var getAddOnItem = function(element) {\n var parentIndex = parseInt(angular.element(element).parents('tr').attr('data-parent-index'), 10);\n var childIndex = parseInt(angular.element(element).parents('tr').attr('data-child-index'), 10);\n return $scope.transaction.lineItems[parentIndex].lineItemAddOns[childIndex];\n };\n\n angular.element('.transaction_items').on('click', '.transaction_item-notes', function(event) {\n event.stopPropagation();\n\n if($notePopover) {\n $notePopover.popover('destroy');\n }\n\n if(notePopoverScope) {\n notePopoverScope.$destroy();\n }\n\n var transaction = getTransaction(this);\n notePopoverScope = $rootScope.$new();\n notePopoverScope.item = transaction;\n notePopoverScope.readonly = $scope.readonly;\n\n var template = '';\n\n $notePopover = angular.element(this).popover({\n content: $compile(template)(notePopoverScope),\n placement: 'right',\n trigger: 'manual',\n html: true\n }).popover('show');\n\n notePopoverScope.$digest();\n\n angular.element($window).on('click', closeNotePopover);\n\n // When popover is closed\n angular.element('.transaction_items').off('hidden.bs.popover').on('hidden.bs.popover', '.transaction_item-notes', function() {\n // Disable close popover handler\n angular.element($window).off('click', closeNotePopover);\n });\n });\n\n angular.element('.transaction_items').on('click', '.transaction_item-add-on-notes', function(event) {\n event.stopPropagation();\n\n if($notePopover) {\n $notePopover.popover('destroy');\n }\n\n if(notePopoverScope) {\n notePopoverScope.$destroy();\n }\n\n var addOnItem = getAddOnItem(this);\n notePopoverScope = $rootScope.$new();\n notePopoverScope.addOn = addOnItem;\n notePopoverScope.readonly = $scope.readonly;\n notePopoverScope.editNotes = $scope.itemAddOnsPerm.editNotes;\n\n var template = '';\n\n $notePopover = angular.element(this).popover({\n content: $compile(template)(notePopoverScope),\n placement: 'right',\n trigger: 'manual',\n html: true\n }).popover('show');\n\n notePopoverScope.$digest();\n\n angular.element($window).on('click', closeNotePopover);\n\n // When popover is closed\n angular.element('.transaction_items').off('hidden.bs.popover').on('hidden.bs.popover', '.transaction_item-add-on-notes', function() {\n // Disable close popover handler\n angular.element($window).off('click', closeNotePopover);\n });\n });\n })();\n\n // Save transaction on any input changing\n angular.element(document).off('change', '.save-trigger').on('change', '.save-trigger', function(event) {\n if(event.target.getAttribute('ng-model') === 'transaction.addtlDiscountPercent') {\n $scope.transaction.addtlDiscountAmount = 0;\n $scope.transaction.addtlDiscountType = 1; //Percent discount type\n } else if(event.target.getAttribute('ng-model') === 'transaction.addtlDiscountAmount') {\n $scope.transaction.addtlDiscountPercent = 0;\n $scope.transaction.addtlDiscountType = 0; //Dollar discount type\n } else if(event.target.getAttribute('ng-model') === 'transaction.employeeId') {\n updateCommissionItemEmployee();\n }\n\n $scope.saveTransaction();\n });\n\n function onCancellationChange(reason) {\n if(reason && !reason.posTransactionCancelTypeId) {\n reason.$remove(function () {\n alerts.push('alerts.transaction.pos-cancel.successfully-removed-cancel-reason');\n $scope.reason.model = initReasonModel($scope.transaction);\n });\n } else {\n var type = _($scope.reason.reasons).findWhere({ id: reason.posTransactionCancelTypeId });\n reason.description = type ? type.description : '';\n\n if(reason && reason.id) {\n reason.modifiedByUser = User.username;\n reason.modifiedDate = new Date();\n }\n\n reason.$saveOrUpdate(function(updatedReason) {\n $scope.reason.model = updatedReason;\n alerts.push('alerts.transaction.pos-cancel.successfully-set-cancel-reason');\n });\n }\n }\n\n function updateCommissionItemEmployee() {\n if($scope.showCommissionLevels && $scope.commissionPerm.assignEmployee && $scope.transaction.employeeId > 0) {\n var commissionItems = [];\n _($scope.transaction.lineItems).each(function(item) {\n if(item && item.commissionItem && item.commissionItem.id && item.commissionItem.status !== 'P') {\n commissionItems.push(item.commissionItem);\n }\n });\n\n var employee = _($scope.employees).findWhere({ id: $scope.transaction.employeeId });\n\n if(commissionItems && commissionItems.length > 0 && employee) {\n modalService.showConfirmation({\n key: 'transaction.message.would-like-update-commission-item-associate?',\n params: {\n number: commissionItems.length,\n employeeName: employee.fullName\n }\n }, function() {\n CommissionItem.bulkUpdateEmployee($scope.transaction.employeeId, _(commissionItems).pluck('id'), getCommissionItemsAndUpdateScopeValues);\n });\n }\n }\n }\n\n function getCommissionItemsAndUpdateScopeValues() {\n CommissionItem.getAllForTransaction($scope.transaction.id, function(items) {\n _($scope.transaction.lineItems).each(function(item, index) {\n var lineItem = $scope.transaction.lineItems[index];\n if(lineItem && lineItem.commissionItem && lineItem.commissionItem.id) {\n var commissionItem = _(items).findWhere({ id: lineItem.commissionItem.id });\n if(commissionItem) {\n $scope.transaction.lineItems[index].commissionItem.version = commissionItem.version;\n $scope.transaction.lineItems[index].commissionItem.employeeId = commissionItem.employeeId;\n }\n }\n });\n\n alerts.pushSuccess('alerts.commission.successfully-updated-commission-associate');\n });\n }\n\n $scope.getSmartFlowTriggerId = function() {\n if(Enums.trxTypes.sale.value == $scope.transaction.typeId) {\n return Enums.smartFlowTriggers.sale_created.id;\n } else if(Enums.trxTypes.special_order.value == $scope.transaction.typeId) {\n return Enums.smartFlowTriggers.special_order_created.id;\n } else if(Enums.trxTypes.layaway.value == $scope.transaction.typeId) {\n return Enums.smartFlowTriggers.layaway_created.id;\n }\n };\n\n $scope.getEventBusKey = function(mode) {\n if(Enums.trxTypes.sale.value == $scope.transaction.typeId) {\n return 'sale:' + mode;\n } else if(Enums.trxTypes.special_order.value == $scope.transaction.typeId) {\n return 'special-order:' + mode;\n } else if(Enums.trxTypes.layaway.value == $scope.transaction.typeId) {\n return 'layaway:' + mode;\n }\n };\n\n $scope.buildTaxAmountTooltip = function(trxItem) {\n var text = $translate.instant('transaction.total-tax') + ': ' + $filter('currency')(trxItem.taxAmount) + '\\n';\n\n if(trxItem.shippingTax) {\n text += $translate.instant('transaction.shipping-tax') + ': ' + $filter('currency')(trxItem.shippingTax);\n }\n return text;\n };\n\n $scope.buildSalePriceTooltip = function(trxItem) {\n if(!trxItem || !trxItem.itemSalePrice || !trxItem.price || trxItem.itemSalePrice <= 0 || trxItem.itemSalePrice >= trxItem.price) {\n return '';\n }\n\n return $translate.instant('item.on-sale') + ': ' + $filter('currency')(trxItem.itemSalePrice);\n };\n\n $scope.validateAddOnItemValue = function(type, addOn, oldValue) {\n if(oldValue === addOn[type]) {\n return false;\n }\n\n if(type === 'price') {\n if(addOn[type] < 0) {\n modalService.showMessage('alerts.add-ons.invalid-price');\n return false;\n } else if(!addOn[type]) {\n addOn[type] = 0;\n }\n } else if(type === 'description') {\n if(addOn[type] === '') {\n modalService.showMessage('alerts.add-ons.invalid-description');\n return false;\n }\n }\n\n return true;\n };\n\n $scope.collapseRow = function(event, trxItem) {\n if(event) {\n event.preventDefault();\n }\n\n trxItem.collapsed = !trxItem.collapsed;\n };\n\n $scope.switchToEdit = function(event, addOn, type, index) {\n if(event) {\n event.preventDefault();\n }\n\n addOn[type + 'OldValue'] = addOn[type];\n addOn.show = {};\n addOn.show[type] = index;\n };\n\n $scope.switchToView = function(event, addOn, trxItem, type, index) {\n if(!addOn.show) {\n return;\n }\n\n if(event) {\n event.preventDefault();\n }\n\n var oldValue = addOn[type + 'OldValue'];\n\n if(oldValue !== undefined && oldValue !== addOn[type]) {\n if($scope.validateAddOnItemValue(type, addOn, oldValue, trxItem)) {\n if(type === 'price') {\n if(!addOn.price) {\n addOn.price = 0;\n }\n\n $scope.onAddOnItemPriceChange(trxItem, addOn.price, oldValue, index);\n } else if(type === 'description') {\n $scope.saveTransactionItem(trxItem, function() {\n alerts.pushSuccess('alerts.transaction.successfully-updated-add-on-item');\n });\n }\n } else {\n addOn[type] = oldValue;\n delete addOn[type + 'OldValue'];\n }\n }\n\n delete addOn.show;\n };\n\n $scope.onAddOnItemPriceChange = function(trxItem, newValue, oldValue) {\n newValue = +newValue;\n\n if(newValue > oldValue) {\n trxItem.price = trxItem.price + (newValue - oldValue);\n } else {\n trxItem.price = trxItem.price - (oldValue - newValue);\n }\n\n if(trxItem.discountType === 0) {\n trxItem.discountPercent = 0; //let the backend compute the new discount percent\n trxItem.adjustedPrice = trxItem.price - trxItem.discountAmount;\n } else {\n trxItem.discountAmount = null; //let the backend compute the new discount amount\n }\n\n $scope.saveTransactionItem(trxItem, function() {\n alerts.pushSuccess('alerts.transaction.successfully-updated-add-on-item');\n });\n };\n\n $scope.applySizeChange = function(trxItem, oldSize, newSize) {\n if(!trxItem.sizeGroup) {\n return;\n }\n\n if(($scope.isColorRequired(trxItem) || $scope.isSizeRequired(trxItem)) && !newSize) {\n $scope.allowInvalidSave = true;\n }\n\n //check to see if there was an upcharge on the old size, if so, the upcharge will be removed before adding another one.\n //this helps fix the issue of having an inventory item that has the upcharge already added\n //into the price.\n var oldSizeObj = _(trxItem.sizeGroup.sizes).findWhere({ size: oldSize });\n var newSizeObj = _(trxItem.sizeGroup.sizes).findWhere({ size: newSize });\n\n if(!newSizeObj) {\n newSizeObj = {upchargePrice: 0, upchargePriceType: 0};\n }\n\n //if their was no size chart for the old size, create a dummy object for comparison\n if(!oldSizeObj) {\n oldSizeObj = {upchargePrice: 0, upchargePriceType: 0};\n }\n\n var sizeUpchargeAppliedToOldSize = oldSizeObj && oldSizeObj.upchargePrice;\n\n //only change pricing if the old size upcharge is not the same as the new size upcharge\n if(oldSizeObj && newSizeObj && (oldSizeObj.upchargePrice !== newSizeObj.upchargePrice || oldSizeObj.upchargePriceType !== newSizeObj.upchargePriceType)) {\n if(!sizeUpchargeAppliedToOldSize) {\n //look up the upcharge and apply\n $scope.applySizeUpcharge(trxItem, trxItem.sizeGroup.sizes, newSize);\n\n if(trxItem.discountType === 0) {\n trxItem.discountPercent = 0; //let the backend compute the new discount percent\n trxItem.adjustedPrice = trxItem.price - trxItem.discountAmount;\n } else {\n trxItem.discountAmount = null; //let the backend compute the new discount amount\n }\n\n } else {\n //if the upcharge was applied to the old size and then they changed it to something else\n //then we need to remove the old size upcharge and apply the other one;\n $scope.removeSizeUpcharge(trxItem, trxItem.sizeGroup.sizes, oldSize);\n $scope.applySizeUpcharge(trxItem, trxItem.sizeGroup.sizes, newSize);\n\n if(trxItem.discountType === 0) {\n trxItem.discountPercent = 0; //let the backend compute the new discount percent\n trxItem.adjustedPrice = trxItem.price - trxItem.discountAmount;\n } else {\n trxItem.discountAmount = null; //let the backend compute the new discount amount\n }\n\n }\n }\n };\n\n $scope.applySizeUpcharge = function(trxItem, availableSizes, itemSize) {\n if(availableSizes && availableSizes.length) {\n for (var i = 0; i < availableSizes.length; i++) {\n if(availableSizes[i].size === itemSize) {\n if(availableSizes[i].upchargePrice) {\n\n //detect which type of upcharge to apply\n if(availableSizes[i].upchargePriceType === 0) {\n trxItem.price = trxItem.price + availableSizes[i].upchargePrice;\n } else if(availableSizes[i].upchargePriceType === 1) {\n var upchargePercent = availableSizes[i].upchargePrice / 100;\n trxItem.price = trxItem.price + (upchargePercent * trxItem.price);\n trxItem.price = Number(trxItem.price.toFixed(2));\n } else {\n continue;\n }\n\n trxItem.sizeUpchargeApplied = true;\n break;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n };\n\n $scope.removeSizeUpcharge = function(trxItem, availableSizes, itemSize, updateSalePrice) {\n if(availableSizes && availableSizes.length) {\n for (var i = 0; i < availableSizes.length; i++) {\n if(availableSizes[i].size === itemSize) {\n if(availableSizes[i].upchargePrice) {\n\n //detect which type of upcharge to apply\n if(availableSizes[i].upchargePriceType === 0) {\n // When add an item to a transaction and we plan to remove the up size charge. Then go ahead and do it for sales price also if it exists.\n if(updateSalePrice && trxItem.itemSalePrice && trxItem.itemSalePrice > 0 && trxItem.itemSalePrice < trxItem.price) {\n trxItem.adjustedPrice = trxItem.itemSalePrice - availableSizes[i].upchargePrice;\n }\n\n trxItem.price = trxItem.price - availableSizes[i].upchargePrice;\n } else if(availableSizes[i].upchargePriceType === 1) {\n var upchargePercent = availableSizes[i].upchargePrice / 100;\n\n // When add an item to a transaction and we plan to remove the up size charge. Then go ahead and do it for sales price also if it exists.\n if(updateSalePrice && trxItem.itemSalePrice && trxItem.itemSalePrice > 0 && trxItem.itemSalePrice < trxItem.price) {\n trxItem.adjustedPrice = trxItem.itemSalePrice / (1 + upchargePercent);\n trxItem.adjustedPrice = Number(trxItem.itemSalePrice.toFixed(2));\n }\n\n trxItem.price = trxItem.price / (1 + upchargePercent);\n trxItem.price = Number(trxItem.price.toFixed(2));\n } else {\n continue;\n }\n\n trxItem.sizeUpchargeApplied = false;\n break;\n } else {\n //no size charge, get out of the loop\n break;\n }\n }\n }\n }\n };\n\n $scope.isColorRequired = function(trxItem) {\n if($scope.isSpecialOrder) {\n if(trxItem.quantity !== 0 && !trxItem.isNonInventoryItem && !trxItem.isService) {\n if($scope.colorAndSizeRequired || (!$scope.colorOrSizeAllowCustomVal && $scope.itemsColorOptions[trxItem.id].length)) {\n return true;\n }\n }\n }\n return false;\n };\n\n $scope.isSizeRequired = function(trxItem) {\n if($scope.isSpecialOrder) {\n if(trxItem.quantity !== 0 && !trxItem.isNonInventoryItem && !trxItem.isService) {\n if($scope.colorAndSizeRequired || (!$scope.colorOrSizeAllowCustomVal && $scope.itemsSizeOptions[trxItem.id].length)) {\n return true;\n }\n }\n }\n return false;\n };\n\n $scope.showSizeModal = function(trxItem, sizeGroup) {\n itemSizesModal.view(trxItem, sizeGroup, $scope.transaction.contact, true, function(size) {\n if(size) {\n $scope.applySizeChange(trxItem, trxItem.itemSize, size);\n trxItem.itemSize = size;\n $scope.saveTransactionItem(trxItem);\n }\n });\n };\n\n $scope.getMinQuantity = function() {\n if(!isReturn) {\n if(isNF525) {\n return 0;\n } else {\n return 1;\n }\n } else {\n return '';\n }\n };\n\n $scope.getMaxQuantity = function() {\n if(isReturn) {\n if(isNF525) {\n return 0;\n } else {\n return -1;\n }\n } else {\n return '';\n }\n };\n\n // open register to prom when redirected from contact.favorites\n var onRedirectFromFavorites = function() {\n if($stateParams.promptLeadAndItemAddOns) {\n getAddOnItems($scope.transaction.lineItems, 0, true);\n }\n\n var itemsToPrompt = _($scope.transaction.lineItems).where({ promptToRegister: true });\n var itemsNumber = itemsToPrompt.length;\n\n if(!itemsNumber || !security.hasPermission(Permissions.REGISTRY)) {\n return;\n }\n\n if(itemsNumber === 1) {\n PromRegistryModal.registerToProm(itemsToPrompt[0], $scope.transaction, false, updateContactEventDateOnTransaction);\n } else {\n modalService.showMessage('transaction.message.items-that-require-prom-registration');\n }\n };\n\n var onRedirectFromRegistry = function() {\n getAddOnItems($scope.transaction.lineItems, 0, true);\n };\n\n // Open payment modal when redirected from 'Create Return Modal'\n var onRedirectFromCreateReturn = function(paymentId) {\n TransactionPaymentsModalService.open($scope.transaction, signatures, $scope.readonly, true, paymentId);\n };\n\n var onRedirectFromConvertingToSOFromSale = function() {\n var lineItems = angular.copy($scope.transaction.lineItems);\n getAddOnItems(lineItems, 0, false);\n };\n\n function getAddOnItems(lineItems, currentIndex, fetchItems) {\n var transactionItem = lineItems[currentIndex];\n\n if(transactionItem && transactionItem.inventoryItemId) {\n Item.getById(transactionItem.inventoryItemId, function(item) {\n $scope.getAvailableAddOnsAndOpenModal(transactionItem, item, {\n showModalOnlyIfAddOnsAvailable: true,\n fetchItemAddOns: fetchItems\n }, function() {\n currentIndex++;\n var nextLineItem = lineItems[currentIndex];\n if(nextLineItem) {\n getAddOnItems(lineItems, currentIndex, fetchItems);\n }\n });\n });\n }\n }\n\n if($stateParams.referrer === 'contact.favorites' && $scope.transaction.version && $scope.transaction.lineItems.length) {\n onRedirectFromFavorites();\n } else if($stateParams.referrer === 'create-return') {\n onRedirectFromCreateReturn(null);\n } else if($stateParams.referrer === 'view-payment') {\n onRedirectFromCreateReturn($stateParams.paymentId);\n } else if($stateParams.referrer === 'contact.registry' && $scope.transaction.version && $scope.transaction.lineItems.length && $stateParams.promptLeadAndItemAddOns) {\n onRedirectFromRegistry();\n }\n\n if($stateParams.promptLeadTimes) {\n onRedirectFromConvertingToSOFromSale();\n }\n\n });\n });\n\n","define('modules/transaction/line-item-address-info-popover/line-item-address-info-popover-directive',[\n 'angular',\n 'underscore',\n 'text!./line-item-address-info-popover-directive.tpl.html',\n '../../resources/address',\n '../../resources/quote-address',\n], function (angular, _, popoverTpl) {\n 'use strict';\n\n return angular.module('bl.transaction.address-info', [])\n\n .directive('lineItemAddressInfoPopover', function ($compile, $q, Address, QuoteAddress) {\n return {\n restrict: 'A',\n scope: {\n lineItem: '=lineItemAddressInfoPopover',\n disableActions: '=lineItemAddressInfoDisable',\n lineItemType: '=lineItemAddressInfoType',\n modalFunction: '&'\n },\n link: function (scope, element) {\n var Resource = Address;\n var popover;\n var compilerTpl = $compile('' + popoverTpl + '
');\n var loadingClass = 'popover-loading';\n\n if(scope.lineItemType === 'quote') {\n Resource = QuoteAddress;\n }\n\n scope.address = {};\n var isInitialized = false;\n var isOpening;\n\n // Open popover\n element.click(function() {\n if(isInitialized) {\n return scope.showPopover();\n } else if(isOpening) {\n return;\n }\n\n isOpening = true;\n var addressId = scope.lineItem.addressId;\n element.addClass(loadingClass);\n\n // Load Address\n var addressPromise = Resource.getById(addressId, function(address) {\n angular.extend(scope.address, address);\n });\n\n $q.all([addressPromise]).then(function() {\n element.removeClass(loadingClass);\n isInitialized = true;\n isOpening = false;\n popover = element.popover({\n content: compilerTpl(scope),\n placement: 'right',\n trigger: 'manual',\n html: true,\n container: 'body'\n });\n\n setTimeout(function () {\n scope.showPopover();\n }, 0);\n }\n );\n });\n\n scope.openModal = function() {\n scope.modalFunction();\n\n popover.popover('hide');\n angular.element(document).off('mousedown', hidePopover);\n };\n\n scope.showPopover = function() {\n popover.popover('show');\n\n angular.element(document).on('mousedown', hidePopover);\n };\n\n // Close on clicking outside\n function hidePopover(event) {\n var target = angular.element(event.target);\n var isInsidePopover = target.closest('.popover').length && !target.is('a');\n var isTriggerLink = target.is(element);\n\n if(!isInsidePopover && !isTriggerLink) {\n popover.popover('hide');\n angular.element(document).off('mousedown', hidePopover);\n }\n }\n }\n };\n });\n});\n\n","define('modules/payment-plans/modals/view-payment-plan/view-payment-plan-modal-ctrl',['../../module', 'angular', 'underscore', 'equals', 'moment'], function (module, angular, _, equals, moment) {\n 'use strict';\n\n module.controller('ViewPaymentPlanCtrl', function (\n $scope,\n $state,\n $translate,\n $window,\n $locale,\n $filter,\n $modal,\n $timeout,\n $compile,\n analytics,\n security,\n AppLicense,\n FULLSTEAM,\n Transaction,\n ResourcesHelper,\n FullcalendarHelper,\n alerts,\n Signature,\n PaymentPlanItem,\n PaymentPlanTemplate,\n MergeFields,\n FullSteamHelper,\n FullSteamModal,\n FullSteam,\n TransactionPaymentsModalService,\n PaymentEmailSettings,\n Contact,\n CompanySetting,\n Enums,\n User,\n modalService,\n EmailService,\n Sms,\n plan,\n emailTemplates,\n smsTemplates,\n PaymentPlan,\n settings,\n posSettings,\n clientPortalSettings,\n customPaymentPlanAgreements,\n customPaymentPlanContracts,\n contact,\n employees,\n view\n ) {\n\n $scope.contact = contact;\n $scope.locale = $locale;\n $scope.agreements = customPaymentPlanAgreements;\n $scope.contracts = customPaymentPlanContracts;\n $scope.today = moment().startOf('day').format();\n $scope.now = new Date();\n $scope.provider = null;\n $scope.providerError = null;\n\n $scope.model = {\n statusType: true\n };\n\n if(plan.status === Enums.statuses.pending.value) {\n $scope.model.statusType = false;\n }\n\n $scope.hasAllChargeItProSettings = TransactionPaymentsModalService.hasAllChargeItProSettings(posSettings);\n $scope.hasAllStripeSettings = TransactionPaymentsModalService.hasAllStripeSettings(posSettings);\n $scope.hasAllFullSteamSettings = TransactionPaymentsModalService.hasAllFullSteamSettings(posSettings);\n\n var providers = {\n 'S':'Stripe',\n 'F':'BridalLive Pay',\n 'C':'Charge It Pro'\n };\n\n if($scope.hasAllFullSteamSettings) {\n $scope.provider = 'F';\n } else if($scope.hasAllChargeItProSettings) {\n $scope.provider = 'C';\n } else if($scope.hasAllStripeSettings) {\n $scope.provider = 'S';\n }\n\n // determine visibility of client portal feature\n var portalFeature = _(clientPortalSettings).findWhere({ type: 'CP_FEATURE_DIGITAL_SIGNATURES' });\n var portalFeatureEnabled = portalFeature ? portalFeature.value === 'Y' : true;\n var userHasPermission = security.hasPermission(\"REQUEST_SIGNATURES\");\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n\n var onlinePaymentsEnabled = $scope.hasAllFullSteamSettings || $scope.hasAllChargeItProSettings || $scope.hasAllStripeSettings;\n $scope.requestSignatureAvailable = (onlinePaymentsEnabled && userHasPermission) || (licenseHasPortalAccess && portalFeatureEnabled && userHasPermission);\n\n var setting = _(posSettings).findWhere({ type: 'POS_FULLSTEAM_ALLOW_CUSTOMER_LEAVE_TIP' });\n $scope.promptForTip = setting ? setting.value === 'Y' : false;\n setting = _(posSettings).findWhere({ type: 'POS_FULLSTEAM_ENABLE_TIP_FEATURE' });\n $scope.fullSteamTipFeatureEnabled = setting ? setting.value === 'Y' : false;\n setting = _(posSettings).findWhere({ type: 'USE_DIG_SIGN' });\n $scope.digitalSignatureEnabled = setting && setting.value === 'Y';\n\n var paymentPlanSetting = _(settings).findWhere({ type: 'POS_PP_REQUIRE_SIG' });\n var isSignatureRequired = !!(paymentPlanSetting && paymentPlanSetting.value === 'Y') && $scope.digitalSignatureEnabled;\n $scope.markSignSalesAgreementRed = isSignatureRequired;\n\n $scope.canEditPlan = security.hasPermission('EDIT_PAYMENT_PLAN');\n $scope.canEditInstallments = security.hasPermission('EDIT_PAYMENT_PLAN_INSTALLMENT');\n\n buildTemplates(emailTemplates, smsTemplates);\n\n $scope.employees = ResourcesHelper.adaptList('employees', _(employees).where({ status: Enums.statuses.active.value }));\n\n var cache = {\n autoPay: null,\n successSMSId: null,\n successEmailId: null,\n errorSMSId: null,\n errorEmailId: null,\n daysBeforeReminder: null,\n status: null,\n set: function (autoPay, successSMSId, successEmailId, errorSMSId, errorEmailId, daysBeforeReminder, status) {\n cache.autoPay = angular.copy(autoPay);\n cache.successSMSId = angular.copy(successSMSId);\n cache.successEmailId = angular.copy(successEmailId);\n cache.errorSMSId = angular.copy(errorSMSId);\n cache.errorEmailId = angular.copy(errorEmailId);\n cache.daysBeforeReminder = angular.copy(daysBeforeReminder);\n cache.status = angular.copy(status);\n },\n isDifferent: function () {\n return !equals(cache.autoPay, $scope.paymentPlan.autoPay)\n || !equals(cache.successSMSId, $scope.paymentPlan.successSMSTemplateId)\n || !equals(cache.successEmailId, $scope.paymentPlan.successEmailTemplateId)\n || !equals(cache.errorSMSId, $scope.paymentPlan.errorSMSTemplateId)\n || !equals(cache.errorEmailId, $scope.paymentPlan.errorEmailTemplateId)\n || !equals(cache.daysBeforeReminder, $scope.paymentPlan.daysBeforeReminder)\n || !equals(cache.status, $scope.paymentPlan.status)\n ;\n }\n };\n\n function populateScope($scope, plan) {\n $scope.paymentPlan = populatePaymentPlan(plan);\n $scope.readonly = plan.status === Enums.statuses.complete.value;\n cache.set($scope.paymentPlan.autoPay, $scope.paymentPlan.successSMSTemplateId, $scope.paymentPlan.successEmailTemplateId, $scope.paymentPlan.errorSMSTemplateId, $scope.paymentPlan.errorEmailTemplateId, $scope.paymentPlan.daysBeforeReminder || 3, $scope.paymentPlan.status);\n }\n\n function buildLink(trxTypeId, payment) {\n var stateToRedirect;\n\n switch (trxTypeId) {\n case Enums.trxTypes.sale.value:\n stateToRedirect = 'transaction.sale';\n break;\n case Enums.trxTypes.layaway.value:\n stateToRedirect = 'transaction.layaway';\n break;\n case Enums.trxTypes.special_order.value:\n stateToRedirect = 'transaction.specialOrder';\n break;\n case Enums.trxTypes.return_trx.value:\n stateToRedirect = 'transaction.return';\n break;\n }\n\n if(stateToRedirect) {\n return stateToRedirect + '({ id: ' + payment.transactionId + ', referrer: \\\"view-payment\\\", paymentId: ' + payment.id + ' })';\n } else {\n return '';\n }\n }\n\n populateScope($scope, plan);\n\n function populatePaymentPlan(plan) {\n _.each(plan.paymentItems, function(item) {\n if(item.reminderSent) {\n item.reminderDateLabel = $translate.instant('payment-plans.reminder-sent') + \" \" + $filter('date')(item.reminderSent, 'MMM d, yyyy');\n } else if(!plan.reminderSMSTemplateId && !plan.reminderEmailTemplateId) {\n item.reminderDateLabel = null;\n } else if(item.reminderDate) {\n item.reminderDateLabel = $translate.instant('payment-plans.reminder-date') + \" \" + $filter('date')(item.reminderDate, 'MMM d, yyyy');\n }\n\n if(moment(item.paymentDueDate).isBefore($scope.today, 'day')) {\n item.pastDue = moment(item.paymentDueDate).fromNow();\n } else {\n delete item.pastDue;\n }\n });\n\n plan.recentPayments = _.uniq(plan.recentPayments, function (payment) {\n return payment.id;\n });\n\n _.each(plan.recentPayments , function(payment) {\n payment.link = buildLink(plan.trxTypeId, payment);\n });\n\n return plan;\n }\n\n $scope.signSalesAgreementModal = function(salesAgreement) {\n $modal\n .open({\n templateUrl: 'js/modules/transaction/details/modals/sign-payment-plan-agreement.tpl.html',\n controller: 'SignPaymentPlanAgreementCtrl',\n windowClass: '__protected __large',\n backdrop: 'static',\n keyboard: false,\n resolve: {\n signatures: function(Signature) {\n return Signature.query({ filterObject: { paymentPlanId: $scope.paymentPlan.id } });\n },\n awsPolicyAndSignature: function(Signature) {\n return Signature.getS3Policy();\n },\n customSalesAgreements: function() {\n return $scope.agreements;\n },\n agreement: function() {\n return salesAgreement;\n },\n paymentPlan: function() {\n return $scope.paymentPlan;\n }\n }\n }).result.then(function(savedSignature) {\n if(savedSignature) {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n\n //mark the sales agreement as signed\n if(salesAgreement) {\n salesAgreement.signed = true;\n }\n } else {\n //if the signature was deleted, reset the state of the signature button\n //mark the sales agreement as not signed\n if(salesAgreement) {\n salesAgreement.signed = false;\n }\n\n var signedAgreements = _($scope.agreements).filter(function(agreement) {\n return agreement.signed;\n });\n\n var signedContracts = _($scope.contracts).filter(function(contract) {\n return contract.signed;\n });\n\n $scope.markSignSalesAgreementRed = signedAgreements.length === 0 && signedContracts.length === 0;\n $scope.markSignSalesAgreementGreen = signedAgreements.length > 1 || signedContracts.length > 1;\n\n if($scope.markSignSalesAgreementRed) {\n $scope.markSignSalesAgreementRed = isSignatureRequired;\n }\n }\n });\n };\n\n $scope.removeCard = function () {\n if($scope.paymentPlan.paymentProfileToken) {\n modalService.showConfirmation('settings.merchant-account.are-you-sure-you-would-like-to-delete?', function () {\n clearPaymentPlanCardDetails();\n });\n }\n };\n\n function clearPaymentPlanCardDetails() {\n $scope.paymentPlan.paymentProfileToken = null;\n $scope.paymentPlan.paymentProfileTokenProvider = null;\n $scope.paymentPlan.paymentProfileMaskedAcctNumber = null;\n $scope.paymentPlan.paymentProfileExpiration = null;\n $scope.paymentPlan.paymentProfileCardType = null;\n $scope.paymentPlan.paymentProfileBillingName = null;\n $scope.paymentPlan.paymentProfileAddedBy = null;\n $scope.paymentPlan.cardDescription = null;\n }\n\n $scope.signTerminalContract = function(customContract) {\n if(!$window.localStorage.fullsteamTerminalId) {\n openSetTerminalModal(customContract, null, false);\n } else {\n openTerminalContractModal(customContract, $scope.paymentPlan.id);\n }\n };\n\n function openTerminalContractModal(contract, paymentPlanId, tempTerminal) {\n if((contract && contract.signed) || (tempTerminal && tempTerminal.terminalId) || isDeviceTypeValidForContract($window.localStorage.fullsteamTerminalType, contract)) {\n $modal\n .open({\n templateUrl: 'js/modules/transaction/details/modals/sign-terminal-contract.tpl.html',\n controller: 'SignTerminalContractCtrl',\n windowClass: '__protected __large',\n backdrop: 'static',\n keyboard: false,\n resolve: {\n agreement: function() {\n return contract;\n },\n signatures: function(Signature) {\n return Signature.query({ filterObject: { paymentPlanId: paymentPlanId } });\n },\n tempTerminal: function () {\n return tempTerminal;\n },\n paymentPlan: function () {\n return $scope.paymentPlan;\n }\n }\n }).result.then(function(savedSignature) {\n if(savedSignature) {\n $scope.markSignSalesAgreementRed = false;\n $scope.markSignSalesAgreementGreen = true;\n\n //mark the sales agreement as signed\n if(contract) {\n contract.signed = true;\n }\n } else {\n //if the signature was deleted, reset the state of the signature button\n //mark the sales agreement as not signed\n if(contract) {\n contract.signed = false;\n }\n\n var signedAgreements = _($scope.agreements).filter(function(agreement) {\n return agreement.signed;\n });\n\n var signedContracts = _($scope.contracts).filter(function(contract) {\n return contract.signed;\n });\n\n $scope.markSignSalesAgreementRed = signedAgreements.length === 0 && signedContracts.length === 0;\n $scope.markSignSalesAgreementGreen = signedAgreements.length > 1 || signedContracts.length > 1;\n\n if($scope.markSignSalesAgreementRed) {\n $scope.markSignSalesAgreementRed = isSignatureRequired;\n }\n }\n });\n }\n }\n\n function isDeviceTypeValidForContract(device, agreement) {\n if(!device) {\n return true;\n }\n\n if(FullSteamHelper.isDeviceTypeNotContractEligible(device)) {\n FullSteam.getTerminals(function(result) {\n var terminals = _((result && result.terminals)).filter(FullSteamHelper.isContractTerminal);\n\n if(terminals && terminals.length) {\n openSetTerminalModal(agreement, terminals, true);\n } else {\n modalService.showMessage('settings.custom-sales-agreement.devices-eligible');\n }\n\n }, function () {\n modalService.showMessage('settings.custom-sales-agreement.devices-eligible');\n });\n\n return false;\n }\n\n return true;\n }\n\n function openSetTerminalModal(agreement, terminals, selectingContractTerminal) {\n return $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/set-terminal-modal.tpl.html',\n controller: 'SetTerminalModalCtrl',\n resolve: {\n terminalResult: function () {\n if(terminals && terminals.length) {\n return { terminals: terminals };\n } else {\n return FullSteam.getTerminals();\n }\n },\n selectingContractTerminal: function () {\n return selectingContractTerminal;\n }\n }\n }).result.then(function(response) {\n if(response) {\n openTerminalContractModal(agreement, $scope.paymentPlan.id, response);\n } else {\n return modalService.showMessage('alerts.merchant-account.please-set-terminal-capture','settings.merchant-account.required-terminal');\n }\n });\n }\n\n $scope.loadSignatures = function () {\n Signature.listAllForPaymentPlan($scope.paymentPlan.id, function(loadedSignatures) {\n $scope.markSignSalesAgreementGreen = _(loadedSignatures).some(function(signature) {\n return Boolean(signature.paymentPlanId);\n });\n\n if(isSignatureRequired) {\n $scope.markSignSalesAgreementRed = !$scope.markSignSalesAgreementGreen;\n }\n\n //now mark each sales agreement as signed, if we have a signature for it\n _(loadedSignatures).each(function(signature) {\n var agreement;\n if(signature.salesAgreementId && signature.paymentPlanId) {\n agreement = _(posSettings).findWhere({ id: signature.salesAgreementId });\n if(!agreement) {\n agreement = _(customPaymentPlanAgreements).findWhere({ id: signature.salesAgreementId });\n }\n\n if(!agreement) {\n agreement = _(customPaymentPlanContracts).findWhere({ id: signature.salesAgreementId });\n }\n }\n\n if(agreement) {\n agreement.signed = true;\n }\n });\n });\n };\n\n /* METHODS */\n\n $scope.getCardImage = function (result) {\n return cardImageType(result);\n };\n\n function cardImageType(details) {\n if(!details || !details.paymentProfileCardType) {\n return '';\n }\n\n var type = details.paymentProfileCardType.toLowerCase();\n\n if(type === 'visa') {\n return '/assets/img/visa.png';\n } else if(type === 'mastercard') {\n return '/assets/img/mastercard.png';\n } else if(type === 'discover') {\n return '/assets/img/discover.png';\n } else if(type === 'american express') {\n return '/assets/img/american_express.png';\n } else {\n return '';\n }\n }\n\n $scope.sendEmail = function(entity, defaultType) {\n var entities = {\n payment: null,\n paymentPlan: null,\n paymentPlanItem: null,\n transaction: null,\n planAgreements: []\n };\n\n if(defaultType === 'payment') {\n entities.payment = entity;\n entities.transaction = { id: entity.transactionId };\n } else if(defaultType === 'paymentPlanItem') {\n entities.paymentPlanItem = entity;\n entities.paymentPlan = { id: entity.paymentPlanId };\n } else if(defaultType === 'paymentPlan') {\n entities.paymentPlan = entity;\n entities.transaction = { id: entity.transactionId };\n }\n\n var promise = EmailService.showSendModal($scope.contact, null, null, entities.transaction, null, null, buildCustomEmailOptions(defaultType), false, entities.payment, entities.paymentPlan, entities.paymentPlanItem);\n\n if(promise && defaultType === 'paymentPlanItem') {\n promise.result.then(function(email) {\n if(!email) {\n return;\n }\n\n if(email.sentOn) {\n console.log(email.sentOn);\n }\n });\n }\n };\n\n function buildCustomEmailOptions(defaultType) {\n var agreements = [];\n\n if(defaultType === 'paymentPlan') {\n agreements = [{ ticked: true, icon: '', description: $translate.instant('modal-service.select-a-payment-plan-agreement') }];\n var terminalIcon = ' ';\n var signedIcon = $translate.instant('common.modal.signed-pen-icon');\n\n _($scope.agreements).each(function(agreement) {\n agreements.push(_.extend(agreement, {\n icon: agreement.signed ? signedIcon : '',\n ticked: false\n }));\n });\n\n _($scope.contracts).each(function(contract) {\n agreements.push(_.extend(contract, {\n icon: contract.signed ? terminalIcon + ' ' + signedIcon : terminalIcon,\n ticked: false\n }));\n });\n }\n\n return {\n showOptions: defaultType === 'paymentPlan',\n defaultTemplateType: defaultType,\n showPlanAgreements: defaultType === 'paymentPlan',\n planAgreements: agreements\n };\n }\n\n $scope.openStoreCreditCardModal = function () {\n var contact = angular.copy($scope.contact);\n $modal.open({\n templateUrl: 'js/modules/appointment-modal/store-credit-card-modal.tpl.html',\n controller: 'StoreCreditCardCtrl',\n resolve: {\n contact: function () {\n return angular.extend(contact || {}, {\n paymentProfileToken: null,\n paymentProfileTokenProvider: null,\n paymentProfileMaskedAcctNumber: null,\n paymentProfileExpiration: null,\n paymentProfileCardType: null,\n paymentProfileBillingName: null\n });\n },\n saveCardToContact: function() {\n return false;\n },\n options: function() {\n return {\n overrideCard: !!($scope.contact && $scope.contact.id && $scope.contact.paymentProfileToken),\n saveToContact: !!($scope.contact && $scope.contact.id && !$scope.contact.paymentProfileToken)\n };\n },\n posSettings: function () {\n return CompanySetting.getPOSSettings();\n },\n previewMode: function () {\n return false;\n }\n },\n windowClass: '__medium',\n backdrop: 'static',\n keyboard: false\n }).result.then(function (response) {\n if(response && response.paymentPlanUpdateInfo) {\n $scope.paymentPlan.cardDescription = populateCardDetailsOnPaymentPlan($scope, response.card);\n savePaymentPlan();\n } else if(response && response.updateAllOfContactInfo) {\n $scope.contact = response.contact;\n $scope.$emit('payment-info-updated', { provider: 'F', contact: response.contact, contactId: contact && contact.id });\n $scope.paymentPlan.cardDescription = populateCardDetailsOnPaymentPlan($scope, $scope.contact);\n savePaymentPlan();\n } else if(response && response.paymentProfileToken) {\n $scope.$emit('payment-info-updated', { provider: null, contact: response, contactId: contact && contact.id });\n $scope.paymentPlan.cardDescription = populateCardDetailsOnPaymentPlan($scope, response);\n savePaymentPlan();\n }\n });\n };\n\n $scope.requestSignature = function () {\n if(!$scope.paymentPlan.contactId) {\n modalService.showMessage('transaction.message.assign-contact-first');\n } else {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/request-plan-signature.tpl.html',\n controller: 'RequestPlanSignatureCtrl',\n windowClass: '__small',\n resolve: {\n companyProfile: function (Company) {\n return Company.get();\n },\n posSettings: function (CompanySetting) {\n return CompanySetting.getPOSSettings();\n },\n customPaymentPlanAgreements: function(CompanySetting) {\n return CompanySetting.getCustomPaymentPlanAgreements();\n },\n smsTemplates: function (SMSTemplate) {\n return SMSTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n emailTemplates: function (EmailTemplate) {\n return EmailTemplate.query({ filterObject: { status: Enums.statuses.active.value }});\n },\n twilioSettings: function (TwilioSettings) {\n return TwilioSettings.get();\n },\n contact: function (Contact) {\n return Contact.getById($scope.paymentPlan.contactId);\n },\n transaction: function () {\n return Transaction.getById($scope.paymentPlan.transactionId);\n },\n paymentPlan: function () {\n return $scope.paymentPlan;\n }\n }\n });\n }\n };\n\n $scope.deletePlan = function() {\n if(!security.hasPermission('DELETE_PAYMENT_PLAN')) {\n modalService.showMessage('payment-plans.permission-to-delete');\n return;\n }\n\n modalService.showConfirmationWithPromptedAction('payment-plans.delete-confirmation',\n $translate.instant('delete').toUpperCase(),\n 'Delete',\n 'payment-plans.delete-warning',\n function () {\n\n var paymentPlan = $scope.paymentPlan;\n paymentPlan.$remove(function () {\n alerts.push('alerts.payment-plans.successfully-deleted-payment-plan');\n $scope.$close({ type: 'delete', plan: paymentPlan });\n\n if(view !== 'search') {\n $scope.$emit('payment-plan-updated');\n }\n\n });\n });\n };\n\n $scope.onSave = function() {\n if(!security.hasPermission('EDIT_PAYMENT_PLAN') && !security.hasPermission('EDIT_PAYMENT_PLAN_INSTALLMENT')) {\n modalService.showMessage('payment-plans.permission-to-save');\n return;\n }\n\n savePaymentPlan(true);\n };\n\n $scope.onClose = function() {\n if(cache.isDifferent() && (security.hasPermission('EDIT_PAYMENT_PLAN') || security.hasPermission('EDIT_PAYMENT_PLAN_INSTALLMENT'))) {\n modalService.showStaticConfirmation(\"payment-plans.save-confirmation\",\n function () {\n savePaymentPlan(true);\n }, function() {\n if(isSignatureRequiredAndValid()) {\n $scope.$close(null);\n }\n }\n );\n } else {\n if(isSignatureRequiredAndValid()) {\n $scope.$close(null);\n }\n }\n };\n\n function formatInstallmentDates(plan) {\n _(plan.paymentItems).each(function (item, i) {\n item.paymentDueDate = +moment(item.paymentDueDate).format('x');\n var time = (new Date(item.paymentDueDate)).setHours(0, 0, 0, 0);\n var dates = FullcalendarHelper.generateDates(item.paymentDueDate, time, false, 3600000);\n item.paymentDueDate = dates.start.getTime();\n item.dueDate = moment(dates.selectedDate).format('YYYY-MM-DD');\n item.modifiedDate = plan.modifiedDate;\n item.modifiedByUser = plan.modifiedByUser;\n });\n }\n\n function savePaymentPlan(closeModal) {\n var plan = angular.copy($scope.paymentPlan);\n plan.modifiedDate = new Date();\n plan.modifiedByUser = User.username;\n\n formatInstallmentDates(plan);\n //updatePaymentPlanDescription(plan);\n\n PaymentPlan.updatePaymentPlan(_(plan).omit('firstInstallmentDate'), function(updatedPlan) {\n alerts.push('alerts.payment-plans.successfully-saved-payment-plan');\n $scope.paymentPlan = populatePaymentPlan(_(updatedPlan).omit('firstInstallmentDate'));\n $scope.paymentPlan.paymentItems = populateFieldsAndSortPlanItems(updatedPlan.paymentItems);\n\n if(closeModal) {\n if(isSignatureRequiredAndValid()) {\n $scope.$close({ type: 'change', plan: updatedPlan });\n } else if(view === 'search') {\n $scope.$emit('payment-plan-updated', { view: view });\n }\n }\n\n if(view !== 'search') {\n $scope.$emit('payment-plan-updated', { view: view });\n }\n\n }, function () {\n console.log('Error Callback');\n });\n }\n\n function isSignatureRequiredAndValid() {\n if($scope.markSignSalesAgreementRed) {\n modalService.showMessage('payment-plans.message.obtain-signature-before-leaving');\n return false;\n }\n\n return true;\n }\n\n $scope.onAmountChange = function(item) {\n validateTotals($scope.paymentPlan.paymentItems);\n item.invalidAmount = $scope.invalidAmount;\n };\n\n $scope.validateInstallmentAmount = function(item) {\n item.amount = (item.amount || '').replace(/[^0-9.]/g, \"\");\n if(!item.amount || item.amount <= 0 || item.amount > $scope.paymentPlan.balanceDue) {\n modalService.showMessage('payment-plans.messages.invalid-installment-amount');\n return false;\n }\n\n return true;\n };\n\n $scope.onDateChange = function(date, installment) {\n if(!installment || !date) {\n return;\n }\n\n var items = $scope.paymentPlan.paymentItems;\n var filteredItems = filterOutTotalRow(items);\n\n filteredItems = _.map(filteredItems, function(item) {\n if(item && item.id === installment.id) {\n item.paymentDueDate = +moment(date).format('x');\n item.dateChanged = true;\n\n if(item.status === Enums.statuses.canceled.value) {\n if(moment(item.paymentDueDate).isAfter($scope.today, 'day') || moment(item.paymentDueDate).diff($scope.today, 'day') >= 0) {\n item.status = $scope.paymentPlan.status;\n }\n } else if(moment(item.paymentDueDate).isBefore($scope.today, 'day')) {\n item.status = Enums.statuses.canceled.value;\n }\n }\n\n item.paymentDueDate = moment(item.paymentDueDate).format('YYYY-MM-DD');\n item.reminderDate = calculateReminderDate(item, $scope.paymentPlan);\n\n if(item.reminderSent) {\n item.reminderDateLabel = $translate.instant('payment-plans.reminder-sent') + \" \" + $filter('date')(item.reminderSent, 'MMM d, yyyy');\n } else if(item.reminderDate) {\n item.reminderDateLabel = $translate.instant('payment-plans.reminder-date') + \" \" + $filter('date')(item.reminderDate, 'MMM d, yyyy');\n }\n\n if(moment(item.paymentDueDate).isBefore($scope.today, 'day')) {\n item.pastDue = moment(item.paymentDueDate).fromNow();\n } else {\n delete item.pastDue;\n }\n\n return item;\n });\n\n var sortedItems = _.sortBy(filteredItems, function(item) {\n return item.paymentDueDate;\n });\n\n _.each(sortedItems, function(item, i) {\n item.sequenceNumber = i + 1;\n });\n\n var totalRow = _(items).findWhere({ totalRow: true });\n\n if(totalRow) {\n var max = _.max(sortedItems, function(item) { return item.sequenceNumber; } );\n totalRow.sequenceNumber = (max && max.sequenceNumber || 0) + 1;\n sortedItems.push(totalRow);\n }\n\n $scope.paymentPlan.paymentItems = populateFieldsAndSortPlanItems(sortedItems);\n };\n\n var errorCache = {};\n $scope.buildInvalidAmountMessage = function () {\n var filter = _($scope.paymentPlan.paymentItems).filter(function (item) { return !item.totalRow && (item.status === 'A' || item.status === 'P'); });\n var installmentsTotal = +_.reduce(_(filter).pluck('amount'), function(memo, num) { return memo + Number(num); }, 0).toFixed(2);\n var balanceDue = $scope.paymentPlan.balanceDue;\n var diff = Math.abs(balanceDue - installmentsTotal);\n\n if(errorCache.diff === diff) {\n return errorCache.message;\n }\n\n errorCache.diff = diff;\n\n var translate = '';\n if(installmentsTotal > balanceDue) {\n translate = { key: 'payment-plans.invalid-remaining-amount-over', params: { diff: $filter('currency')(diff), totalAmount: $filter('currency')(balanceDue) }};\n } else if(installmentsTotal < balanceDue) {\n translate = { key: 'payment-plans.invalid-remaining-amount-under', params: { diff: $filter('currency')(diff), totalAmount: $filter('currency')(balanceDue) }};\n } else if(_(filter).filter(function (item) { return isInvalidAmount(item.amount); }).length > 0) {\n translate = { key: 'payment-plans.invalid-remaining-amount-zero', params: { zero: $filter('currency')(0) }};\n }\n\n if(!translate) {\n errorCache = {};\n return;\n }\n\n errorCache.message = $translate.instant(translate.key, translate.params);\n return errorCache.message;\n };\n\n /**\n * Helpers\n */\n\n $scope.receivedToolTip = function (item) {\n if(!item) {\n return '';\n }\n\n if(item.paymentReceivedDate && item.modifiedByUser) {\n return $translate.instant('common.modal.charged-by') + ' ' +\n item.modifiedByUser + ' ' + $translate.instant('common.on') + ' ' +\n $filter('date')(item.paymentReceivedDate, 'mediumDate') + ' ' + $translate.instant('transaction.at') + ' ' + $filter('date')(item.paymentReceivedDate, 'shortTime');\n } else if(item.paymentReceivedDate) {\n return $translate.instant('modal-service.text-message.received-on:') + ' ' +\n $filter('date')(item.paymentReceivedDate, 'mediumDate') + ' ' + $translate.instant('transaction.at') + ' ' + $filter('date')(item.paymentReceivedDate, 'shortTime');\n }\n\n return '';\n };\n\n function calculateReminderDate(installment, plan) {\n var days = plan.daysBeforeReminder ? plan.daysBeforeReminder : 3;\n var reminderDate = moment(installment.paymentDueDate).subtract('days', days).toDate();\n if(moment(reminderDate).isBefore($scope.today, 'day') || moment(reminderDate).diff($scope.today, 'day') <= 0) {\n return null;\n }\n\n return moment(reminderDate).format('YYYY-MM-DD');\n }\n\n function populateFieldsAndSortPlanItems(items) {\n _.each(items, function(item) {\n if(item.reminderDate) {\n item.reminderDateLabel = $translate.instant('payment-plans.reminder-date') + \" \" + $filter('date')(item.reminderDate, 'MMM d, yyyy');\n }\n\n if(moment(item.paymentDueDate).isBefore($scope.today, 'day')) {\n item.pastDue = moment(item.paymentDueDate).fromNow();\n } else {\n delete item.pastDue;\n }\n });\n\n return _.sortBy(items, function(item) {\n return item.sequenceNumber;\n });\n }\n\n function isInvalidAmount(amount) {\n return amount === 0 || amount === null || amount === undefined;\n }\n\n function filterOutTotalRow(items) {\n return _.filter(items, function(item) {\n return !item.totalRow;\n });\n }\n\n function validateTotals(items) {\n var filter = filterOutTotalRow(items);\n var invalidInstallment = _(filter).filter(function(item) { return isInvalidAmount(item.amount); }).length > 0;\n var installmentsTotal = +_.reduce(_(filter).pluck('amount'), function(memo, num) { return memo + Number(num); }, 0).toFixed(2);\n $scope.invalidAmount = installmentsTotal !== $scope.paymentPlan.totalAmount || invalidInstallment;\n\n if(!$scope.invalidAmount) {\n _(items).each(function (item) {\n item.invalidAmount = false;\n });\n }\n }\n\n function populateCardDetailsOnPaymentPlan($scope, result) {\n if(!result) {\n return;\n }\n\n if(!result.paymentProfileToken && !result.paymentProfileTokenProvider) {\n return;\n }\n\n if($scope.paymentPlan) {\n $scope.paymentPlan.paymentProfileToken = result.paymentProfileToken;\n $scope.paymentPlan.paymentProfileTokenProvider = result.paymentProfileTokenProvider;\n $scope.paymentPlan.paymentProfileMaskedAcctNumber = result.paymentProfileMaskedAcctNumber;\n $scope.paymentPlan.paymentProfileExpiration = result.paymentProfileExpiration;\n $scope.paymentPlan.paymentProfileCardType = result.paymentProfileCardType;\n $scope.paymentPlan.paymentProfileBillingName = result.paymentProfileBillingName;\n $scope.paymentPlan.paymentProfileAddedBy = result.paymentProfileAddedBy;\n }\n\n var cardDescription = '';\n\n if(result.paymentProfileCardType) {\n cardDescription += result.paymentProfileCardType.toUpperCase();\n }\n\n if(result.paymentProfileMaskedAcctNumber) {\n var mask = result.paymentProfileMaskedAcctNumber;\n cardDescription += ' **** ' + mask.replace(/\\D/g,'');\n }\n\n return cardDescription;\n }\n\n function updatePaymentPlanDescription(plan) {\n var filter = filterOutTotalRow(plan.paymentItems);\n var length = filter.length;\n\n var translatable = {\n key: 'payment-plans.plan-overview',\n params: {\n balanceDue: $filter('currency')(plan.installmentsTotalAmount),\n installments: length,\n startDate: $filter('date')(filter[0].paymentDueDate, 'mediumDate'),\n endDate: $filter('date')(filter[length - 1].paymentDueDate, 'mediumDate')\n }\n };\n\n plan.description = $translate.instant(translatable.key, translatable.params);\n }\n\n function buildTemplates(emailTemps, smsTemps) {\n $scope.emailTemplates = [];\n $scope.smsTemplates = [];\n\n var planMergeFields = _.union(_.where(MergeFields, { paymentPlanRequired: true }), _.where(MergeFields, { billingRequest: true }));\n\n if(emailTemps && emailTemps.length) {\n _.each(planMergeFields, function (mergeField) {\n _.each(emailTemps, function (template) {\n if (template && template.body) {\n if(includesValue(template.body, mergeField.key)) {\n if($scope.emailTemplates.indexOf(template) === -1) {\n $scope.emailTemplates.push(template);\n }\n }\n }\n });\n });\n }\n\n if(smsTemps && smsTemps.length) {\n _.each(planMergeFields, function (mergeField) {\n _.each(smsTemps, function (template) {\n if (template && template.body) {\n if(includesValue(template.body, mergeField.key)) {\n if($scope.smsTemplates.indexOf(template) === -1) {\n $scope.smsTemplates.push(template);\n }\n }\n }\n });\n });\n }\n }\n\n function includesValue(container, value) {\n var returnValue = false;\n var pos = container.indexOf(value);\n if(pos >= 0) {\n returnValue = true;\n }\n return returnValue;\n }\n\n function updateInstallments(status) {\n if(status === Enums.statuses.active.value) {\n _.each($scope.paymentPlan.paymentItems, function(item) {\n if(item.status === Enums.statuses.error.value) {\n item.failedAttempts = 0;\n }\n\n if(item.status === Enums.statuses.pending.value || item.status === Enums.statuses.error.value) {\n if(moment(item.paymentDueDate).isAfter($scope.today, 'day') || moment(item.paymentDueDate).diff($scope.today, 'day') >= 0) {\n item.status = Enums.statuses.active.value;\n } else {\n item.status = Enums.statuses.canceled.value;\n }\n }\n });\n } else if(status === Enums.statuses.pending.value) {\n _.each($scope.paymentPlan.paymentItems, function(item) {\n if(item.status === Enums.statuses.active.value || item.status === Enums.statuses.open.value) {\n item.status = status;\n }\n });\n }\n }\n\n var ignoreFirstLoad = true;\n $scope.$watch('model.statusType', function (newValue) {\n if(ignoreFirstLoad) {\n ignoreFirstLoad = false;\n return;\n }\n\n $scope.paymentPlan.status = newValue ? Enums.statuses.active.value : Enums.statuses.pending.value;\n updateInstallments($scope.paymentPlan.status);\n });\n });\n});\n\n","define('modules/transaction/search/search-ctrl',['./../module', 'angular', 'underscore'], function (module, angular, _) {\n 'use strict';\n\n module.controller('TransactionSearchCtrl', function ($scope, $state, $stateParams, $controller, $modal, $translate, $timeout, $locale, $filter,\n Enums, Transaction, posSettings, employees, alerts, modalService, ResourcesHelper, security,\n PurchaseOrderQuickSubmit, Contact, TransactionPaymentsModalService, CompanySetting, PaymentPlanModal) {\n\n $scope.locale = $locale;\n\n var setting = _(posSettings).findWhere({ type: 'POS_ENABLED_ZERO_DUE_DATE' });\n $scope.showZeroOutDate = setting && setting.value === 'Y';\n $scope.isUS = $locale.isUS;\n\n var headers = [\n {\n title: ''\n },\n {\n title: 'contact.purchase.headers.trx-#',\n sortField: 'trxNumber'\n },\n {\n title: 'reports.headers.order-date',\n sortField: 'orderDate',\n active: true\n },\n {\n title: 'common.contact',\n sortField: 'contactName'\n },\n {\n title: 'common.modal.event-date',\n sortField: 'eventDate'\n },\n {\n title: 'transaction.event-contact',\n sortField: 'eventContactName'\n },\n {\n title: 'common.associate',\n sortField: 'employeeName',\n hideOnTablets: true\n },\n {\n title: 'purchase-order.total',\n sortField: 'totalAmount'\n },\n {\n title: 'reports.headers.bal-due',\n sortField: 'balanceDue'\n },\n {\n title: 'transaction.percent-paid',\n sortField: 'percentPaid'\n }\n ];\n\n $scope.doIgnoreMemorizeSearch = !!_($stateParams).chain().values().compact().value().length;\n $scope.canCreatePlan = security.hasPlanPermission('paymentPlanAccess') && security.hasPermission('CREATE_PAYMENT_PLAN');\n\n angular.extend(this, $controller('SearchCtrl', { $scope: $scope, headers: headers }));\n\n $scope.enumStatuses = Enums.statuses;\n\n // default search values\n var typeId = $stateParams.typeId || Enums.trxTypes.all.value;\n var status = $stateParams.status || Enums.statuses.all.value;\n $scope.initialValues = { status: status, typeId: parseInt(typeId, 10), orderDate: $stateParams.trxStartDate, orderDateBefore: $stateParams.trxEndDate, eventDateBefore: $stateParams.eventDateBefore };\n $scope.formValues = angular.copy($scope.initialValues);\n $scope.memorizeSearch();\n\n //setup data for search form\n $scope.statuses = [\n Enums.statuses.all,\n Enums.statuses.pending,\n Enums.statuses.complete,\n Enums.statuses.voided\n ];\n\n $scope.types = [\n Enums.trxTypes.all,\n Enums.trxTypes.sale,\n Enums.trxTypes.special_order,\n Enums.trxTypes.return_trx,\n Enums.trxTypes.layaway\n ];\n\n //add the \"All\" options to the lists\n if(employees) {\n employees = ResourcesHelper.adaptList('employees', employees);\n employees.unshift({ fullName: $translate.instant('common.all') });\n }\n\n $scope.employees = employees;\n\n $scope.results = $scope.selectedResults = [];\n $scope.search = function () {\n Transaction.query($scope.buildFilterObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.addPaymentPlan = function() {\n PaymentPlanModal.openAddPaymentPlan();\n };\n\n $scope.bulkPrint = function () {\n if (!$scope.selectedResults.length) {\n return modalService.showMessage('transaction.message.select-at-least-one', 'common.message.wooops');\n }\n\n if ($scope.selectedResults.length > 10 && $scope.locale.isNF525) {\n return modalService.showMessage('transaction.message.print-maximum-10', 'common.message.wooops');\n }\n\n Transaction.bulkPrint(_($scope.selectedResults).pluck('id'));\n };\n\n $scope.exportToExcel = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('transaction.message.select-at-least-one', 'common.message.wooops');\n return false;\n }\n\n modalService.showConfirmation({\n key: 'transaction.message.would-like-export-to-excel?',\n params: { number: $scope.selectedResults.length }\n }, function () {\n Transaction.exportToExcel(_($scope.selectedResults).pluck('id'));\n });\n };\n\n $scope.changeEmployee = function (emp) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('transaction.message.select-at-least-one', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.transaction.successfully-assigned-to',\n params: {\n number: $scope.selectedResults.length,\n name: emp.fullName\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'transaction.message.would-like-assign-to?',\n params: {\n number: $scope.selectedResults.length,\n name: emp.fullName\n }\n }, function () {\n Transaction.bulkUpdateEmployee(emp.id, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.openCreatePOModal = function () {\n $modal\n .open({\n templateUrl: 'js/modules/transaction/search/create-purchase-orders-modal.tpl.html',\n controller: 'CreatePurchaseOrdersModalCtrl',\n resolve: {\n purchaseOrdersPreviews: function (PurchaseOrder) {\n return PurchaseOrder.previewForTrxs(_($scope.selectedResults).pluck('id'));\n },\n departments: function (Department) {\n return Department.query({sortField: 'name', filterObject: {status: Enums.statuses.active.value}});\n },\n posSettings: function (CompanySetting) {\n 'ngInject';\n return CompanySetting.getPOSSettings();\n }\n },\n windowClass: '__large',\n backdrop: 'static'\n })\n .result\n .then(function (transactionItems) {\n if (!transactionItems) {\n return;\n }\n\n PurchaseOrderQuickSubmit.openModal(_(transactionItems).pluck('purchaseOrderId'), true);\n });\n };\n\n $scope.openSplitPayments = function() {\n var selectedItems = _($scope.selectedResults).filter(function (item) {\n return item.balanceDue > 0;\n });\n\n if (!selectedItems.length) {\n return modalService.showMessage('contact.messages.select-transactions-with-positive-balance-due');\n }\n\n TransactionPaymentsModalService.openSplitPayments(selectedItems)\n .result\n .then(function(results) {\n $scope.search();\n if(results) {\n openSplitPaymentsSuccessModal(results);\n }\n });\n };\n\n var permissionsMap = {\n 1: 'SALES',\n 2: 'SPECIAL_ORDERS',\n 3: 'RETURNS',\n 4: 'LAYAWAY'\n };\n\n $scope.userHasPermissions = function (transaction) {\n return security.hasPermission(permissionsMap[transaction.typeId]);\n };\n\n /**\n * Methods\n */\n\n $scope.trxBalanceDueZeroDateTooltip = function(date) {\n if(!date) {\n return '';\n }\n\n return $translate.instant('common.balance-due-zero-date') + ': ' + $filter('date')(date, 'mediumDate');\n };\n\n function openSplitPaymentsSuccessModal(transactions) {\n $modal.open({\n templateUrl: 'js/modules/transaction/details/modals/split-payments-confirmation-modal.tpl.html',\n controller: 'SplitPaymentsConfirmationModalCtrl',\n resolve: {\n transactions: function () {\n return transactions;\n },\n posSettings: function (CompanySetting) {\n return CompanySetting.getPOSSettings();\n }\n }\n });\n }\n\n var typeState = {\n 1: 'transaction.sale',\n 2: 'transaction.specialOrder',\n 3: 'transaction.return',\n 4: 'transaction.layaway'\n };\n\n $scope.gotoTransaction = function (transaction) {\n $state.go(typeState[transaction.typeId], { id: transaction.id });\n };\n\n //functions to support the type ahead on Event Contact\n $scope.searchEventContacts = function (value) {\n var values = _(value.split(',')).map($.trim);\n\n var filterObject = {\n lastName: values[0],\n firstName: values[1]\n };\n\n return Contact.query({\n page: 1,\n size: 5,\n filterObject: filterObject\n }).then(function (eventContacts) {\n return eventContacts.result;\n });\n };\n\n $scope.chooseEventContact = function (eventContact) {\n $scope.eventContact = eventContact;\n $scope.formValues.eventContactId = eventContact.id;\n $scope.formValues.eventContactName = eventContact.firstName + ' ' + eventContact.lastName;\n $scope.eventContactName = $scope.formValues.eventContactName;\n };\n\n $scope.unselectEventContact = function () {\n $scope.eventContact = null;\n $scope.formValues.eventContactId = null;\n $scope.formValues.eventContactName = null;\n $scope.eventContactName = null;\n };\n\n /**\n * Init\n */\n\n $timeout(function () {\n if($scope.formValues.eventContactId) {\n Contact.getById($scope.formValues.eventContactId, $scope.chooseEventContact);\n }\n });\n });\n});\n\n","define('modules/vendor/search-ctrl',['./module', 'angular', 'jquery', 'underscore'], function (module, angular, $, _) {\n 'use strict';\n\n module.controller('VendorSearchCtrl', function($scope, $controller, $modal, $translate, Enums, Vendor, VendorModal, alerts, modalService, ImportService, security, AppLicense, clientPortalSettings) {\n var headers = [\n {\n title: 'department.code',\n sortField: 'code'\n },\n {\n title: 'common.name',\n sortField: 'name'\n },\n {\n title: 'common.modal.phone-number',\n sortField: 'phoneNumber'\n },\n {\n title: 'settings.company-profile.fax-number',\n sortField: 'faxNumber'\n },\n {\n title: 'marketplace.vendor.account',\n sortField: 'accountNumber'\n }\n ];\n\n $.extend(this, $controller('SearchCtrl', {$scope: $scope, headers:headers}));\n\n // default search values\n $scope.initialValues = { status:Enums.statuses.active.value, isVendorVisibleOnLookbook: Enums.showingOnLookbookFilterType.all.value };\n $scope.formValues = angular.copy($scope.initialValues);\n\n // determine visibility of client portal feature\n var portalFeature = _(clientPortalSettings).findWhere({ type: 'CP_FEATURE_LOOKBOOK' });\n var portalFeatureEnabled = portalFeature ? portalFeature.value === 'Y' : true;\n var userHasPermission = security.hasPermission(\"EDIT_SHOW_ON_LOOKBOOK\");\n var licenseHasPortalAccess = AppLicense.get()[\"clientPortalAccess\"];\n $scope.lookbookUpdatesAvailable = licenseHasPortalAccess && portalFeatureEnabled && userHasPermission;\n\n //setup data for search form\n $scope.statuses = [\n Enums.statuses.all,\n Enums.statuses.active,\n Enums.statuses.inactive\n ];\n\n $scope.showingOnLookbookFilterType = [\n Enums.showingOnLookbookFilterType.all,\n Enums.showingOnLookbookFilterType.showing,\n Enums.showingOnLookbookFilterType.not_showing\n ];\n\n $scope.results = $scope.selectedResults = [];\n $scope.search = function () {\n Vendor.query($scope.buildFilterObject(), function (resultModel) {\n $scope.updateResults(resultModel);\n });\n };\n\n $scope.remove = function(vendor) {\n modalService.showConfirmation('marketplace.vendor.would-like-to-delete?', function () {\n vendor.$remove(function () {\n $scope.onResultRemoved(vendor);\n });\n });\n };\n\n $scope.showVendorModal = function() {\n VendorModal.open().result.then(function() {\n $scope.search();\n });\n };\n\n $scope.exportToExcel = function () {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('marketplace.vendor.select-at-least-one', 'common.message.wooops');\n return false;\n }\n\n modalService.showConfirmation({\n key: 'marketplace.vendor.would-like-to-export-to-excel?',\n params: { number: $scope.selectedResults.length }\n }, function () {\n Vendor.exportToExcel(_($scope.selectedResults).pluck('id'));\n });\n };\n\n $scope.changeDisplayOnLookbook = function (value) {\n if (!$scope.selectedResults.length) {\n modalService.showMessage('marketplace.vendor.select-at-least-one', 'common.message.wooops');\n return false;\n }\n\n var successcb = function () {\n $scope.search();\n alerts.pushSuccess({\n key: 'alerts.vendor.successfully-changed-the-lookbook-visibility',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('settings.widgets.enabled') : $translate.instant('settings.widgets.disabled'))\n }\n });\n };\n\n modalService.showConfirmation({\n key: 'alerts.vendor.are-you-sure-you-would-like-to-change-the-lookbook-visibility?',\n params: {\n number: $scope.selectedResults.length,\n value: (value ? $translate.instant('settings.widgets.enabled') : $translate.instant('settings.widgets.disabled'))\n }\n }, function () {\n Vendor.bulkUpdateDisplayOnLookbook(value, _($scope.selectedResults).pluck('id'), successcb);\n });\n };\n\n $scope.import = function () {\n ImportService.openImportModal({\n modelDescription: $translate.instant('settings.role.vendors'),\n Resource: Vendor,\n allowUpdates: false,\n templateUrlCreate: '/assets/docs/import_templates/vendors.csv'\n });\n };\n });\n});\n\n","define('modules/wait-list/module',[\n 'angular',\n 'ui-router',\n '../common/permissions-service',\n '../resources/wait-list',\n '../resources/appointment-type',\n '../resources/appointment',\n '../resources/employee',\n '../resources/category',\n '../resources/fitting-room',\n './modal/wait-list-modal-service',\n './wait-list-helper-service'\n], function (angular) {\n 'use strict';\n\n return angular.module('bl.wait-list', [\n 'bl.wait-list.modal',\n 'bl.wait-list.helper',\n 'bl.resources.wait-list',\n 'bl.resources.appointment-type',\n 'bl.resources.appointment',\n 'bl.resources.employee',\n 'bl.resources.category',\n 'bl.resources.fitting-room',\n 'ui.router',\n 'bl.permissions'\n ])\n .config(function ($stateProvider, Permissions) {\n var template = function (name) {\n return 'js/modules/wait-list/' + name + '.tpl.html';\n };\n\n $stateProvider\n .state('wait-list', {\n url: '/wait-list?startDateTime?endDateTime?',\n permissions: Permissions.WAIT_LISTS,\n views: {\n 'main@': {\n controller: 'WaitListSearchCtrl',\n templateUrl: template('search/search')\n }\n }\n });\n });\n});\n\n","define('modules/wait-list/modal/edit-wait-list-ctrl',['./module', 'angular', 'underscore', 'moment'], function (module, angular, _, moment) {\n 'use strict';\n\n module.controller('EditWaitListModalCtrl', function ($scope, $rootScope, $locale, $window, $state, $modal, eventsBus, Appointment, CustomFieldValues,\n modalService, Enums, FullcalendarHelper, ResourcesHelper, appointmentTypes, budget_ranges, User, fittingRooms,\n employees, waitList, contact, calendarView, alerts, security, $timeout, Sms, EmailService, settings,\n CompanySetting, Contact, posSettings, WaitList, WaitListHelper) {\n // INIT MODELS\n $scope.contact = contact || {};\n $scope.waitList = waitList || {};\n $scope.fittingRooms = ResourcesHelper.adaptList('fittingRooms', fittingRooms, _(settings).findWhere({ type : 'AC_HIDE_UNASSIGNED_FITTING_ROOM' }));\n $scope.employees = ResourcesHelper.adaptList('employees', _(employees).where({ appointmentsAllowed: true }));\n $scope.budget_ranges = ResourcesHelper.adaptList('budgetRanges', budget_ranges);\n $scope.appointmentTypes = appointmentTypes;\n $scope.contact = contact;\n $scope.settings = settings;\n $scope.posSettings = posSettings;\n $scope.canBook = security.hasPermission('BOOK_WAIT_LISTS');\n $scope.canDelete = security.hasPermission('DELETE_WAIT_LISTS');\n $scope.state = {\n isContactChosen: false\n };\n\n $scope.custom = {\n readonly: true,\n showRows: true,\n show: false,\n fields: [],\n hide: !security.hasPermission('ADDITIONAL_DETAILS')\n };\n\n if(!$scope.custom.hide) {\n getCustomFieldValues($scope.contact);\n }\n\n\n var chosenContact;\n var timeFormat = $locale.DATETIME_FORMATS.timepickerFormat.moment;\n\n var setting = _(settings).findWhere({ type: CompanySetting.AC_WAIT_LIST_MAX_TIME_SLOTS });\n $scope.maxTimeWindows = setting && setting.value ? Number(setting.value) : 3;\n\n if (!waitList || waitList.status === Enums.statuses.pending.value) {\n $scope.appointmentTypes = _.where(appointmentTypes, {status: Enums.statuses.active.value});\n }\n\n $scope.waitList = waitList;\n $scope.waitList.contactName = waitList.contactName;\n $scope.waitList.associate = _($scope.employees).findWhere({ id: waitList.employeeId }) || $scope.employees[0];\n $scope.waitList.allDay = waitList.allDay;\n $scope.waitList.contactInterviewId = waitList.contactInterviewId;\n $scope.waitList.eventDate = waitList.eventDate;\n $scope.waitList.startDate = new Date(waitList.startDateTime);\n $scope.waitList.startTime = moment(waitList.startDateTime).format(timeFormat);\n $scope.waitList.endTime = moment(waitList.startDateTime + waitList.duration).format(timeFormat);\n $scope.waitList.type = _($scope.appointmentTypes).findWhere({ id: waitList.typeId }) || $scope.appointmentTypes[0];\n $scope.waitList.fittingRoom = _($scope.fittingRooms).findWhere({ id: waitList.fittingRoomId }) || $scope.fittingRooms[0];\n $scope.waitList.notes = waitList.notes;\n $scope.waitList.status = waitList.status;\n $scope.waitList.budget = waitList.budget;\n $scope.waitList.budgetRangeId = waitList.budgetRangeId;\n\n var range = _($scope.budget_ranges).findWhere({ id: $scope.waitList.budgetRangeId });\n\n if(range) {\n $scope.waitList.budgetRangeId = range.id;\n } else {\n setBudgetRangeValue($scope.waitList.budget, 'waitList');\n }\n\n $scope.waitList.dateTimes = formatDateTimesForWaitList(waitList);\n\n var findEmployeeById = function(id) {\n return _($scope.employees).findWhere({ id: id });\n };\n\n $scope.waitList.employee = findEmployeeById(waitList.employeeId) || $scope.employees[0];\n $scope.waitList.createdByUser = waitList.createdByUser;\n $scope.waitList.createdDate = waitList.createdDate;\n $scope.waitList.modifiedByUser = waitList.modifiedByUser;\n $scope.waitList.modifiedDate = waitList.modifiedDate;\n\n $scope.saveWaitList = function() {\n var waitListCopy = angular.extend(new WaitList(), $scope.waitList);\n\n if(!waitListCopy.bestPhoneNumber) {\n modalService.showMessage({ key: 'wait-list.phone-number-required-message', params: { type: waitListCopy.type.description }}, 'wait-list.phone-number-required');\n return;\n }\n\n // Reset start/end date times before building\n for(var j = 2; j < 6; j++) {\n waitListCopy['startDateTime' + j] = null;\n waitListCopy['endDateTime' + j] = null;\n }\n\n _.each(waitListCopy.dateTimes, function (times, i) {\n var startTime = FullcalendarHelper.timeToArray(times.startTime);\n var endTime = FullcalendarHelper.timeToArray(times.endTime);\n var startDateTime = (new Date(waitList.startDate)).setHours(startTime.hours, startTime.minutes, 0, 0);\n var endDateTime = (new Date(waitList.startDate)).setHours(endTime.hours, endTime.minutes, 0, 0);\n var duration = endDateTime - startDateTime;\n var twelveHours = 43200000;\n var fifteenMinutes = 900000;\n duration = Math.max(Math.min(duration, twelveHours), fifteenMinutes);\n\n var dates = FullcalendarHelper.generateDates(\n waitList.startDate,\n startDateTime,\n waitList.allDay,\n duration\n );\n\n waitListCopy['startDateTime' + (i === 0 ? '' : i + 1)] = dates.start.getTime();\n waitListCopy['endDateTime' + (i === 0 ? '' : i + 1)] = dates.end.getTime();\n });\n\n waitListCopy.notes = $scope.waitList.notes;\n waitListCopy.allDay = $scope.waitList.allDay;\n waitListCopy.typeId = $scope.waitList.type && $scope.waitList.type.id;\n waitListCopy.employeeId = $scope.waitList.employee && $scope.waitList.employee.id;\n waitListCopy.fittingRoomId = $scope.waitList.fittingRoom && $scope.waitList.fittingRoom.id;\n waitListCopy.status = $scope.waitList.status;\n waitListCopy.modifiedByUser = User.username;\n waitListCopy.modifiedDate = new Date();\n\n delete waitListCopy.dateTimes;\n\n var waitListSaved = waitListCopy.$update();\n\n return {\n // Templates that will be called after successful saving of wait list\n thenOpenContact: function () {\n waitListSaved.then(function (data) {\n $state.go('contact.personal_info', { id: data.contactId });\n });\n },\n thenClose: function () {\n waitListSaved.then(function (data) {\n $scope.$close(data);\n });\n },\n result: waitListSaved\n };\n };\n\n $scope.onConvert = function (e, contact, waitList) {\n if (e) {\n e.preventDefault();\n }\n\n WaitListHelper.createAppointment(contact, waitList, function () {\n $scope.$close();\n }, function () {\n console.error('Something went wrong.');\n });\n };\n\n $scope.onSendEmail = function ($event, c) {\n if($event) {\n $event.preventDefault();\n }\n\n var appointment = null;\n\n if($scope.waitList && $scope.waitList.appointmentId) {\n appointment = { id: $scope.waitList.appointmentId };\n }\n\n EmailService.showSendModal(c, appointment);\n $scope.$close();\n };\n\n $scope.onSendText = function ($event, c) {\n if($event) {\n $event.preventDefault();\n }\n\n var appointment = null;\n\n if($scope.waitList && $scope.waitList.appointmentId) {\n appointment = { id: $scope.waitList.appointmentId };\n }\n\n Sms.showSendModal(c, appointment);\n $scope.$close();\n };\n\n $scope.gotoContact = function () {\n $scope.$close();\n $state.go('contact.personal_info', {id: $scope.contact.id});\n };\n\n $scope.chooseContact = function (contact, assignEmployee) {\n assignEmployee = assignEmployee !== false;\n $scope.state.isContactChosen = true;\n $scope.contact = {};\n\n angular.extend($scope.contact, contact);\n\n $scope.waitList.contactId = contact.id;\n $scope.waitList.contactName = contact.firstName + ' ' + contact.lastName;\n chosenContact = angular.copy($scope.contact);\n\n if(assignEmployee) {\n $scope.waitList.employee = findEmployeeById(contact.employeeId);\n\n if(!$scope.waitList.employee) {\n $scope.waitList.employee = $scope.employees[0];\n }\n }\n };\n\n if(contact) {\n $scope.chooseContact(contact, false);\n }\n\n $scope.unselectContact = function () {\n chosenContact = undefined;\n delete $scope.contact;\n\n $scope.waitList.contactName = null;\n $scope.waitList.contactId = null;\n $scope.state.isContactChosen = false;\n };\n\n $scope.onDelete = function ($event) {\n if($event) {\n $event.preventDefault();\n }\n\n modalService.showConfirmation('wait-list.message.delete', function () {\n $scope.waitList.$remove(function () {\n $scope.$close();\n });\n });\n };\n\n $scope.openStoreCreditCardModal = function () {\n return $modal.open({\n templateUrl: 'js/modules/appointment-modal/store-credit-card-modal.tpl.html',\n controller: 'StoreCreditCardCtrl',\n resolve: {\n contact: function () {\n return $scope.contact;\n },\n saveCardToContact: function() {\n return true;\n },\n posSettings: function () {\n return CompanySetting.getPOSSettings();\n },\n previewMode: function () {\n return false;\n },\n options: function() {\n return {\n overrideCard: false,\n saveToContact: false\n };\n }\n },\n windowClass: '__medium',\n backdrop: 'static',\n keyboard: false\n });\n };\n\n $scope.removeTime = function (times) {\n $scope.waitList.dateTimes = _($scope.waitList.dateTimes).without(times);\n };\n\n $scope.addTime = function () {\n var length = $scope.waitList.dateTimes.length;\n var times = $scope.waitList.dateTimes[length - 1];\n\n if(times) {\n var endTime = FullcalendarHelper.timeToArray(times.endTime);\n\n $scope.waitList.dateTimes.push({\n id: times.id ? times.id + 1 : 1,\n startTime: moment((new Date($scope.waitList.startDate)).setHours(endTime.hours, endTime.minutes, 0, 0)).format(timeFormat),\n endTime: moment((new Date($scope.waitList.startDate)).setHours(endTime.hours, endTime.minutes, 0, 0) + $scope.waitList.type.duration).format(timeFormat)\n });\n } else {\n $scope.waitList.dateTimes.push({\n id: null,\n startTime: moment().format(timeFormat),\n endTime: moment(new Date($scope.waitList.startDate).valueOf() + $scope.waitList.type.duration).format(timeFormat)\n });\n }\n };\n\n function formatDateTimesForWaitList(wait) {\n return _.map(wait.dateTimes, function (times) {\n return {\n id: times.id,\n startTime: moment(times.startTime).format(timeFormat),\n endTime: moment(times.endTime).format(timeFormat),\n hasError: false\n };\n });\n }\n\n function getCustomFieldValues(contact) {\n if(contact && contact.id) {\n CustomFieldValues.listContactCustomFieldValues(contact.id, function(fields) {\n if($scope.contact && $scope.contact.id && _.findWhere(fields, { entityId: $scope.contact.id })) {\n $scope.custom.fields = fields;\n }\n }, function () {\n // do nothing...\n });\n }\n }\n\n $scope.$watch('waitList.status', function (status) {\n $scope.disabled = _.contains([Enums.statuses.booked.value, Enums.statuses.canceled.value], status);\n });\n\n function validateTimesForOverLapping(dateTimes) {\n if(dateTimes && dateTimes.length) {\n for(var i = 0; i < dateTimes.length; i++) {\n for(var j = i + 1; j < dateTimes.length; j++) {\n dateTimes[j].hasError = moment(dateTimes[j].startTime, timeFormat).isBefore(moment(dateTimes[i].endTime, timeFormat));\n }\n }\n }\n\n $scope.dateError = !!(_(dateTimes).findWhere({ hasError: true }));\n }\n\n $scope.onTimeChange = function () {\n validateTimesForOverLapping($scope.waitList.dateTimes);\n };\n\n $scope.onBudgetChange = _.debounce(function(budget) {\n setBudgetRangeValue(budget, 'waitList');\n }, 1000);\n\n function setBudgetRangeValue(budget, entityType) {\n var budgetRangeId;\n\n if (budget && $scope.budget_ranges.length) {\n for (var i = 0; i < $scope.budget_ranges.length; i++) {\n if (budget > $scope.budget_ranges[i].min && budget < $scope.budget_ranges[i].max) {\n budgetRangeId = $scope.budget_ranges[i].id;\n break;\n } else if (!$scope.budget_ranges[i].min && budget < $scope.budget_ranges[i].max) {\n budgetRangeId = $scope.budget_ranges[i].id;\n break;\n } else if (!$scope.budget_ranges[i].max && budget > $scope.budget_ranges[i].min) {\n budgetRangeId = $scope.budget_ranges[i].id;\n break;\n } else if (budget >= $scope.budget_ranges[i].min && budget <= $scope.budget_ranges[i].max) {\n budgetRangeId = $scope.budget_ranges[i].id;\n break;\n }\n }\n }\n\n if(budgetRangeId) {\n $scope[entityType].budgetRangeId = budgetRangeId;\n }\n }\n\n $scope.$watchCollection('waitList.dateTimes', function(dateTimes) {\n validateTimesForOverLapping(dateTimes);\n });\n });\n});\n\n"]}