2?ha.call(arguments,2):[];return M(a)&&!(a instanceof RegExp)?c.length?
+function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function jc(b,a){var c=a;/^\$+/.test(b)?c=p:oa(a)?c="$WINDOW":a&&ba===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function ca(b,a){return JSON.stringify(b,jc,a?" ":null)}function ob(b){return F(b)?JSON.parse(b):b}function Xa(b){b&&b.length!==0?(b=E(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;
+return b}function pa(b){b=y(b).clone();try{b.html("")}catch(a){}return y("").append(b).html().match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+E(b)})}function Ya(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=u(c[1])?decodeURIComponent(c[1]):!0)});return a}function pb(b){var a=[];m(b,function(b,d){a.push(Za(d,!0)+(b===!0?"":"="+Za(b,!0)))});return a.length?a.join("&"):""}function $a(b){return Za(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,
+"=").replace(/%2B/gi,"+")}function Za(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(a?null:/%20/g,"+")}function kc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(i,function(a){i[a]=!0;c(ba.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+
+a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function qb(b,a){b=y(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=rb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,i){a.$apply(function(){b.data("$injector",i);c(b)(a)})}]);return c}function ab(b,a){a=a||"_";return b.replace(lc,
+function(b,d){return(d?a:"")+b.toLowerCase()})}function qa(b,a,c){if(!b)throw new A("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&K(b)&&(b=b[b.length-1]);qa(M(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function mc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,
+d,e){return function(){b[e||"push"]([c,d,arguments]);return j}}if(!e)throw A("No module: "+d);var b=[],c=[],k=a("$injector","invoke"),j={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){c.push(a);
+return this}};g&&k(g);return j})}})}function sb(b){return b.replace(nc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(oc,"Moz$1")}function bb(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,k,j,l,n;b.length;){i=b.shift();f=0;for(h=i.length;f
"+b;a.removeChild(a.firstChild);cb(this,a.childNodes);this.remove()}else cb(this,b)}function db(b){return b.cloneNode(!0)}function sa(b){tb(b);for(var a=0,b=b.childNodes||[];a
+-1}function wb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})}function xb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function cb(b,a){if(a)for(var a=!a.nodeName&&u(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"),V.length>20&&c.warn("Cookie '"+a+"' possibly not set or overflowed because too many cookies were already set ("+
+V.length+" > 20 )")}else{if(h.cookie!==s){s=h.cookie;d=s.split("; ");V={};for(f=0;f0&&(V[unescape(e.substring(0,g))]=unescape(e.substring(g+1)))}return V}};f.defer=function(a,b){var c;o++;c=l(function(){delete r[c];e(a)},b||0);r[c]=!0;return c};f.defer.cancel=function(a){return r[a]?(delete r[a],n(a),e(D),!0):!1}}function xc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new wc(b,d,a,c)}]}function yc(){this.$get=function(){function b(b,
+d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw A("cacheId "+b+" taken");var i=0,f=x({},d,{id:b}),h={},k=d&&d.capacity||Number.MAX_VALUE,j={},l=null,n=null;return a[b]={put:function(a,b){var c=j[a]||(j[a]={key:a});e(c);t(b)||(a in h||i++,h[a]=b,i>k&&this.remove(n.key))},get:function(a){var b=j[a];if(b)return e(b),h[a]},remove:function(a){var b=j[a];if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete j[a];
+delete h[a];i--},removeAll:function(){h={};i=0;j={};l=n=null},destroy:function(){j=f=h=null;delete a[b]},info:function(){return x({},f,{size:i})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function zc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Cb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ";
+this.directive=function f(d,e){F(d)?(qa(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d],function(a){try{var f=b.invoke(a);if(M(f))f={compile:J(f)};else if(!f.compile&&f.link)f.compile=J(f.link);f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(g){c(g)}});return e}])),a[d].push(e)):m(d,nb(f));return this};this.$get=["$injector","$interpolate","$exceptionHandler",
+"$http","$templateCache","$parse","$controller","$rootScope",function(b,h,k,j,l,n,r,o){function w(a,b,c){a instanceof y||(a=y(a));m(a,function(b,c){b.nodeType==3&&(a[c]=y(b).wrap("").parent()[0])});var d=v(a,b,a,c);return function(b,c){qa(b,"scope");var e=c?ua.clone.call(a):a;e.data("$scope",b);q(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function q(a,b){try{a.addClass(b)}catch(c){}}function v(a,b,c,d){function e(a,c,d,g){for(var k,h,n,j,o,l=0,r=0,q=f.length;lC.priority)break;if(X=C.scope)N("isolated scope",B,C,s),I(X)&&(q(s,"ng-isolate-scope"),B=C),q(s,"ng-scope"),v=v||C;H=C.name;if(X=C.controller)u=u||{},N("'"+H+"' controller",u[H],C,s),u[H]=C;if(X=C.transclude)N("transclusion",D,C,s),D=C,o=C.priority,X=="element"?(W=y(b),s=c.$$element=y("<\!-- "+H+": "+c[H]+" --\>"),b=s[0],Ga(e,y(W[0]),b),t=w(W,d,o)):(W=y(db(b)).contents(),s.html(""),t=w(W,d));if(X=C.template)if(N("template",z,C,s),z=C,X=Ia(X),C.replace){W=y(""+Q(X)+"
").contents();
+b=W[0];if(W.length!=1||b.nodeType!==1)throw new A(g+X);Ga(e,s,b);H={$attr:{}};a=a.concat(Y(b,a.splice(E+1,a.length-(E+1)),H));L(c,H);G=a.length}else s.html(X);if(C.templateUrl)N("template",z,C,s),z=C,j=V(a.splice(E,a.length-E),j,s,c,e,C.replace,t),G=a.length;else if(C.compile)try{x=C.compile(s,c,t),M(x)?f(null,x):x&&f(x.pre,x.post)}catch(J){k(J,pa(s))}if(C.terminal)j.terminal=!0,o=Math.max(o,C.priority)}j.scope=v&&v.scope;j.transclude=D&&t;return j}function z(d,e,g,h){var j=!1;if(a.hasOwnProperty(e))for(var n,
+e=b.get(e+c),o=0,l=e.length;on.priority)&&n.restrict.indexOf(g)!=-1)d.push(n),j=!0}catch(r){k(r)}return j}function L(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){f=="class"?(q(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):f=="style"?e.attr("style",e.attr("style")+";"+b):f.charAt(0)!="$"&&!a.hasOwnProperty(f)&&(a[f]=b,d[f]=c[f])})}function V(a,b,c,d,e,
+f,k){var h=[],n,o,r=c[0],q=a.shift(),w=x({},q,{controller:null,templateUrl:null,transclude:null});c.html("");j.get(q.templateUrl,{cache:l}).success(function(j){var l,q,j=Ia(j);if(f){q=y(""+Q(j)+"
").contents();l=q[0];if(q.length!=1||l.nodeType!==1)throw new A(g+j);j={$attr:{}};Ga(e,c,l);Y(l,a,j);L(d,j)}else l=r,c.html(j);a.unshift(w);n=B(a,c,d,k);for(o=v(c.contents(),k);h.length;){var aa=h.pop(),j=h.pop();q=h.pop();var s=h.pop(),m=l;q!==r&&(m=db(l),Ga(j,y(q),m));n(function(){b(o,s,m,e,aa)},
+s,m,e,aa)}h=null}).error(function(a,b,c,d){throw A("Failed to load template: "+d.url);});return function(a,c,d,e,f){h?(h.push(c),h.push(d),h.push(e),h.push(f)):n(function(){b(o,c,d,e,f)},c,d,e,f)}}function s(a,b){return b.priority-a.priority}function N(a,b,c,d){if(b)throw A("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function H(a,b){var c=h(b,!0);c&&a.push({priority:0,compile:J(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);q(d.data("$binding",e),
+"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function W(a,b,c,d){var e=h(c,!0);e&&b.push({priority:100,compile:J(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=h(c[d],!0));c[d]=p;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function Ga(a,b,c){var d=b[0],e=d.parentNode,f,g;if(a){f=0;for(g=a.length;f0){var f=N[0],e=f.text;if(e==a||e==b||e==c||e==d||!a&&!b&&!c&&!d)return f}return!1}function f(b,c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),N.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function k(a,b){return function(c,d){return a(c,d,b)}}function j(a,b,c){return function(d,f){return b(d,f,a,c)}}function l(){for(var a=[];;)if(N.length>0&&!i("}",")",";","]")&&a.push(t()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,
+f=0;f","<=",">="))a=j(a,b.fn,q());return a}function v(){for(var a=m(),b;b=f("*","/","%");)a=j(a,b.fn,m());return a}function m(){var a;return f("+")?B():(a=f("-"))?j(V,a.fn,m()):(a=f("!"))?k(a.fn,m()):B()}function B(){var a;if(f("("))a=t(),h(")");else if(f("["))a=z();else if(f("{"))a=L();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=ea(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");
+return a}function z(){var a=[];if(g().text!="]"){do a.push(H());while(f(","))}h("]");return function(b,c){for(var d=[],f=0;f1;d++){var e=a.shift(),g=
+b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;i7),hasEvent:function(c){if(c=="input"&&$==9)return!1;if(t(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=J(T)}function Nb(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=E(Q(b.substr(0,e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Ob(b){var a=I(b)?b:p;return function(c){a||(a=Nb(b));return c?a[E(c)]||null:a}}function Pb(b,a,c){if(M(c))return c(b,a);m(c,
+function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){F(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ob(d,!0)));return d}],transformRequest:[function(a){return I(a)&&Ta.apply(a)!=="[object File]"?ca(a):a}],headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},
+e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,h,k,j){function l(a){function c(a){var b=x({},a,{data:Pb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:k.reject(b)}a.method=la(a.method);var e=a.transformRequest||d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=x({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[E(a.method)],a.headers),e=Pb(a.data,Ob(g),e),h;t(a.data)&&delete g["Content-Type"];
+h=n(a,e,g);h=h.then(c,c);m(w,function(a){h=a(h)});h.success=function(b){h.then(function(c){b(c.data,c.status,c.headers,a)});return h};h.error=function(b){h.then(null,function(c){b(c.data,c.status,c.headers,a)});return h};return h}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(w,[a,b,Nb(c)]):m.remove(w));f(b,a,c);h.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?j.resolve:j.reject)({data:a,status:c,headers:Ob(d),config:b})}function i(){var a=Va(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,
+1)}var j=k.defer(),n=j.promise,m,p,w=r(b.url,b.params);l.pendingRequests.push(b);n.then(i,i);b.cache&&b.method=="GET"&&(m=I(b.cache)?b.cache:o);if(m)if(p=m.get(w))if(p.then)return p.then(i,i),p;else K(p)?f(p[1],p[0],U(p[2])):f(p,200,{});else m.put(w,n);p||a(b.method,w,c,e,d,b.timeout,b.withCredentials);return n}function r(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==p||(I(a)&&(a=ca(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+
+c.join("&")}var o=c("$http"),w=[];m(e,function(a){w.push(F(a)?j.get(a):j.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(x(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]=function(b,c,d){return l(x(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Wc(){this.$get=["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0],
+a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;$?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c)}return function(e,h,k,j,l,n,r){function o(a,c,d,e){c=(h.match(Gb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(D)}b.$$incOutstandingRequestCount();h=h||b.url();
+if(E(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};i(h.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?o(j,200,d[p].data):o(j,-2);delete d[p]})}else{var q=new a;q.open(e,h,!0);m(l,function(a,b){a&&q.setRequestHeader(b,a)});var v;q.onreadystatechange=function(){q.readyState==4&&o(j,v||q.status,q.responseText,q.getAllResponseHeaders())};if(r)q.withCredentials=!0;q.send(k||"");n>0&&c(function(){v=-1;q.abort()},n)}}}function Zc(){this.$get=function(){return{id:"en-us",
+NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),
+SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var k=c.defer(),j=k.promise,l=u(h)&&!h,f=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),
+d(a)}l||b.$apply()},f),h=function(){delete g[j.$$timeoutId]};j.$$timeoutId=f;g[f]=k;j.then(h,h);return j}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Qb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Rb);a("date",Sb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number",
+Tb);a("orderBy",Ub);a("uppercase",ed)}function ad(){return function(b,a){if(!(b instanceof Array))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c=j+l)for(var k=i.length-j,n=0;n0||e>-c)e+=c;e===0&&c==-12&&(e=
+12);return jb(e,a,d)}}function Ma(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Sb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0;b[9]&&(g=G(b[9]+b[10]),i=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-i,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;return function(c,e){var g="",i=[],f,h,e=e||
+"mediumDate",e=b.DATETIME_FORMATS[e]||e;F(c)&&(c=fd.test(c)?G(c):a(c));wa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(h=gd.exec(e))?(i=i.concat(ha.call(h,1)),e=i.pop()):(i.push(e),e=null);m(i,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return ca(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?a=b.length:a<
+-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return R(function(c,d,e){c.$watch(e[b],function(b,e){if(a===!0||c.$index%2===a)e&&b!==e&&(I(e)&&!K(e)&&(e=Ua(e,function(a,b){if(a)return b})),d.removeClass(K(e)?e.join(" "):e)),I(b)&&!K(b)&&(b=Ua(b,
+function(a,b){if(a)return b})),b&&d.addClass(K(b)?b.join(" "):b)},!0)})}var E=function(b){return F(b)?b.toLowerCase():b},la=function(b){return F(b)?b.toUpperCase():b},A=T.Error,$=G((/msie (\d+)/.exec(E(navigator.userAgent))||[])[1]),y,ia,ha=[].slice,Sa=[].push,Ta=Object.prototype.toString,Zb=T.angular||(T.angular={}),ta,Db,Z=["0","0","0"];D.$inject=[];ma.$inject=[];Db=$<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?
+b.nodeName:b[0].nodeName};var lc=/[A-Z]/g,id={full:"1.0.2",major:1,minor:0,dot:2,codeName:"debilitating-awesomeness"},Ba=P.cache={},Aa=P.expando="ng-"+(new Date).getTime(),pc=1,$b=T.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},eb=T.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},nc=/([\:\-\_]+(.))/g,oc=/^moz([A-Z])/,ua=P.prototype={ready:function(b){function a(){c||
+(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);P(T).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?y(this[b]):y(this[this.length+b])},length:0,push:Sa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[E(b)]=b});var Ab={};m("input,select,option,textarea,button,form".split(","),function(b){Ab[la(b)]=!0});m({data:vb,inheritedData:Da,scope:function(b){return Da(b,
+"$scope")},controller:yb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=sb(a);if(u(c))b.style[a]=c;else{var d;$<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];$<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=E(a);if(Ea[d])if(u(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||D).specified?d:p;else if(u(c))b.setAttribute(a,
+c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(u(c))b[a]=c;else return b[a]},text:x($<9?function(b,a){if(b.nodeType==1){if(t(a))return b.innerText;b.innerText=a}else{if(t(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(t(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(t(a))return b.value;b.value=a},html:function(b,a){if(t(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&
+e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Yc=T.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw new A("This browser does not support XMLHttpRequest.");};Qb.$inject=["$provide"];Rb.$inject=["$locale"];Tb.$inject=["$locale"];
+var Wb=".",hd={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ma("Month"),MMM:Ma("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ma("Day"),EEE:Ma("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=a.getTimezoneOffset();return jb(a/60,2)+jb(Math.abs(a%60),2)}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
+fd=/^\d+$/;Sb.$inject=["$locale"];var dd=J(E),ed=J(la);Ub.$inject=["$parse"];var jd=J({restrict:"E",compile:function(a,c){c.href||c.$set("href","");return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a,c){var d=fa("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=fa("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,
+e,g){g.$observe(c,function(c){g.$set(a,c);$&&e.prop(a,c)})}}}});var Pa={$addControl:D,$removeControl:D,$setValidity:D,$setDirty:D};Xb.$inject=["$element","$attrs","$scope"];var Sa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:Xb,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",h);d.bind("$destroy",function(){c(function(){eb(d[0],"submit",h)},0,!1)})}var k=d.parent().controller("form"),
+j=i.name||i.ngForm;j&&(a[j]=f);k&&d.bind("$destroy",function(){k.$removeControl(f);j&&(a[j]=p);x(f,Pa)})}}}};return a?x(U(d),{restrict:"EAC"}):d}]},kd=Sa(),ld=Sa(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Ra,number:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);e.$parsers.push(function(a){var c=S(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""?
+null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return S(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!S(a)&&ah?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return S(a)||wa(a)?(e.$setValidity("number",
+!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){t(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&
+a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;F(g)||(g=!0);F(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:D,button:D,submit:D,reset:D},cc=["$browser","$sniffer",
+function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(bc[E(g.type)]||bc.text)(d,e,g,i,c,a)}}}],Oa="ng-valid",Na="ng-invalid",Qa="ng-pristine",Yb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+ab(c,"-"):"";e.removeClass((a?Na:Oa)+c).addClass((a?Oa:Na)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=
+!0;this.$invalid=!1;this.$name=d.name;var g=g(d.ngModel),f=g.assign;if(!f)throw A(Eb+d.ngModel+" ("+pa(e)+")");this.$render=D;var h=e.inheritedData("$formController")||Pa,k=0,j=this.$error={};e.addClass(Qa);i(!0);this.$setValidity=function(a,c){if(j[a]!==!c){if(c){if(j[a]&&k--,!k)i(!0),this.$valid=!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,k++;j[a]=!c;i(c,a);h.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=
+!1,e.removeClass(Qa).addClass(Yb),h.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,f(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(g,function(a){if(l.$modelValue!==a){var c=l.$formatters,d=c.length;for(l.$modelValue=a;d--;)a=c[d](a);if(l.$viewValue!==a)l.$viewValue=a,l.$render()}})}],qd=function(){return{require:["ngModel","^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],i=e[1]||Pa;i.$addControl(g);
+c.bind("$destroy",function(){i.$removeControl(g)})}}},rd=J({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(S(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel",
+link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return K(a)?a.join(", "):p})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=R(function(a,c,d){c.addClass("ng-binding").data("$binding",
+d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],yd=kb("",!0),zd=kb("Odd",0),Ad=kb("Even",1),Bd=R({compile:function(a,c){c.$set("ngCloak",p);
+a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),function(a){var c=fa("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(E(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Ed=R(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),
+Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,i){var f=i.ngInclude||i.src,h=i.onload||"",k=i.autoscroll;return function(g,i){var n=0,m,o=function(){m&&(m.$destroy(),m=null);i.html("")};g.$watch(f,function(f){var q=++n;f?a.get(f,{cache:c}).success(function(a){q===n&&(m&&m.$destroy(),m=g.$new(),i.html(a),e(i.contents())(m),u(k)&&(!k||g.$eval(k))&&d(),m.$emit("$includeContentLoaded"),g.$eval(h))}).error(function(){q===n&&
+o()}):o()})}}}}],Gd=R({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=R({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),k=i.offset||0,j=e.$eval(h),l={},n=c.startSymbol(),r=c.endSymbol();m(j,function(a,e){l[e]=c(a.replace(d,n+f+"-"+k+r))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(j[c]||(c=a.pluralCat(c-k)),l[c](e,g,!0))},function(a){g.text(a)})}}}],
+Jd=R({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,i){var f=i.ngRepeat,i=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),h,k,j;if(!i)throw A("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=i[1];h=i[2];i=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!i)throw A("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");k=i[3]||i[1];j=i[2];var l=new fb;a.$watch(function(a){var e,f,i=a.$eval(h),m=hc(i,
+!0),p,y=new fb,B,z,t,u,s=c;if(K(i))t=i||[];else{t=[];for(B in i)i.hasOwnProperty(B)&&B.charAt(0)!="$"&&t.push(B);t.sort()}e=0;for(f=t.length;ex;)u.pop().element.remove()}for(;v.length>w;)v.pop()[0].element.remove()}var i;if(!(i=w.match(d)))throw A("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+w+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),r=c(i[7]),v=[[{element:f,label:""}]];q&&(a(q)(e),q.removeClass("ng-scope"),
+q.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=r(e)||[],d={},h,i,j,m,q,s;if(o){i=[];m=0;for(s=v.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}');
diff --git a/app/assets/stylesheets/admin/openfoodweb.css.scss b/app/assets/stylesheets/admin/openfoodweb.css.scss
index 0f535b3f58..633b768371 100644
--- a/app/assets/stylesheets/admin/openfoodweb.css.scss
+++ b/app/assets/stylesheets/admin/openfoodweb.css.scss
@@ -12,3 +12,9 @@
float: none;
margin-bottom: 0;
}
+
+
+#new_enterprise_fee_set input.search {
+ float: right;
+ margin-bottom: 1em;
+}
diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb
new file mode 100644
index 0000000000..ca48ac7dc5
--- /dev/null
+++ b/app/controllers/admin/enterprise_fees_controller.rb
@@ -0,0 +1,37 @@
+module Admin
+ class EnterpriseFeesController < ResourceController
+ before_filter :load_enterprise_fee_set, :only => :index
+ before_filter :load_data
+
+ def index
+ respond_to do |format|
+ format.html
+ format.json { @presented_collection = @collection.each_with_index.map { |ef, i| EnterpriseFeePresenter.new(self, ef, i) } }
+ end
+ end
+
+ def bulk_update
+ @enterprise_fee_set = EnterpriseFeeSet.new(params[:enterprise_fee_set])
+ if @enterprise_fee_set.save
+ redirect_to main_app.admin_enterprise_fees_path, :notice => 'Your enterprise fees have been updated.'
+ else
+ render :index
+ end
+ end
+
+
+ private
+ def load_enterprise_fee_set
+ @enterprise_fee_set = EnterpriseFeeSet.new :collection => collection
+ end
+
+ def load_data
+ @calculators = EnterpriseFee.calculators.sort_by(&:name)
+ end
+
+ def collection
+ super.order('enterprise_id', 'fee_type', 'name') + (1..3).map { EnterpriseFee.new }
+ end
+
+ end
+end
diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb
index 08f6e2133f..a297eadc9c 100644
--- a/app/controllers/admin/enterprises_controller.rb
+++ b/app/controllers/admin/enterprises_controller.rb
@@ -16,7 +16,7 @@ module Admin
private
def load_enterprise_set
- @enterprise_set = EnterpriseSet.new :enterprises => collection
+ @enterprise_set = EnterpriseSet.new :collection => collection
end
def load_countries
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b5bcef894e..b3ef2dff28 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -14,4 +14,16 @@ class ApplicationController < ActionController::Base
@distributors = Enterprise.is_distributor.with_distributed_active_products_on_hand.by_name
end
+ # All render calls within the block will be performed with the specified format
+ # Useful for rendering html within a JSON response, particularly if the specified
+ # template or partial then goes on to render further partials without specifying
+ # their format.
+ def with_format(format, &block)
+ old_formats = formats
+ self.formats = [format]
+ block.call
+ self.formats = old_formats
+ nil
+ end
+
end
diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb
new file mode 100644
index 0000000000..5d913eb291
--- /dev/null
+++ b/app/helpers/angular_form_builder.rb
@@ -0,0 +1,45 @@
+class AngularFormBuilder < ActionView::Helpers::FormBuilder
+ def ng_fields_for(record_name, *args, &block)
+ raise "Nested ng_fields_for is not yet supported" if @fields_for_record_name.present?
+ @fields_for_record_name = record_name
+ block.call self
+ @fields_for_record_name = nil
+ end
+
+ def ng_text_field(method, options = {})
+ # @object_name --> "enterprise_fee_set"
+ # @fields_for_record_name --> :collection
+ # @object.send(@fields_for_record_name).first.class.to_s.underscore --> enterprise_fee
+
+ value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}"
+
+ @template.text_field_tag angular_name(method), value, :id => angular_id(method)
+ end
+
+ def ng_hidden_field(method, options = {})
+ value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}"
+
+ @template.hidden_field_tag angular_name(method), value, :id => angular_id(method)
+ end
+
+ def ng_select(method, choices, angular_field, options = {})
+ options.reverse_merge!({'id' => angular_id(method)})
+
+ @template.select_tag angular_name(method), @template.ng_options_for_select(choices, angular_field), options
+ end
+
+ def ng_collection_select(method, collection, value_method, text_method, angular_field, options = {})
+ options.reverse_merge!({'id' => angular_id(method)})
+
+ @template.select_tag angular_name(method), @template.ng_options_from_collection_for_select(collection, value_method, text_method, angular_field), options
+ end
+
+ private
+ def angular_name(method)
+ "#{@object_name}[#{@fields_for_record_name}_attributes][{{ $index }}][#{method}]"
+ end
+
+ def angular_id(method)
+ "#{@object_name}_#{@fields_for_record_name}_attributes_{{ $index }}_#{method}"
+ end
+end
diff --git a/app/helpers/angular_form_helper.rb b/app/helpers/angular_form_helper.rb
new file mode 100644
index 0000000000..a7fd6e0e28
--- /dev/null
+++ b/app/helpers/angular_form_helper.rb
@@ -0,0 +1,25 @@
+module AngularFormHelper
+ def ng_options_for_select(container, angular_field=nil)
+ return container if String === container
+
+ container.map do |element|
+ html_attributes = option_html_attributes(element)
+ text, value = option_text_and_value(element).map { |item| item.to_s }
+ selected_attribute = %Q( ng-selected="#{angular_field} == '#{value}'") if angular_field
+ %()
+ end.join("\n").html_safe
+ end
+
+ def ng_options_from_collection_for_select(collection, value_method, text_method, angular_field)
+ options = collection.map do |element|
+ [element.send(text_method), element.send(value_method)]
+ end
+
+ ng_options_for_select(options, angular_field)
+ end
+end
+
+
+class ActionView::Helpers::InstanceTag
+ include AngularFormHelper
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 4f41a1a0ed..794aee744b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -6,6 +6,13 @@ module ApplicationHelper
end
+ def ng_form_for(name, *args, &block)
+ options = args.extract_options!
+
+ form_for(name, *(args << options.merge(:builder => AngularFormBuilder)), &block)
+ end
+
+
# Pass URL helper calls on to spree where applicable so that we don't need to use
# spree.foo_path in any view rendered from non-spree-namespaced controllers.
def method_missing(method, *args, &block)
diff --git a/app/helpers/enterprise_fees_helper.rb b/app/helpers/enterprise_fees_helper.rb
new file mode 100644
index 0000000000..b7ec2b9018
--- /dev/null
+++ b/app/helpers/enterprise_fees_helper.rb
@@ -0,0 +1,5 @@
+module EnterpriseFeesHelper
+ def enterprise_fee_type_options
+ EnterpriseFee::FEE_TYPES.map { |f| [f.capitalize, f] }
+ end
+end
diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb
new file mode 100644
index 0000000000..8107f1fdf7
--- /dev/null
+++ b/app/models/enterprise_fee.rb
@@ -0,0 +1,13 @@
+class EnterpriseFee < ActiveRecord::Base
+ belongs_to :enterprise
+
+ calculated_adjustments
+ has_one :calculator, :as => :calculable, :dependent => :destroy, :class_name => 'Spree::Calculator'
+
+ attr_accessible :enterprise_id, :fee_type, :name, :calculator_type
+
+ FEE_TYPES = %w(packing transport admin sales)
+
+ validates_inclusion_of :fee_type, :in => FEE_TYPES
+ validates_presence_of :name
+end
diff --git a/app/models/enterprise_fee_set.rb b/app/models/enterprise_fee_set.rb
new file mode 100644
index 0000000000..4ae02f51d8
--- /dev/null
+++ b/app/models/enterprise_fee_set.rb
@@ -0,0 +1,7 @@
+class EnterpriseFeeSet < ModelSet
+ def initialize(attributes={})
+ super(EnterpriseFee, EnterpriseFee.all,
+ proc { |attrs| attrs[:enterprise_id].blank? },
+ attributes)
+ end
+end
diff --git a/app/models/enterprise_set.rb b/app/models/enterprise_set.rb
index b741167dfe..81591b6dad 100644
--- a/app/models/enterprise_set.rb
+++ b/app/models/enterprise_set.rb
@@ -1,34 +1,5 @@
-# Tableless model to handle updating multiple enterprises at once from a
-# single form. Used to update next_collection_at field for all distributors in
-# admin backend.
-class EnterpriseSet
- include ActiveModel::Conversion
- extend ActiveModel::Naming
-
- attr_accessor :enterprises
-
+class EnterpriseSet < ModelSet
def initialize(attributes={})
- @enterprises = Enterprise.all
-
- attributes.each do |name, value|
- send("#{name}=", value)
- end
+ super(Enterprise, Enterprise.all, nil, attributes)
end
-
- def enterprises_attributes=(attributes)
- attributes.each do |k, attributes|
- # attributes == {:id => 123, :next_collection_at => '...'}
- e = @enterprises.detect { |e| e.id.to_s == attributes[:id].to_s }
- e.assign_attributes(attributes.except(:id))
- end
- end
-
- def save
- enterprises.all?(&:save)
- end
-
- def persisted?
- false
- end
-
end
diff --git a/app/models/model_set.rb b/app/models/model_set.rb
new file mode 100644
index 0000000000..0452b0bafb
--- /dev/null
+++ b/app/models/model_set.rb
@@ -0,0 +1,40 @@
+# Tableless model to handle updating multiple models at once from a single form
+class ModelSet
+ include ActiveModel::Conversion
+ extend ActiveModel::Naming
+
+ attr_accessor :collection
+
+ def initialize(klass, collection, reject_if=nil, attributes={})
+ @klass, @collection, @reject_if = klass, collection, reject_if
+
+ attributes.each do |name, value|
+ send("#{name}=", value)
+ end
+ end
+
+ def collection_attributes=(attributes)
+ attributes.each do |k, attributes|
+ # attributes == {:id => 123, :next_collection_at => '...'}
+ e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? }
+ if e.nil?
+ @collection << @klass.new(attributes) unless @reject_if.andand.call(attributes)
+ else
+ e.assign_attributes(attributes.except(:id))
+ end
+ end
+ end
+
+ def errors
+ @collection.map { |ef| ef.errors.full_messages }.flatten
+ end
+
+ def save
+ collection.all?(&:save)
+ end
+
+ def persisted?
+ false
+ end
+
+end
diff --git a/app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb b/app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb
new file mode 100644
index 0000000000..a363758646
--- /dev/null
+++ b/app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb
@@ -0,0 +1,4 @@
+Deface::Override.new(:virtual_path => "spree/admin/configurations/index",
+ :name => "add_enterprise_fees_to_admin_configurations_menu",
+ :insert_bottom => "[data-hook='admin_configurations_menu']",
+ :partial => 'enterprise_fees/admin_configurations_menu')
diff --git a/app/overrides/set_auth_token_in_test.rb b/app/overrides/set_auth_token_in_test.rb
new file mode 100644
index 0000000000..eb1b7128b3
--- /dev/null
+++ b/app/overrides/set_auth_token_in_test.rb
@@ -0,0 +1,9 @@
+Deface::Override.new(:virtual_path => "spree/layouts/spree_application",
+ :insert_bottom => "[data-hook='inside_head']",
+ :partial => "layouts/auth_token_script",
+ :name => "auth_token_script")
+
+Deface::Override.new(:virtual_path => "spree/layouts/admin",
+ :insert_bottom => "[data-hook='admin_inside_head']",
+ :partial => "layouts/auth_token_script",
+ :name => "auth_token_script")
diff --git a/app/presenters/enterprise_fee_presenter.rb b/app/presenters/enterprise_fee_presenter.rb
new file mode 100644
index 0000000000..a0a6d8460a
--- /dev/null
+++ b/app/presenters/enterprise_fee_presenter.rb
@@ -0,0 +1,31 @@
+class EnterpriseFeePresenter
+ def initialize(controller, enterprise_fee, index)
+ @controller, @enterprise_fee, @index = controller, enterprise_fee, index
+ end
+
+ delegate :id, :enterprise_id, :fee_type, :name, :calculator_type, :to => :enterprise_fee
+
+ def enterprise_fee
+ @enterprise_fee
+ end
+
+
+ def enterprise_name
+ @enterprise_fee.enterprise.andand.name
+ end
+
+ def calculator_description
+ @enterprise_fee.calculator.andand.description
+ end
+
+ def calculator_settings
+ result = nil
+
+ @controller.send(:with_format, :html) do
+ result = @controller.render_to_string :partial => 'admin/enterprise_fees/calculator_settings', :locals => {:enterprise_fee => @enterprise_fee, :index => @index}
+ end
+
+ result.gsub('[0]', '[{{ $index }}]').gsub('_0_', '_{{ $index }}_')
+ end
+
+end
diff --git a/app/views/admin/enterprise_fees/_calculator_settings.html.haml b/app/views/admin/enterprise_fees/_calculator_settings.html.haml
new file mode 100644
index 0000000000..45e2a7c394
--- /dev/null
+++ b/app/views/admin/enterprise_fees/_calculator_settings.html.haml
@@ -0,0 +1,12 @@
+-# Render only the calculator settings and not the surrounding form
+- enterprise_fee_set = ModelSet.new(EnterpriseFee, EnterpriseFee.where(:id => enterprise_fee.id))
+- calculator_form_string = nil
+- form_for enterprise_fee_set, :as => :enterprise_fee_set, :url => '' do |form|
+ - form.fields_for :collection do |f|
+ - calculator_form_string = capture do
+ - if !enterprise_fee.new_record?
+ .calculator-settings
+ = f.fields_for :calculator do |calculator_form|
+ = preference_fields(enterprise_fee.calculator, calculator_form)
+
+= calculator_form_string
diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml
new file mode 100644
index 0000000000..c52afb060c
--- /dev/null
+++ b/app/views/admin/enterprise_fees/index.html.haml
@@ -0,0 +1,31 @@
+= ng_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'enterprise_fees', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form|
+ - if @enterprise_fee_set.errors.present?
+ %h2 Errors
+ %ul
+ - @enterprise_fee_set.errors.each do |error|
+ %li= error
+
+ %input.search{'ng-model' => 'query', 'placeholder' => 'Search'}
+
+ %table.index#listing_enterprise_fees
+ %thead
+ %tr
+ %th Enterprise
+ %th Fee Type
+ %th Name
+ %th Calculator
+ %th Calculator values
+ %th
+ %tbody
+ = enterprise_fee_set_form.ng_fields_for :collection do |f|
+ %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees | filter:query'}
+ %td
+ = f.ng_hidden_field :id
+ = f.ng_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true
+ %td= f.ng_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type'
+ %td= f.ng_text_field :name
+ %td= f.ng_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {'class' => 'calculator_type', 'ng-model' => 'calculatorType', 'spree-ensure-calculator-preferences-match-type' => "1"}
+ %td{'ng-bind-html-unsafe-compiled' => 'enterprise_fee.calculator_settings'}
+ %td{'spree-delete-resource' => "1"}
+
+ = enterprise_fee_set_form.submit 'Update'
diff --git a/app/views/admin/enterprise_fees/index.rep b/app/views/admin/enterprise_fees/index.rep
new file mode 100644
index 0000000000..c6bccc5a4e
--- /dev/null
+++ b/app/views/admin/enterprise_fees/index.rep
@@ -0,0 +1,10 @@
+r.list_of :enterprise_fees, @presented_collection do
+ r.element :id
+ r.element :enterprise_id
+ r.element :enterprise_name
+ r.element :fee_type
+ r.element :name
+ r.element :calculator_type
+ r.element :calculator_description
+ r.element :calculator_settings
+end
diff --git a/app/views/admin/enterprises/index.html.erb b/app/views/admin/enterprises/index.html.erb
index 75dad0f85f..c1e0869b86 100644
--- a/app/views/admin/enterprises/index.html.erb
+++ b/app/views/admin/enterprises/index.html.erb
@@ -19,7 +19,7 @@
- <%= f.fields_for :enterprises do |enterprise_form| %>
+ <%= f.fields_for :collection do |enterprise_form| %>
<% enterprise = enterprise_form.object %>
| <%= link_to enterprise.name, main_app.admin_enterprise_path(enterprise) %> |
diff --git a/app/views/enterprise_fees/_admin_configurations_menu.html.haml b/app/views/enterprise_fees/_admin_configurations_menu.html.haml
new file mode 100644
index 0000000000..13045a0fb2
--- /dev/null
+++ b/app/views/enterprise_fees/_admin_configurations_menu.html.haml
@@ -0,0 +1,3 @@
+%tr
+ %td= link_to "Enterprise Fees", main_app.admin_enterprise_fees_path
+ %td Create and manage fees charged by enterprises on order cycles
diff --git a/app/views/layouts/_auth_token_script.html.haml b/app/views/layouts/_auth_token_script.html.haml
new file mode 100644
index 0000000000..bc0f6c8dc4
--- /dev/null
+++ b/app/views/layouts/_auth_token_script.html.haml
@@ -0,0 +1,4 @@
+:javascript
+ if(typeof AUTH_TOKEN === 'undefined') {
+ var AUTH_TOKEN = '';
+ }
diff --git a/config/application.rb b/config/application.rb
index 0e77035c39..fa5411b9a6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -28,6 +28,13 @@ module Openfoodweb
initializer "spree.register.calculators" do |app|
app.config.spree.calculators.shipping_methods << OpenFoodWeb::Calculator::Itemwise
app.config.spree.calculators.shipping_methods << OpenFoodWeb::Calculator::Weight
+
+ app.config.spree.calculators.enterprise_fees = [Spree::Calculator::FlatPercentItemTotal,
+ Spree::Calculator::FlatRate,
+ Spree::Calculator::FlexiRate,
+ Spree::Calculator::PerItem,
+ Spree::Calculator::PriceSack,
+ OpenFoodWeb::Calculator::Weight]
end
@@ -36,7 +43,7 @@ module Openfoodweb
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- # config.autoload_paths += %W(#{config.root}/extras)
+ config.autoload_paths += %W(#{config.root}/app/presenters)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb
index 0a5d763b52..59982c1a9e 100644
--- a/config/initializers/spree.rb
+++ b/config/initializers/spree.rb
@@ -26,3 +26,17 @@ Spree.config do |config|
# Auto-capture payments. Without this option, payments must be manually captured in the paypal interface.
config.auto_capture = true
end
+
+
+# Add calculators category for enterprise fees
+module Spree
+ module Core
+ class Environment
+ class Calculators
+ include EnvironmentExtension
+
+ attr_accessor :enterprise_fees
+ end
+ end
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index 60454a3ae1..0ad57b0304 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -12,6 +12,9 @@ Openfoodweb::Application.routes.draw do
resources :enterprises do
post :bulk_update, :on => :collection, :as => :bulk_update
end
+ resources :enterprise_fees do
+ post :bulk_update, :on => :collection, :as => :bulk_update
+ end
end
# Mount Spree's routes
diff --git a/db/migrate/20121115010717_create_enterprise_fees.rb b/db/migrate/20121115010717_create_enterprise_fees.rb
new file mode 100644
index 0000000000..00c3057bfb
--- /dev/null
+++ b/db/migrate/20121115010717_create_enterprise_fees.rb
@@ -0,0 +1,11 @@
+class CreateEnterpriseFees < ActiveRecord::Migration
+ def change
+ create_table :enterprise_fees do |t|
+ t.references :enterprise
+ t.string :fee_type
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4bbccb5c05..f0e2589174 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20121031222403) do
+ActiveRecord::Schema.define(:version => 20121115010717) do
create_table "cms_blocks", :force => true do |t|
t.integer "page_id", :null => false
@@ -130,6 +130,14 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
add_index "cms_snippets", ["site_id", "identifier"], :name => "index_cms_snippets_on_site_id_and_identifier", :unique => true
add_index "cms_snippets", ["site_id", "position"], :name => "index_cms_snippets_on_site_id_and_position"
+ create_table "enterprise_fees", :force => true do |t|
+ t.integer "enterprise_id"
+ t.string "fee_type"
+ t.string "name"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
create_table "enterprises", :force => true do |t|
t.string "name"
t.string "description"
@@ -186,8 +194,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "alternative_phone"
t.integer "state_id"
t.integer "country_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "company"
end
@@ -196,12 +204,12 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
create_table "spree_adjustments", :force => true do |t|
t.integer "source_id"
- t.decimal "amount", :precision => 8, :scale => 2, :default => 0.0
+ t.decimal "amount", :precision => 8, :scale => 2
t.string "label"
t.string "source_type"
t.integer "adjustable_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.boolean "mandatory"
t.boolean "locked"
t.integer "originator_id"
@@ -233,15 +241,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "type"
t.integer "calculable_id", :null => false
t.string "calculable_type", :null => false
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_configurations", :force => true do |t|
t.string "name"
t.string "type", :limit => 50
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "spree_configurations", ["name", "type"], :name => "index_configurations_on_name_and_type"
@@ -279,8 +287,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "environment", :default => "development"
t.string "server", :default => "test"
t.boolean "test_mode", :default => true
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_inventory_units", :force => true do |t|
@@ -288,8 +296,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "state"
t.integer "variant_id"
t.integer "order_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "shipment_id"
t.integer "return_authorization_id"
end
@@ -303,8 +311,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "variant_id"
t.integer "quantity", :null => false
t.decimal "price", :precision => 8, :scale => 2, :null => false
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "max_quantity"
t.integer "shipping_method_id"
end
@@ -316,22 +324,22 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "source_id"
t.string "source_type"
t.text "details"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_mail_methods", :force => true do |t|
t.string "environment"
t.boolean "active", :default => true
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_option_types", :force => true do |t|
t.string "name", :limit => 100
t.string "presentation", :limit => 100
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "position", :default => 0, :null => false
end
@@ -345,8 +353,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "name"
t.string "presentation"
t.integer "option_type_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_option_values_variants", :id => false, :force => true do |t|
@@ -365,8 +373,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.decimal "adjustment_total", :precision => 8, :scale => 2, :default => 0.0, :null => false
t.decimal "credit_total", :precision => 8, :scale => 2, :default => 0.0, :null => false
t.integer "user_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.datetime "completed_at"
t.integer "bill_address_id"
t.integer "ship_address_id"
@@ -387,8 +395,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.text "description"
t.boolean "active", :default => true
t.string "environment", :default => "development"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.datetime "deleted_at"
t.string "display_on"
end
@@ -396,8 +404,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
create_table "spree_payments", :force => true do |t|
t.decimal "amount", :precision => 8, :scale => 2, :default => 0.0, :null => false
t.integer "order_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "source_id"
t.string "source_type"
t.integer "payment_method_id"
@@ -426,8 +434,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "owner_id"
t.string "owner_type"
t.text "value"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "key"
t.string "value_type"
end
@@ -452,16 +460,16 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "position"
t.integer "product_id"
t.integer "option_type_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_product_properties", :force => true do |t|
t.string "value"
t.integer "product_id"
t.integer "property_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "spree_product_properties", ["product_id"], :name => "index_product_properties_on_product_id"
@@ -485,8 +493,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "meta_keywords"
t.integer "tax_category_id"
t.integer "shipping_category_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "count_on_hand", :default => 0, :null => false
t.integer "supplier_id"
t.boolean "group_buy"
@@ -531,8 +539,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "user_id"
t.integer "product_group_id"
t.string "type"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "spree_promotion_rules", ["product_group_id"], :name => "index_promotion_rules_on_product_group_id"
@@ -549,8 +557,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
create_table "spree_properties", :force => true do |t|
t.string "name"
t.string "presentation", :null => false
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_properties_prototypes", :id => false, :force => true do |t|
@@ -560,8 +568,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
create_table "spree_prototypes", :force => true do |t|
t.string "name"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_return_authorizations", :force => true do |t|
@@ -570,8 +578,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.decimal "amount", :precision => 8, :scale => 2, :default => 0.0, :null => false
t.integer "order_id"
t.text "reason"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_roles", :force => true do |t|
@@ -594,8 +602,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "order_id"
t.integer "shipping_method_id"
t.integer "address_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "state"
end
@@ -603,15 +611,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
create_table "spree_shipping_categories", :force => true do |t|
t.string "name"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_shipping_methods", :force => true do |t|
t.string "name"
t.integer "zone_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "display_on"
t.integer "shipping_category_id"
t.boolean "match_none"
@@ -627,8 +635,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "transaction_id"
t.integer "customer_id"
t.string "payment_type"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_state_changes", :force => true do |t|
@@ -636,8 +644,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "previous_state"
t.integer "stateful_id"
t.integer "user_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "stateful_type"
t.string "next_state"
end
@@ -651,8 +659,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
create_table "spree_tax_categories", :force => true do |t|
t.string "name"
t.string "description"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.boolean "is_default", :default => false
t.datetime "deleted_at"
end
@@ -661,15 +669,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.decimal "amount", :precision => 8, :scale => 5
t.integer "zone_id"
t.integer "tax_category_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.boolean "included_in_price", :default => false
end
create_table "spree_taxonomies", :force => true do |t|
t.string "name", :null => false
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_taxons", :force => true do |t|
@@ -678,8 +686,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "name", :null => false
t.string "permalink"
t.integer "taxonomy_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.integer "lft"
t.integer "rgt"
t.string "icon_file_name"
@@ -697,8 +705,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "permissable_id"
t.string "permissable_type"
t.string "token"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "spree_tokenized_permissions", ["permissable_id", "permissable_type"], :name => "index_tokenized_name_and_type"
@@ -707,8 +715,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "environment"
t.string "analytics_id"
t.boolean "active", :default => true
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_users", :force => true do |t|
@@ -729,8 +737,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.string "login"
t.integer "ship_address_id"
t.integer "bill_address_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.string "authentication_token"
t.string "unlock_token"
t.datetime "locked_at"
@@ -753,7 +761,7 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.boolean "is_master", :default => false
t.integer "product_id"
t.integer "count_on_hand", :default => 0, :null => false
- t.decimal "cost_price", :precision => 8, :scale => 2, :default => 0.0
+ t.decimal "cost_price", :precision => 8, :scale => 2
t.integer "position"
end
@@ -763,15 +771,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do
t.integer "zoneable_id"
t.string "zoneable_type"
t.integer "zone_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "spree_zones", :force => true do |t|
t.string "name"
t.string "description"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.boolean "default_tax", :default => false
t.integer "zone_members_count", :default => 0
end
diff --git a/spec/factories.rb b/spec/factories.rb
index 8348f98afd..b643b92f1d 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -20,6 +20,15 @@ FactoryGirl.define do
is_distributor true
end
+ factory :enterprise_fee, :class => EnterpriseFee do
+ enterprise { Enterprise.first || FactoryGirl.create(:supplier_enterprise) }
+ fee_type 'packing'
+ name '$0.50 / kg'
+ calculator { FactoryGirl.build(:weight_calculator) }
+
+ after_create { |ef| ef.calculator.save! }
+ end
+
factory :product_distribution, :class => ProductDistribution do
product { |pd| Spree::Product.first || FactoryGirl.create(:product) }
distributor { |pd| Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise) }
@@ -33,6 +42,12 @@ FactoryGirl.define do
factory :itemwise_calculator, :class => OpenFoodWeb::Calculator::Itemwise do
end
+
+ factory :weight_calculator, :class => OpenFoodWeb::Calculator::Weight do
+ after_build { |c| c.set_preference(:per_kg, 0.5) }
+ after_create { |c| c.set_preference(:per_kg, 0.5); c.save! }
+ end
+
end
diff --git a/spec/models/enterprise_fee_spec.rb b/spec/models/enterprise_fee_spec.rb
new file mode 100644
index 0000000000..1616b0decd
--- /dev/null
+++ b/spec/models/enterprise_fee_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe EnterpriseFee do
+ describe "associations" do
+ it { should belong_to(:enterprise) }
+ end
+
+ describe "validations" do
+ it { should validate_presence_of(:name) }
+ end
+end
diff --git a/spec/requests/admin/enterprise_fees_spec.rb b/spec/requests/admin/enterprise_fees_spec.rb
new file mode 100644
index 0000000000..8af01b149f
--- /dev/null
+++ b/spec/requests/admin/enterprise_fees_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+feature %q{
+ As an administrator
+ I want to manage enterprise fees
+}, js: true do
+ include AuthenticationWorkflow
+ include WebHelper
+
+ scenario "listing enterprise fees" do
+ fee = create(:enterprise_fee)
+
+ login_to_admin_section
+ click_link 'Configuration'
+ click_link 'Enterprise Fees'
+
+ page.should have_selector "option[selected]", text: fee.enterprise.name
+ page.should have_selector "option[selected]", text: 'Packing'
+ page.should have_selector "input[value='$0.50 / kg']"
+ page.should have_selector "option[selected]", text: 'Weight (per kg)'
+ page.should have_selector "input[value='0.5']"
+ end
+
+ scenario "creating an enterprise fee" do
+ # Given an enterprise
+ e = create(:supplier_enterprise, name: 'Feedme')
+
+ # When I go to the enterprise fees page
+ login_to_admin_section
+ click_link 'Configuration'
+ click_link 'Enterprise Fees'
+
+ # And I fill in the fields for a new enterprise fee and click update
+ select 'Feedme', from: 'enterprise_fee_set_collection_attributes_0_enterprise_id'
+ select 'Admin', from: 'enterprise_fee_set_collection_attributes_0_fee_type'
+ fill_in 'enterprise_fee_set_collection_attributes_0_name', with: 'Hello!'
+ select 'Flat Percent', from: 'enterprise_fee_set_collection_attributes_0_calculator_type'
+ click_button 'Update'
+
+ # Then I should see my fee and fields for the calculator
+ page.should have_content "Your enterprise fees have been updated."
+ page.should have_selector "input[value='Hello!']"
+
+ # When I fill in the calculator fields and click update
+ fill_in 'enterprise_fee_set_collection_attributes_0_calculator_attributes_preferred_flat_percent', with: '12.34'
+ click_button 'Update'
+
+ # Then I should see the correct values in my calculator fields
+ page.should have_selector "#enterprise_fee_set_collection_attributes_0_calculator_attributes_preferred_flat_percent[value='12.34']"
+ end
+
+ scenario "editing an enterprise fee" do
+ # Given an enterprise fee
+ fee = create(:enterprise_fee)
+ create(:enterprise, name: 'Foo')
+
+ # When I go to the enterprise fees page
+ login_to_admin_section
+ click_link 'Configuration'
+ click_link 'Enterprise Fees'
+
+ # And I update the fields for the enterprise fee and click update
+ select 'Foo', from: 'enterprise_fee_set_collection_attributes_0_enterprise_id'
+ select 'Admin', from: 'enterprise_fee_set_collection_attributes_0_fee_type'
+ fill_in 'enterprise_fee_set_collection_attributes_0_name', with: 'Greetings!'
+ select 'Flat Percent', from: 'enterprise_fee_set_collection_attributes_0_calculator_type'
+ click_button 'Update'
+
+ # Then I should see the updated fields for my fee
+ page.should have_selector "option[selected]", text: 'Foo'
+ page.should have_selector "option[selected]", text: 'Admin'
+ page.should have_selector "input[value='Greetings!']"
+ page.should have_selector "option[selected]", text: 'Flat Percent'
+ end
+
+ scenario "deleting an enterprise fee" do
+ # Given an enterprise fee
+ fee = create(:enterprise_fee)
+
+ # When I go to the enterprise fees page
+ login_to_admin_section
+ click_link 'Configuration'
+ click_link 'Enterprise Fees'
+
+ # And I click delete
+ click_link 'Delete'
+ page.driver.wait_until(page.driver.browser.switch_to.alert.accept)
+
+ # Then my enterprise fee should have been deleted
+ visit admin_enterprise_fees_path
+ page.should_not have_selector "input[value='#{fee.name}']"
+ end
+
+end
diff --git a/spec/requests/admin/enterprises_spec.rb b/spec/requests/admin/enterprises_spec.rb
index dae560df69..5c4b433852 100644
--- a/spec/requests/admin/enterprises_spec.rb
+++ b/spec/requests/admin/enterprises_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
feature %q{
As an administrator
- I want manage enterprises
+ I want to manage enterprises
} do
include AuthenticationWorkflow
include WebHelper
@@ -109,9 +109,9 @@ feature %q{
click_link 'Enterprises'
# And I fill in some new collection times and save them
- fill_in 'enterprise_set_enterprises_attributes_0_next_collection_at', :with => 'One'
- fill_in 'enterprise_set_enterprises_attributes_1_next_collection_at', :with => 'Two'
- fill_in 'enterprise_set_enterprises_attributes_2_next_collection_at', :with => 'Three'
+ fill_in 'enterprise_set_collection_attributes_0_next_collection_at', :with => 'One'
+ fill_in 'enterprise_set_collection_attributes_1_next_collection_at', :with => 'Two'
+ fill_in 'enterprise_set_collection_attributes_2_next_collection_at', :with => 'Three'
click_button 'Update'
# Then my times should have been saved