From 4efe3fdf88ef846909360436a9158399f446788a Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 29 Aug 2014 18:01:54 +1000 Subject: [PATCH 01/68] Remove old map icons --- app/assets/images/map-icon-both.svg | 30 ------------------------ app/assets/images/map-icon-hub.svg | 28 ---------------------- app/assets/images/map-icon-producer.svg | 31 ------------------------- 3 files changed, 89 deletions(-) delete mode 100644 app/assets/images/map-icon-both.svg delete mode 100644 app/assets/images/map-icon-hub.svg delete mode 100644 app/assets/images/map-icon-producer.svg diff --git a/app/assets/images/map-icon-both.svg b/app/assets/images/map-icon-both.svg deleted file mode 100644 index a51867f841..0000000000 --- a/app/assets/images/map-icon-both.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/assets/images/map-icon-hub.svg b/app/assets/images/map-icon-hub.svg deleted file mode 100644 index fcd6a4913e..0000000000 --- a/app/assets/images/map-icon-hub.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/assets/images/map-icon-producer.svg b/app/assets/images/map-icon-producer.svg deleted file mode 100644 index 50ec792e91..0000000000 --- a/app/assets/images/map-icon-producer.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - From a31e8ff82f19fee91d0ca06ec55caf1e04ca503c Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 29 Aug 2014 18:03:15 +1000 Subject: [PATCH 02/68] New map icons --- app/assets/images/map_001-producer-only.svg | 63 +++++++++++++++++ .../images/map_002-producer-only-profile.svg | 65 ++++++++++++++++++ app/assets/images/map_003-producer-shop.svg | 63 +++++++++++++++++ .../images/map_004-producer-shop-profile.svg | 65 ++++++++++++++++++ app/assets/images/map_005-hub.svg | 46 +++++++++++++ app/assets/images/map_006-hub-profile.svg | 48 +++++++++++++ app/assets/images/map_007-shop.svg | 47 +++++++++++++ app/assets/images/map_008-shop-profile.svg | 68 +++++++++++++++++++ 8 files changed, 465 insertions(+) create mode 100644 app/assets/images/map_001-producer-only.svg create mode 100644 app/assets/images/map_002-producer-only-profile.svg create mode 100644 app/assets/images/map_003-producer-shop.svg create mode 100644 app/assets/images/map_004-producer-shop-profile.svg create mode 100644 app/assets/images/map_005-hub.svg create mode 100644 app/assets/images/map_006-hub-profile.svg create mode 100644 app/assets/images/map_007-shop.svg create mode 100644 app/assets/images/map_008-shop-profile.svg diff --git a/app/assets/images/map_001-producer-only.svg b/app/assets/images/map_001-producer-only.svg new file mode 100644 index 0000000000..b34c01f1ed --- /dev/null +++ b/app/assets/images/map_001-producer-only.svg @@ -0,0 +1,63 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_002-producer-only-profile.svg b/app/assets/images/map_002-producer-only-profile.svg new file mode 100644 index 0000000000..0522f91257 --- /dev/null +++ b/app/assets/images/map_002-producer-only-profile.svg @@ -0,0 +1,65 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_003-producer-shop.svg b/app/assets/images/map_003-producer-shop.svg new file mode 100644 index 0000000000..84c324ac4e --- /dev/null +++ b/app/assets/images/map_003-producer-shop.svg @@ -0,0 +1,63 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_004-producer-shop-profile.svg b/app/assets/images/map_004-producer-shop-profile.svg new file mode 100644 index 0000000000..4d35236813 --- /dev/null +++ b/app/assets/images/map_004-producer-shop-profile.svg @@ -0,0 +1,65 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_005-hub.svg b/app/assets/images/map_005-hub.svg new file mode 100644 index 0000000000..be2d292c20 --- /dev/null +++ b/app/assets/images/map_005-hub.svg @@ -0,0 +1,46 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_006-hub-profile.svg b/app/assets/images/map_006-hub-profile.svg new file mode 100644 index 0000000000..b284ea34f3 --- /dev/null +++ b/app/assets/images/map_006-hub-profile.svg @@ -0,0 +1,48 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_007-shop.svg b/app/assets/images/map_007-shop.svg new file mode 100644 index 0000000000..e2ebe118ce --- /dev/null +++ b/app/assets/images/map_007-shop.svg @@ -0,0 +1,47 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + diff --git a/app/assets/images/map_008-shop-profile.svg b/app/assets/images/map_008-shop-profile.svg new file mode 100644 index 0000000000..635602b7bf --- /dev/null +++ b/app/assets/images/map_008-shop-profile.svg @@ -0,0 +1,68 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ae442769b5be0cec898c4390c4b4dd73f7791f71 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 29 Aug 2014 18:03:47 +1000 Subject: [PATCH 03/68] New version of icon font for OFN system --- app/assets/stylesheets/darkswarm/style.css | 48 +++++++- public/OFN.eot | Bin 33328 -> 41972 bytes public/OFN.svg | 130 +++++++++++---------- public/OFN.ttf | Bin 33180 -> 41824 bytes public/OFN.woff | Bin 20408 -> 26600 bytes 5 files changed, 113 insertions(+), 65 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/style.css b/app/assets/stylesheets/darkswarm/style.css index 843d362510..3d2a98276b 100755 --- a/app/assets/stylesheets/darkswarm/style.css +++ b/app/assets/stylesheets/darkswarm/style.css @@ -1,10 +1,10 @@ @font-face { font-family: 'OFN'; - src:url('/OFN.eot?-4w617y'); - src:url('/OFN.eot?#iefix-4w617y') format('embedded-opentype'), - url('/OFN.woff?-4w617y') format('woff'), - url('/OFN.ttf?-4w617y') format('truetype'), - url('/OFN.svg?-4w617y#OFN') format('svg'); + src:url('/OFN.eot?eslsji'); + src:url('/OFN.eot?#iefixeslsji') format('embedded-opentype'), + url('/OFN.woff?eslsji') format('woff'), + url('/OFN.ttf?eslsji') format('truetype'), + url('/OFN.svg?eslsji#OFN') format('svg'); font-weight: normal; font-style: normal; } @@ -194,6 +194,42 @@ .ofn-i_057-expand:before { content: "\e638"; } -.ofn-i-058-graph:before { +.ofn-i_058-graph:before { content: "\e639"; } +.ofn-i_059-producer:before { + content: "\e63a"; +} +.ofn-i_060-producer-reversed:before { + content: "\e63b"; +} +.ofn-i_061-producer-map:before { + content: "\e63c"; +} +.ofn-i_062-producer-map-reversed:before { + content: "\e63d"; +} +.ofn-i_063-hub:before { + content: "\e63e"; +} +.ofn-i_064-hub-reversed:before { + content: "\e63f"; +} +.ofn-i_065-hub-map:before { + content: "\e640"; +} +.ofn-i_066-hub-map-reversed:before { + content: "\e641"; +} +.ofn-i_067-shop:before { + content: "\e642"; +} +.ofn-i_068-shop-reversed:before { + content: "\e643"; +} +.ofn-i_069-shop-map:before { + content: "\e644"; +} +.ofn-i_070-shop-map-reversed:before { + content: "\e645"; +} diff --git a/public/OFN.eot b/public/OFN.eot index 5839e08963919a65421fff92487b00d200abbd8c..0329e7ac1e04dc7335e89aaf5a0477a63ed085bf 100755 GIT binary patch literal 41972 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 literal 33328 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 nwq|Z%Generated by IcoMoon - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/OFN.ttf b/public/OFN.ttf index cac7377e06ccb11f1d2ba5db16a4d9462a2168c0..51423d386053203737e1dd8ea131f5a8d33244d2 100755 GIT binary patch literal 41824 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 literal 33180 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 IH|PHRZ@{8$tN;K2 diff --git a/public/OFN.woff b/public/OFN.woff index 4ccf9c61920df1d4496474a980dc7b921001a174..e58fda0e071f16a6a47ab04a92f7ad342fc91315 100755 GIT binary patch delta 7901 zcmb7J3z!tumF^l+a1}+3lp!QWk%xZ15X85jBA`5!w+R6RWF9A_af>k+KJl{X~syi(r2 z$K%<)C$Q#;(8{&5=XyM)rRcDgAMA<$eq?*qwrbovkLwlun7aNOofX?^aj)!JT(hy` z+8t%v@LbsxT;ICR^fUEKR$6~$-*5~bxpWe=kW-)@x!=RxMFVAwk8Z% z-ic1*-DBA*&z@~nWwlWpdgLbUV%Np2c%#QtvhEsQZnzGm z=L!_t%RcE@ZY>l7Q-u43hlR(4MZ!|ySz)d41!1%Bf>0q;7QZ6Q78chpzW?4Qc(ITd zPx4|BFWiPDsLi{F7jt+qmlyZ)VjeHn~U#Uyn1oN;=wdxbMSs#d#tqbE+fnS|N>0GtN+9#H}wOMG* z3xBk-XO0sVTR*(<9bvZh#^>G@8jD}LX{#WNST{~PUvlyK;=fLl1>uq6 z_UV5Rgm*08%o)X&nY#obQvA1BeS+{_F?!o?_^JN(`QwE5toqxVig(<-Ll6{e_@3#- z)3eVD_)g!P)F9wn`sTL*|cd5v5QyR6_VPd zHpQAlrD@sN8ao*6t))FwS+j39?WURoG(-ox`udZrW2MyehoEkOpwLvMWH z%xVfyKy8afwWt=O5NWA+-qOQ zJ&jaDHI0q?h)ud^UTn1XKDb|ar})W-fWu!EUwZg!B_;MGt9Aa|&ou6kbc65lxREdt zuZ0I2$wv(v>z0sp^@1s%X7Pv-GolcHs%xmlAaD*P&04VFvvM4w)pgR1L_85UnKQ>8 zlIVLEE_{!^M;BJnleDL0*+b4W7HAUH7~dIb(2lCe03Qa?a4l*ofT*&VsTrCcOGcBi zgl4D-5rv@{EYK}3w@Y^Xm`*Z#2PAu@V)%K0v!a5YpG{ka%g@jm8Xlq3%()${$A^ZF z({UQAq%tb2tVBVTLv)-?veTfKa|@1xM$Rp`-RgR*M)20_8h83OIGNl8@OHzmQtUr#JJj zfKDl^A4Zj;MwC>-H>tq04^M)Jsz4J_Lo-<<8|aruh?GJLT9=?Zqq|{fAS6u-P&CQ0 zf+H?d94Jfj9qKt&m3RR|U|0jTYj8v3+X?+RoiMx+*+(tZ(Am<9$#r$2==K$w)|vCLDb-S~Ru3e7nL59#_dTU+Vn`3c`kDQpGf5&j5NIX&1`1?g zX4y<83yaGHNM_Cq`!^g5vc1SrCC#VGAi*fbw3rrBBWg4iFlCads;0IIdZ_KEK}zY# zcoL(Ltg%+&71%9!RmD_PUKb-T?AW~x@GKvpmuax`;6N^H85uprjBHw`U%f+OA7#CX z0OK|!rfX77rtCK&dQ6YDLr*D);<%L%&A6$X;Ql0}%u*2wLZ+IJ{8CEN}W~o|6rCcl6#`J=L zz$DS)j=?fcrdk-36N938FOB3d;XN53)8LYN3!?rG=(>|qzNa*}_`@L(nqmFO2_cSpO(1u($1ZT#KL&6v>%Fj>qLYZR4j!N*OfyLrSC>gm z3Q8#yWcja3M!W9rG1&;{Uh?Zc zu28;Z=|z5mBOHa42HS^whvJ*`YCtGvsAjZ1*xPm#)R*(2^%-<$^dP6g7#<4gFk~xk z=@um^sU@|pXuhr8HwqazRm3r)C1*lSCNjc0-!(2CzUg!nvBELKA8l&|2RG%t2*IdO zpl(KpwY>2GJwoPw=FELfYM^>*1R=gc`LF9e&w>yYXonNxt&Sj3TQJy0Z4^XgN$u%0 z3Q7m5jX9EiUUIIbTW^+W8aemc<<68e{{(yl{|v)OC+%`uBKv-N|JFaEC$r}{=Ox-( zSGSk;QvH5N39+7@;NFM7Le|27kSI&+=z8+I~#wyjA<22jd@jID4=u>#NxB%%j6jygCjtni1)II8l{eHQ{f?zObI z7MIm{q$AuJ+RxFTi_x^4hn3$$G%rMItEfe^U?NoC>Z_${+IDp3NgnVVGDo?{n?Te)V3i8( z9l68~bqDPni{bGE9||U+Cfygpz~eGPKZt>sKo9t#E$?e@PPit)g`|aI2o2+gm7pYg zF#0hN@HNObPf0&f`_6PjqRoiJ!Py6?o7*s_oDM{}E@#I_IUA6Lq47!PwVYM}Y~m_7 z8@sHwMe0nd(F(B+Kee!R2rS#uLDy1Ts|luE3*uHYjJA*C6z3NEF=@F5dfg=U=zZ`| z7C-?Xuj!3%h}wDy%mj9V%cs<wAU}h0Au*Z{Ibtc&I1hleWelS_0J)69nX)|pq&>`ySFN`EB&+Ymq4b!<(r(UPm>C{$w zj>>l}tz176?uWx+i~1!uo<~HF@LuCFKa3AmIAb_m+~s`0n^3YyC}Kf%F6nbc@EVj3 zwQ#L-9Rmk03G!0YSy&S*BYvLpMw8)8Bp>dH_R(1yDDYUxpRF`mSQqPUOWMK{ge_Fv zxBlcJmXzaBkOC z-p{kqs@mE;qe8x(H@mm`Yu#oZ-5jdzM>E}Nt$J{(O!nub=V+@dpCe0$)|ZECxkKLb ziuYwihOfBttSyu$ht3`OCPJLox1M^A*!P{YKvRu(BWLi0FBveyNx0KwE<0qhB)->8 z!yp_t8H3M&khza?eFrx`4rlj{%*>F&aihv6tsfk08 z97N03ydo)JnbEWiih|x^5xg_uDkC0CgtOwd@7AA#ah)C>K24`-n9JH5+P?M<=C^+) z0X?!a;WEf30AxTIa5nxj$i~|ffJFAOfySrlkC)<%n-OMwdQ|Ka5!ZOR7#jd2YQ0{rPWu{ZBbBEg?9YCNV#;))uJu&Rw4NJO^J69)2&6i5Y;Awd6jAEqu^ zI%3=mqnt>YkQKn;WHK5Sx5DfF)h^2~_c0N-eR!BKLXp21=C5`6C;RMZlK!Yui^Yq8zctF5 zris+zhV;&}BqFqccVx})imhfKM|pRy-FZa+$gT#8Vk+BZW|d^r3>%7}Xi7B9Lanb+ zC@V%&TFStlNG1~_}wtm!|B?Hxa9L)__k$g5yQ9Vq!P%LaB>EaSuc^*4Rf&itLsFJH>~?sV28sSnC>gtgoylGpEV z!L%PC5sQFWIAJYsiEU;uOF366UAc}r%CLj9FM&raBhT(WKWd;JlFcmT#QlS@0`fkJdh^YS39>MR;)jUzeZXT zZ3>cgZlnrU$`qCiZlzR4jALa;1ye+1GZL3m-YCAken+`GTR$8)hlD>Zw?|kfmOfi@ zt(dJ1VPFDSV`ZIhi&-;$Ss4BtWVO$ETq0G3gNKV}4nP7=kL4uhecP@f?_J_c_Vw^& z;gF&+m7FL%lBgJR7#Za#&{Du0dzlWN7L7y_R*Kk|yRaC7HWeTDJ3b>fZO5uBHa!tf zs_`g0u;q1l1WJWRkv#tvm>)?EZ%f?FgG=g9!XOC~DwPEm9M1TS|=0F4YV z0|qY-6pgzW{7|1u1O1JcO}dz#d=V?ySMk4h$CY>l(<9#cOt!8u6gu*&&1K@@67z+4|z9!{vT{5SCovyC3BzS4Z`RU zyhO8Ias|KhXWKHX?wQ8~kM+_s)2}Guqq?vB=`%M8C8=k(Jo#l@eg?-qmzu`TH{;9? Z0SABV(zW~erE8A||0XXh);zmJ_+Pr|kzN1* delta 1851 zcmY*aeQXrh5#PCE;n{!4p~PlNM4)Rn$Kwl<-n`L;nJvh7|IF zENm2txz${7V0>=d{1uPwT;R-@E#+oC`}m%P6^lw14=$G}79>`zUzPQf z={23}?mpA`>~kATMP)_*+sHT7Y?>;5tE9Jdb!k`G_Oic}7nZ+T?k+!H{?QBVTTXBJ z@_sb!;ggJ}#d{KV{=tmpe94SHFjkjIJH_8W&;`V1@RKt;;4^NXRRxRqFJ>KsX8y^n z3ivf o9C`F_?0*Z4o#2B_7Sq%@kLo#UJ@9)$1ZlXL$Kw`E*)8RO$WclK^GB zcm5i^YyL?yMPx_?0PFOk%#{E(zCTNYJpH>Y3E-eU`^On3u<1Dq^UaXYElch|I)5hn z9_-Se%-IJJ;>yx1ruaR5$1)edLj9B6y8s{auk!QsoB0O;>h;Pe1_A!8f4lr^nt0Ar z4*i!a|6n2}=cyY&--gvU0qpu$KaI?A#T|)MX&GWO7K>x*Z`I#m7s+XbDe8g$inv}n zjE%}+h2d4~{2XHz(T9ex`1xg0@T}IS4Pqyr_C(#hU3&wMst)xi;-ET$$dG$OqO7?? zF7%>b@%mVY53OPwwy8FJG+beS#257pxZieOU;!0Xv8}6punAdmF=8@LMD(MA%&GXD zqU=)IBd6+)HP_&)xb^bO*YHEU^g-w{E~@VHzvUZf#U^ZQX|`b_HV@+{4)?|0jO_GR;!ZlO zLSQnb!LMRK(JGk!9HLBA1%@*Z@cqLO=9i7CU%StaOGPO!DfRT7@) zah}1m*xgO)Or5jm1h(PpuVWi~4<&Say>9fNCyX3_sQ#+Y{Si? zIEF*Rq()nYZS=k6k{ypz!f~*#KSooZ17Ab?afcmQS|XI##jidWgO7DvA=Txd^tnZU zG{M*WubbAmlI3xqgj17~Q#gf_J8&~rA1Hh=X=bO|6j_uhTe6?Yvfu0T_&XF?QA9&2 zGV9=;E;(R`sAwcA9lCu$tcaT${*LdmcvkX`WWn7r%1rObxl3Wqr}+u8N)qTW)~ce) zE~*@Is4}8aH;lZLGRB>}o3gH|sz!_(@wAo12j#1_5M&(^($qdTz=+Mw8mB&yEE@-5JX(5FzLpaLP;!)~J zIVBgUiR6^5;P*>fV2n5Y?D`}7!1LvgR-ZqA9wzvy(tA*#AKlz(g3Wx6r5>2xYIzr^ zLp`}gh61i{sfKYbZOwzVe8bjUQ~Va+`*89QH%lMcdI3PxcUNRmJK!zbUPwtqGZIk= z=cMwQ+OpY&aATSTZU{O2Vr?N5^8eJXt1?%FZtqRs_? Date: Fri, 29 Aug 2014 18:04:06 +1000 Subject: [PATCH 04/68] Applying new icons for map view --- app/serializers/api/enterprise_serializer.rb | 6 +++--- app/views/json/partials/_enterprise.rabl | 6 +++--- spec/serializers/enterprise_serializer_spec.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 850c88ad15..6fa7264630 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -69,11 +69,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def icon if object.is_primary_producer? and object.is_distributor? - "/assets/map-icon-both.svg" + "/assets/map_003-producer-shop.svg" elsif object.is_primary_producer? - "/assets/map-icon-producer.svg" + "/assets/map_001-producer-only.svg" else - "/assets/map-icon-hub.svg" + "/assets/map_005-hub.svg" end end diff --git a/app/views/json/partials/_enterprise.rabl b/app/views/json/partials/_enterprise.rabl index d9fa11f529..5b52898482 100644 --- a/app/views/json/partials/_enterprise.rabl +++ b/app/views/json/partials/_enterprise.rabl @@ -22,10 +22,10 @@ end node :icon do |e| if e.is_primary_producer? and e.is_distributor? - image_path "map-icon-both.svg" + image_path "map_003-producer-shop.svg" elsif e.is_primary_producer? - image_path "map-icon-producer.svg" + image_path "map_001-producer-only.svg" else - image_path "map-icon-hub.svg" + image_path "map_005-hub.svg" end end diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index 8072862e72..5fe57a99e2 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -16,6 +16,6 @@ describe Api::EnterpriseSerializer do it "will render urls" do serializer = Api::EnterpriseSerializer.new enterprise - serializer.to_json.should match "map-icon-hub.svg" + serializer.to_json.should match "map_005-hub.svg" end end From 116eb6a2c6fcf7471b9747b25402f1d9533268f4 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 11:38:19 +1000 Subject: [PATCH 05/68] template updates for modals - links not buttons --- .../javascripts/templates/partials/hub_actions.html.haml | 6 +++--- .../javascripts/templates/partials/hub_details.html.haml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/templates/partials/hub_actions.html.haml b/app/assets/javascripts/templates/partials/hub_actions.html.haml index 54b8ee98b1..42492ad06e 100644 --- a/app/assets/javascripts/templates/partials/hub_actions.html.haml +++ b/app/assets/javascripts/templates/partials/hub_actions.html.haml @@ -4,13 +4,13 @@ Shop for %strong {{enterprise.name}} products at: - %a.button.hub{"ng-repeat" => "hub in enterprise.hubs", + %a.hub{"ng-repeat" => "hub in enterprise.hubs", "bo-href" => "hub.path", "bo-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_033-open-sign{"bo-if" => "hub.active"} %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} - {{hub.name}} + .hub-name {{hub.name}} .button-address {{ hub.address.city }} , {{hub.address.state_name}} - %i.ofn-i_007-caret-right + / %i.ofn-i_007-caret-right diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml index 8de1039f10..73786bf631 100644 --- a/app/assets/javascripts/templates/partials/hub_details.html.haml +++ b/app/assets/javascripts/templates/partials/hub_details.html.haml @@ -14,11 +14,11 @@ Delivery .row .columns.small-12 - %a.button.hub.expand{"bo-href" => "enterprise.path", + %a.hub{"bo-href" => "enterprise.path", "ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}", "ofn-empties-cart" => "enterprise"} %i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"} %i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"} - {{enterprise.name}} + .hub-name {{enterprise.name}} .button-address {{ enterprise.address.city }} , {{enterprise.address.state_name}} - %i.ofn-i_007-caret-right + / %i.ofn-i_007-caret-right From 0138fded170d4a27974058d0365d4c053bb64783 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 11:39:04 +1000 Subject: [PATCH 06/68] Styling for modals --- .../darkswarm/modal-enterprises.css.sass | 93 ++++++++++++------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass index 4883aa3572..e3d9decf10 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass @@ -2,6 +2,23 @@ @import mixins +// Generic styles for use + +.modal-centered + text-align: center + p + margin-bottom: 0 + +.modal-header, p.modal-header + text-align: center + text-transform: uppercase + font-size: 1rem + font-weight: 400 + color: $disabled-dark + border-bottom: 1px solid $disabled-dark + margin-top: 0.75rem + margin-bottom: 0.5rem + // Enterprise promo image and text .highlight @@ -82,51 +99,55 @@ color: $disabled-dark span text-transform: capitalize - - .button.secondary - background-color: #999 - .button.hub + + .hub margin-right: 1rem - margin-top: 0.25rem - margin-bottom: 1rem - padding-left: 1rem - padding-right: 1rem - font-weight: bold + margin-top: 0.5rem + margin-bottom: 0.5rem + // padding-left: 1rem + // padding-right: 1rem + display: inline-block + + &.secondary + color: $disabled-dark + + &.primary + color: $clr-brick + i.ofn-i_033-open-sign, i.ofn-i_032-closed-sign - margin-right: 0.15rem - font-size: 1.25rem - i.ofn-i_007-caret-right - margin-left: 1rem + margin-right: 0.1rem + font-size: 2rem + padding: 0.15rem 0.25rem 0.35rem 0.25rem + float: left + + // i.ofn-i_007-caret-right + // margin-left: .1rem + + .hub-name + margin-top: 0.6rem + margin-bottom: 0.2rem + font-weight: 400 + display: inline-block + border-bottom: 1px solid transparent .button-address + margin-top: 0.55rem + margin-bottom: 0.2rem + margin-left: 0.1rem + text-transform: uppercase font-weight: 300 font-style: italic font-size: 0.75rem - display: inline + display: inline-block + border-bottom: 1px solid transparent @media all and (max-width: 640px) display: none - .button.hub.expand - text-align: left - i.ofn-i_007-caret-right - float: right - i.ofn-i_033-open-sign, i.ofn-i_032-closed-sign - float: left - margin-right: 0.5rem + + &:hover, &:focus, &:active + color: #666 + .hub-name, .button-address + // text-decoration: underline + border-bottom: 1px solid #999 -// Generic styles for use -.modal-centered - text-align: center - p - margin-bottom: 0 - -.modal-header, p.modal-header - text-align: center - text-transform: uppercase - font-size: 1rem - font-weight: 400 - color: $disabled-dark - border-bottom: 1px solid $disabled-dark - margin-top: 0.75rem - margin-bottom: 0.5rem From c64ef39e229730e1cb24c4a4b0372b4cfea77879 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 11:39:43 +1000 Subject: [PATCH 07/68] Ran a DB:micrate, this is a change to the order --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index dbb50d6b33..462e89c952 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -563,9 +563,9 @@ ActiveRecord::Schema.define(:version => 20140826043521) do t.string "email" t.text "special_instructions" t.integer "distributor_id" - t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" + t.integer "order_cycle_id" t.integer "cart_id" end From ed0f2de2f33c5e3cec6dc148b199a0f577de3511 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 11:56:21 +1000 Subject: [PATCH 08/68] More styling for CTA links on modals --- .../darkswarm/modal-enterprises.css.sass | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass index e3d9decf10..6f86566dd3 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass @@ -91,7 +91,7 @@ label text-transform: uppercase font-size: 0.875rem - margin-bottom: 0 + margin-bottom: 0.75rem 5rem color: $dark-grey @@ -101,9 +101,9 @@ text-transform: capitalize .hub - margin-right: 1rem - margin-top: 0.5rem - margin-bottom: 0.5rem + margin-right: 2rem + margin-top: 0rem + margin-bottom: 0.75rem // padding-left: 1rem // padding-right: 1rem display: inline-block @@ -120,17 +120,14 @@ padding: 0.15rem 0.25rem 0.35rem 0.25rem float: left - // i.ofn-i_007-caret-right - // margin-left: .1rem - .hub-name - margin-top: 0.6rem + margin-top: 0.75rem margin-bottom: 0.2rem font-weight: 400 display: inline-block border-bottom: 1px solid transparent .button-address - margin-top: 0.55rem + margin-top: 0.5rem margin-bottom: 0.2rem margin-left: 0.1rem text-transform: uppercase @@ -143,11 +140,14 @@ display: none &:hover, &:focus, &:active - color: #666 - .hub-name, .button-address - // text-decoration: underline - border-bottom: 1px solid #999 - + &.secondary + color: #666 + .hub-name, .button-address + border-bottom: 1px solid #999 + &.primary + color: $clr-brick-bright + .hub-name, .button-address + border-bottom: 1px solid $clr-brick-bright From 94fbb624f5fe61e76d2d53b0d6fe13e5753e40d8 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 12:14:55 +1000 Subject: [PATCH 09/68] Make CTA links separate from hub link styling --- .../javascripts/templates/partials/hub_actions.html.haml | 2 +- .../javascripts/templates/partials/hub_details.html.haml | 4 ++-- app/assets/stylesheets/darkswarm/modal-enterprises.css.sass | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/templates/partials/hub_actions.html.haml b/app/assets/javascripts/templates/partials/hub_actions.html.haml index 42492ad06e..42a8b8cd28 100644 --- a/app/assets/javascripts/templates/partials/hub_actions.html.haml +++ b/app/assets/javascripts/templates/partials/hub_actions.html.haml @@ -4,7 +4,7 @@ Shop for %strong {{enterprise.name}} products at: - %a.hub{"ng-repeat" => "hub in enterprise.hubs", + %a.cta-hub{"ng-repeat" => "hub in enterprise.hubs", "bo-href" => "hub.path", "bo-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml index 73786bf631..43e75dbbd2 100644 --- a/app/assets/javascripts/templates/partials/hub_details.html.haml +++ b/app/assets/javascripts/templates/partials/hub_details.html.haml @@ -2,7 +2,7 @@ .cta-container.small-12.columns .row .small-4.columns - %label{"active-table-hub-link" => "enterprise", change: "Change hub to:", shop: "Shop now at:"} + %label{"active-table-hub-link" => "enterprise", change: "Change shop to:", shop: "Shop now at:"} .small-8.columns.right %label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"} Delivery options: @@ -14,7 +14,7 @@ Delivery .row .columns.small-12 - %a.hub{"bo-href" => "enterprise.path", + %a.cta-hub{"bo-href" => "enterprise.path", "ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}", "ofn-empties-cart" => "enterprise"} %i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"} diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass index 6f86566dd3..2009381daa 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass @@ -100,7 +100,7 @@ span text-transform: capitalize - .hub + .cta-hub margin-right: 2rem margin-top: 0rem margin-bottom: 0.75rem From b487c9848b3e1d9f8cf24c5b5cf5f3d3bd087fd6 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 12:15:14 +1000 Subject: [PATCH 10/68] Add checkbox for show / hide profiles --- app/views/shared/components/_filter_controls.html.haml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/shared/components/_filter_controls.html.haml b/app/views/shared/components/_filter_controls.html.haml index 55b7ef1adb..b4dff8a0ce 100644 --- a/app/views/shared/components/_filter_controls.html.haml +++ b/app/views/shared/components/_filter_controls.html.haml @@ -1,5 +1,5 @@ .row - .small-12.columns + .small-12.medium-6.columns %a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", "ng-show" => "FilterSelectorsService.selectors.length > 0"} {{ filterText(filtersActive) }} @@ -8,3 +8,6 @@ %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} No filters + .small-12.medium-6.columns.text-right + %input{type: "checkbox", name: "profile"}>< + %label Show profiles \ No newline at end of file From 8f6d04a12934a6d45b52b06608b0e50fb77f1d96 Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 12:30:52 +1000 Subject: [PATCH 11/68] Adding properties to enterprise serialiser to allow differentiation between profile, shopfront and aggregators --- app/serializers/api/enterprise_serializer.rb | 15 +++++++++++++-- app/views/home/_skinny.html.haml | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 6fa7264630..ff6ffce12f 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -30,11 +30,12 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer cached delegate :cache_key, to: :object - attributes :name, :id, :description, :latitude, :longitude, - :long_description, :website, :instagram, :linkedin, :twitter, + attributes :name, :id, :description, :latitude, :longitude, + :long_description, :website, :instagram, :linkedin, :twitter, :facebook, :is_primary_producer, :is_distributor, :phone, :visible, :email, :hash, :logo, :promo_image, :icon, :path, :pickup, :delivery + attributes :has_shopfront, :can_aggregate has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer has_many :supplied_taxons, serializer: Api::IdSerializer @@ -77,6 +78,16 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer end end + # TODO: Remove this when flags on enterprises are switched over + def has_shopfront + object.type != 'profile' + end + + # TODO: Remove this when flags on enterprises are switched over + def can_aggregate + object.is_distributor && !object.is_primary_producer + end + # TODO when ActiveSerializers supports URL helpers # Then refactor. See readme https://github.com/rails-api/active_model_serializers def path diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index 2b59132999..828031abb7 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,6 +1,7 @@ .row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}", bindonce: true} .columns.small-12.medium-6.large-5.skinny-head %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} + /%i{ ng: { class: "{'ofn-i_063-hub': hub.can_aggregate, 'ofn-i_059-producer': !hub.can_aggregate}" } } %i.ofn-i_040-hub %span %strong {{ hub.name | truncate:40}} From 658c27408e5a391559ad03be6369eeda12ad5253 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 12:32:45 +1000 Subject: [PATCH 12/68] Styling for hub listing page and tweaks to markup to accomodate new icons and styling --- .../stylesheets/darkswarm/hub_node.css.sass | 31 +++++++++---------- app/views/home/_hubs.html.haml | 4 +-- app/views/home/_skinny.html.haml | 5 ++- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index d0b5dcc0ef..8906d2cfa2 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -11,14 +11,12 @@ overflow-y: visible //Hub icon styline - i.ofn-i_040-hub - @include border-radius(99999rem) - font-size: 1.15rem + // i.ofn-i_040-hub + i.ofn-i_063-hub + font-size: 2rem display: inline-block - padding: 0.2rem - margin-top: 0.1rem - margin-bottom: 0.1rem - background-color: transparent + margin-right: 0.25rem + float: left @media all and (max-width: 768px) font-size: 1rem @@ -26,6 +24,12 @@ a:hover, a:active, a:focus color: $clr-brick-bright + .hub span.hub-name-listing + margin-top: 0.5rem + display: inline-block + float: left + font-weight: 700 + @media all and (max-width: 640px) &.closed, &.open .active_table_row:first-child .skinny-head @@ -91,20 +95,15 @@ &.current //overwrites active_table &.closed - a, a i, a span, a strong - color: $clr-brick + &, & * + color: white &.closed, &.open - a i.ofn-i_040-hub - color: white + div.active_table_row background-color: $clr-brick - - a:hover, a:focus, a:active strong, span - color: $clr-brick-bright - i.ofn-i_040-hub - background-color: $clr-brick-bright + color: $clr-brick-light diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index 5669014bcf..099e19a099 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -2,7 +2,7 @@ #hubs.hubs{"ng-controller" => "HubsCtrl"} .row .small-12.columns - %h1 Find hubs in your area... + %h1 Shop your local area... / %div / Shop a / %ofn-modal{title: "food hub"} @@ -14,7 +14,7 @@ / %i.ofn-i_020-search %input{type: :text, "ng-model" => "query", - placeholder: "Search by Hub or Suburb name", + placeholder: "Search by Shop or Suburb name", "ng-debounce" => "150", "ofn-disable-enter" => true} diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index 2b59132999..b50fa17f6d 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,9 +1,8 @@ .row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}", bindonce: true} .columns.small-12.medium-6.large-5.skinny-head %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} - %i.ofn-i_040-hub - %span - %strong {{ hub.name | truncate:40}} + %i.ofn-i_063-hub + %span.hub-name-listing {{ hub.name | truncate:40}} .columns.small-4.medium-2.large-2 {{ hub.address.city }} .columns.small-2.medium-1.large-1 From 32ee8afdce06eecd80479ea1a625bc3bf119e21e Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 14:46:04 +1000 Subject: [PATCH 13/68] logic for producer shop icon in list view --- app/serializers/api/enterprise_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index ff6ffce12f..305e1bcb9a 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -85,7 +85,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer # TODO: Remove this when flags on enterprises are switched over def can_aggregate - object.is_distributor && !object.is_primary_producer + object.is_distributor && object.suppliers != [object] end # TODO when ActiveSerializers supports URL helpers From 14aada180873469c038624a6e54657482c2a0452 Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 15:09:15 +1000 Subject: [PATCH 14/68] Add 'profile only' logic to maps icons --- app/serializers/api/enterprise_serializer.rb | 24 ++++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 305e1bcb9a..d7cbc08ff2 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -69,12 +69,26 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer end def icon - if object.is_primary_producer? and object.is_distributor? - "/assets/map_003-producer-shop.svg" - elsif object.is_primary_producer? - "/assets/map_001-producer-only.svg" + if has_shopfront + if can_aggregate + "/assets/map_005-hub.svg" + else + if object.is_distributor + "/assets/map_003-producer-shop.svg" + else + "/assets/map_001-producer-only.svg" + end + end else - "/assets/map_005-hub.svg" + if can_aggregate + "/assets/map_006-hub-profile.svg" + else + if object.is_distributor + "/assets/map_004-producer-shop-profile.svg" + else + "/assets/map_002-producer-only-profile.svg" + end + end end end From 602dfbe0026f299dae966b6ba0b126780f5a40f6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 31 Jul 2014 15:20:22 +1000 Subject: [PATCH 15/68] ng_text_field handling options parameter --- app/helpers/angular_form_builder.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 5d913eb291..56fcab79fa 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -12,8 +12,9 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder # @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} }}" + options.reverse_merge!({'id' => angular_id(method)}) - @template.text_field_tag angular_name(method), value, :id => angular_id(method) + @template.text_field_tag angular_name(method), value, options end def ng_hidden_field(method, options = {}) From aaa32528ea21e87a02af457e43d322e1fc95e935 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 31 Jul 2014 15:21:13 +1000 Subject: [PATCH 16/68] Giving an example name for an enterprise fee. The example is displayed as input placeholder. See bugherd #439. --- app/views/admin/enterprise_fees/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index d3e9a0b88c..48517c5931 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -27,7 +27,7 @@ = f.ng_hidden_field :id = f.ng_collection_select :enterprise_id, @enterprises, :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_text_field :name, { placeholder: 'e.g. packing fee' } %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.actions{'spree-delete-resource' => "1"} From c5cfda5283455221219f3573b9c372c5f9e5f9c6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 22 Aug 2014 16:04:46 +1000 Subject: [PATCH 17/68] Adding login_nav partial from spree_auth_devise Preparing to change 'Store' link for Bugherd #443. --- app/views/spree/layouts/admin/_login_nav.html.erb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/views/spree/layouts/admin/_login_nav.html.erb diff --git a/app/views/spree/layouts/admin/_login_nav.html.erb b/app/views/spree/layouts/admin/_login_nav.html.erb new file mode 100644 index 0000000000..a43437c4ab --- /dev/null +++ b/app/views/spree/layouts/admin/_login_nav.html.erb @@ -0,0 +1,8 @@ +<% if spree_current_user %> +
    +
  • <%= t(:logged_in_as) %>: <%= spree_current_user.email %>
  • +
  • <%= link_to t(:account), spree.edit_user_path(spree_current_user) %>
  • +
  • <%= link_to t(:logout), spree.logout_path %>
  • +
  • <%= link_to t(:store), spree.products_path, :target => '_blank' %>
  • +
+<% end %> From 7603ea867f61bbd2dc121ca38b284a0ceb893f6f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 22 Aug 2014 16:10:23 +1000 Subject: [PATCH 18/68] Store links to root_path instead of product_path --- app/views/spree/layouts/admin/_login_nav.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/spree/layouts/admin/_login_nav.html.erb b/app/views/spree/layouts/admin/_login_nav.html.erb index a43437c4ab..7ab28b3344 100644 --- a/app/views/spree/layouts/admin/_login_nav.html.erb +++ b/app/views/spree/layouts/admin/_login_nav.html.erb @@ -3,6 +3,6 @@
  • <%= t(:logged_in_as) %>: <%= spree_current_user.email %>
  • <%= link_to t(:account), spree.edit_user_path(spree_current_user) %>
  • <%= link_to t(:logout), spree.logout_path %>
  • -
  • <%= link_to t(:store), spree.products_path, :target => '_blank' %>
  • +
  • <%= link_to t(:store), spree.root_path, :target => '_blank' %>
  • <% end %> From 913c167fdf0591a9ba193441c56efb443a153907 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 Aug 2014 16:38:10 +1000 Subject: [PATCH 19/68] Revert 5ede8d1, reinstating a4be0ff..7b89e6a --- .../enterprise_relationships.js.coffee | 6 +- .../admin/order_cycles_controller.rb | 7 +- app/helpers/order_cycles_helper.rb | 14 ++- app/models/enterprise_relationship.rb | 10 +- .../_enterprise_relationship.html.haml | 4 +- .../enterprise_relationships/_form.html.haml | 15 ++- .../order_cycles/_exchange_form.html.haml | 2 +- app/views/admin/order_cycles/_form.html.haml | 4 +- app/views/admin/order_cycles/show.rep | 2 +- lib/open_food_network/permissions.rb | 37 ++++++ .../admin/enterprise_relationships_spec.rb | 21 ++-- spec/features/admin/order_cycles_spec.rb | 113 ++++++++++++------ .../enterprise_relationships_spec.js.coffee | 2 +- .../lib/open_food_network/permissions_spec.rb | 74 ++++++++++++ spec/models/enterprise_relationship_spec.rb | 27 ++++- 15 files changed, 263 insertions(+), 75 deletions(-) create mode 100644 lib/open_food_network/permissions.rb create mode 100644 spec/lib/open_food_network/permissions_spec.rb diff --git a/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee b/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee index 7a799f2f28..cad556efd8 100644 --- a/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee +++ b/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee @@ -2,7 +2,7 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris new class EnterpriseRelationships create_errors: "" all_permissions: [ - 'add_products_to_order_cycle' + 'add_to_order_cycle' 'manage_products' ] @@ -24,5 +24,5 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris permission_presentation: (permission) -> switch permission - when "add_products_to_order_cycle" then "can add products to order cycle from" - when "manage_products" then "can manage the products of" \ No newline at end of file + when "add_to_order_cycle" then "can add to order cycle" + when "manage_products" then "can manage the products of" diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 566c660112..63daf918de 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -1,7 +1,10 @@ +require 'open_food_network/permissions' require 'open_food_network/order_cycle_form_applicator' module Admin class OrderCyclesController < ResourceController + include OrderCyclesHelper + before_filter :load_order_cycle_set, :only => :index def show @@ -23,7 +26,7 @@ module Admin respond_to do |format| if @order_cycle.save - OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go! + OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, order_cycle_permitted_enterprises).go! flash[:notice] = 'Your order cycle has been created.' format.html { redirect_to admin_order_cycles_path } @@ -40,7 +43,7 @@ module Admin respond_to do |format| if @order_cycle.update_attributes(params[:order_cycle]) - OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go! + OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, order_cycle_permitted_enterprises).go! flash[:notice] = 'Your order cycle has been updated.' format.html { redirect_to admin_order_cycles_path } diff --git a/app/helpers/order_cycles_helper.rb b/app/helpers/order_cycles_helper.rb index 92cd968e0b..dfd957a143 100644 --- a/app/helpers/order_cycles_helper.rb +++ b/app/helpers/order_cycles_helper.rb @@ -3,8 +3,20 @@ module OrderCyclesHelper @current_order_cycle ||= current_order(false).andand.order_cycle end + def order_cycle_permitted_enterprises + OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises + end + + def order_cycle_producer_enterprises + order_cycle_permitted_enterprises.is_primary_producer.by_name + end + def coordinating_enterprises - Enterprise.is_distributor.managed_by(spree_current_user).order('name') + order_cycle_hub_enterprises + end + + def order_cycle_hub_enterprises + order_cycle_permitted_enterprises.is_distributor.by_name end def order_cycle_local_remote_class(distributor, order_cycle) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index 5c10aafbbf..dba99cc7b3 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -9,12 +9,20 @@ class EnterpriseRelationship < ActiveRecord::Base scope :with_enterprises, joins('LEFT JOIN enterprises AS parent_enterprises ON parent_enterprises.id = enterprise_relationships.parent_id'). joins('LEFT JOIN enterprises AS child_enterprises ON child_enterprises.id = enterprise_relationships.child_id') - scope :by_name, with_enterprises.order('parent_enterprises.name, child_enterprises.name') scope :involving_enterprises, ->(enterprises) { where('parent_id IN (?) OR child_id IN (?)', enterprises, enterprises) } + scope :permitting, ->(enterprises) { where('child_id IN (?)', enterprises) } + + scope :with_permission, ->(permission) { + joins(:permissions). + where('enterprise_relationship_permissions.name = ?', permission) + } + + scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name') + def permissions_list=(perms) perms.andand.each { |name| permissions.build name: name } diff --git a/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml b/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml index 56f0d46c01..6a7dcdc7f4 100644 --- a/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml +++ b/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml @@ -1,8 +1,8 @@ -%td {{ enterprise_relationship.parent_name }} +%td {{ enterprise_relationship.child_name }} %td %ul %li{"ng-repeat" => "permission in enterprise_relationship.permissions"} {{ EnterpriseRelationships.permission_presentation(permission.name) }} -%td {{ enterprise_relationship.child_name }} +%td {{ enterprise_relationship.parent_name }} %td.actions %a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'} diff --git a/app/views/admin/enterprise_relationships/_form.html.haml b/app/views/admin/enterprise_relationships/_form.html.haml index b9c2f265cd..86086c8781 100644 --- a/app/views/admin/enterprise_relationships/_form.html.haml +++ b/app/views/admin/enterprise_relationships/_form.html.haml @@ -1,14 +1,13 @@ %tr - %td - %select{name: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"} - %td - permits - / %div{"ng-repeat" => "permission in EnterpriseRelationships.all_permissions"} - / %label - / %input{type: "checkbox", "ng-model" => "permissions[permission]"} - / {{ EnterpriseRelationships.permission_presentation(permission) }} %td %select{name: "enterprise_relationship_child_id", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.all_enterprises"} + %td + %div{"ng-repeat" => "permission in EnterpriseRelationships.all_permissions"} + %label + %input{type: "checkbox", "ng-model" => "permissions[permission]"} + {{ EnterpriseRelationships.permission_presentation(permission) }} + %td + %select{name: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"} %td.actions %input{type: "button", value: "Create", "ng-click" => "create()"} .errors {{ EnterpriseRelationships.create_errors }} diff --git a/app/views/admin/order_cycles/_exchange_form.html.haml b/app/views/admin/order_cycles/_exchange_form.html.haml index 3539892034..8f81b9f03f 100644 --- a/app/views/admin/order_cycles/_exchange_form.html.haml +++ b/app/views/admin/order_cycles/_exchange_form.html.haml @@ -23,4 +23,4 @@ = f.submit 'Add fee', 'ng-click' => 'addExchangeFee($event, exchange)' %td.actions - %a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text"} + %a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"} diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index 68dfed9f1a..e859374a3b 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -25,7 +25,7 @@ %tr.products{'ng-show' => 'exchange.showProducts'} = render 'exchange_supplied_products_form' -= select_tag :new_supplier_id, options_from_collection_for_select(Enterprise.is_primary_producer.managed_by(spree_current_user).by_name, :id, :name), {'ng-model' => 'new_supplier_id'} += select_tag :new_supplier_id, options_from_collection_for_select(order_cycle_producer_enterprises, :id, :name), {'ng-model' => 'new_supplier_id'} = f.submit 'Add supplier', 'ng-click' => 'addSupplier($event)' @@ -50,7 +50,7 @@ %tr.products{'ng-show' => 'exchange.showProducts'} = render 'exchange_distributed_products_form' -= select_tag :new_distributor_id, options_from_collection_for_select(Enterprise.is_distributor.managed_by(spree_current_user).by_name, :id, :name), {'ng-model' => 'new_distributor_id'} += select_tag :new_distributor_id, options_from_collection_for_select(order_cycle_hub_enterprises, :id, :name), {'ng-model' => 'new_distributor_id'} = f.submit 'Add distributor', 'ng-click' => 'addDistributor($event)' .actions diff --git a/app/views/admin/order_cycles/show.rep b/app/views/admin/order_cycles/show.rep index 04fc659813..fb71602eb6 100644 --- a/app/views/admin/order_cycles/show.rep +++ b/app/views/admin/order_cycles/show.rep @@ -9,7 +9,7 @@ r.element :order_cycle, @order_cycle do r.element :id end - r.list_of :exchanges, @order_cycle.exchanges.managed_by(spree_current_user).order('id ASC') do |exchange| + r.list_of :exchanges, OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_exchanges(@order_cycle).order('id ASC') do |exchange| r.element :id r.element :sender_id r.element :receiver_id diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb new file mode 100644 index 0000000000..99c0aa0c15 --- /dev/null +++ b/lib/open_food_network/permissions.rb @@ -0,0 +1,37 @@ +module OpenFoodNetwork + class Permissions + def initialize(user) + @user = user + end + + # Find enterprises that an admin is allowed to add to an order cycle + def order_cycle_enterprises + managed_enterprise_ids = managed_enterprises.pluck :id + permitted_enterprise_ids = related_enterprises_with(:add_to_order_cycle).pluck :id + + Enterprise.where('id IN (?)', managed_enterprise_ids + permitted_enterprise_ids) + end + + # Find the exchanges of an order cycle that an admin can manage + def order_cycle_exchanges(order_cycle) + enterprises = managed_enterprises + related_enterprises_with(:add_to_order_cycle) + order_cycle.exchanges.to_enterprises(enterprises).from_enterprises(enterprises) + end + + + private + + def managed_enterprises + Enterprise.managed_by(@user) + end + + def related_enterprises_with(permission) + parent_ids = EnterpriseRelationship. + permitting(managed_enterprises). + with_permission(permission). + pluck(:parent_id) + + Enterprise.where('id IN (?)', parent_ids) + end + end +end diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index 757f8bc2e6..7317f44066 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -14,9 +14,9 @@ feature %q{ scenario "listing relationships" do # Given some enterprises with relationships e1, e2, e3, e4 = create(:enterprise), create(:enterprise), create(:enterprise), create(:enterprise) - create(:enterprise_relationship, parent: e1, child: e2, permissions_list: [:add_products_to_order_cycle]) + create(:enterprise_relationship, parent: e1, child: e2, permissions_list: [:add_to_order_cycle]) create(:enterprise_relationship, parent: e2, child: e3, permissions_list: [:manage_products]) - create(:enterprise_relationship, parent: e3, child: e4, permissions_list: [:add_products_to_order_cycle, :manage_products]) + create(:enterprise_relationship, parent: e3, child: e4, permissions_list: [:add_to_order_cycle, :manage_products]) # When I go to the relationships page click_link 'Enterprises' @@ -24,10 +24,10 @@ feature %q{ # Then I should see the relationships within('table#enterprise-relationships') do - page.should have_relationship e1, e2, ['can add products to order cycle from'] + page.should have_relationship e1, e2, ['can add to order cycle'] page.should have_relationship e2, e3, ['can manage the products of'] page.should have_relationship e3, e4, - ['can add products to order cycle from', 'can manage the products of'] + ['can add to order cycle', 'can manage the products of'] end end @@ -38,16 +38,17 @@ feature %q{ visit admin_enterprise_relationships_path select 'One', from: 'enterprise_relationship_parent_id' - #check 'can add products to order cycle from' - #check 'can manage the products of' - #uncheck 'can manage the products of' + + check 'can add to order cycle' + check 'can manage the products of' + uncheck 'can manage the products of' select 'Two', from: 'enterprise_relationship_child_id' click_button 'Create' - page.should have_relationship e1, e2 #, ['can add products to order cycle from'] + page.should have_relationship e1, e2, ['can add to order cycle'] er = EnterpriseRelationship.where(parent_id: e1, child_id: e2).first er.should be_present - #er.permissions.map(&:name).should == ['add_products_to_order_cycle'] + er.permissions.map(&:name).should == ['add_to_order_cycle'] end @@ -119,6 +120,6 @@ feature %q{ def have_relationship(parent, child, perms=[]) perms = perms.join(' ') || 'permits' - have_table_row [parent.name, perms, child.name, ''] + have_table_row [child.name, perms, parent.name, ''] end end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 2423a6dc05..76ee3bdfc2 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -436,26 +436,37 @@ feature %q{ context "as an enterprise user" do - let(:supplier1) { create(:supplier_enterprise, name: 'First Supplier') } - let(:supplier2) { create(:supplier_enterprise, name: 'Another Supplier') } - let(:distributor1) { create(:distributor_enterprise, name: 'First Distributor') } - let(:distributor2) { create(:distributor_enterprise, name: 'Another Distributor') } - let!(:distributor1_fee) { create(:enterprise_fee, enterprise: distributor1, name: 'First Distributor Fee') } - before(:each) do - product = create(:product, supplier: supplier1) - product.distributors << distributor1 + let!(:supplier_managed) { create(:supplier_enterprise, name: 'Managed supplier') } + let!(:supplier_unmanaged) { create(:supplier_enterprise, name: 'Unmanaged supplier') } + let!(:supplier_permitted) { create(:supplier_enterprise, name: 'Permitted supplier') } + let!(:distributor_managed) { create(:distributor_enterprise, name: 'Managed distributor') } + let!(:distributor_unmanaged) { create(:distributor_enterprise, name: 'Unmanaged Distributor') } + let!(:distributor_permitted) { create(:distributor_enterprise, name: 'Permitted distributor') } + let!(:distributor_managed_fee) { create(:enterprise_fee, enterprise: distributor_managed, name: 'Managed distributor fee') } + let!(:supplier_permitted_relationship) do + create(:enterprise_relationship, parent: supplier_permitted, child: supplier_managed, + permissions_list: [:add_to_order_cycle]) + end + let!(:distributor_permitted_relationship) do + create(:enterprise_relationship, parent: distributor_permitted, child: distributor_managed, + permissions_list: [:add_to_order_cycle]) + end + + before do + product = create(:product, supplier: supplier_managed) + product.distributors << distributor_managed product.save! @new_user = create_enterprise_user - @new_user.enterprise_roles.build(enterprise: supplier1).save - @new_user.enterprise_roles.build(enterprise: distributor1).save + @new_user.enterprise_roles.build(enterprise: supplier_managed).save + @new_user.enterprise_roles.build(enterprise: distributor_managed).save login_to_admin_as @new_user end scenario "viewing a list of order cycles I am coordinating" do - oc_user_coordinating = create(:simple_order_cycle, { suppliers: [supplier1, supplier2], coordinator: supplier1, distributors: [distributor1, distributor2], name: 'Order Cycle 1' } ) - oc_for_other_user = create(:simple_order_cycle, { coordinator: supplier2, name: 'Order Cycle 2' } ) + oc_user_coordinating = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_unmanaged], coordinator: supplier_managed, distributors: [distributor_managed, distributor_unmanaged], name: 'Order Cycle 1' } ) + oc_for_other_user = create(:simple_order_cycle, { coordinator: supplier_unmanaged, name: 'Order Cycle 2' } ) click_link "Order Cycles" @@ -464,8 +475,8 @@ feature %q{ page.should_not have_content oc_for_other_user.name # The order cycle should not show enterprises that I don't manage - page.should_not have_selector 'td.suppliers', text: supplier2.name - page.should_not have_selector 'td.distributors', text: distributor2.name + page.should_not have_selector 'td.suppliers', text: supplier_unmanaged.name + page.should_not have_selector 'td.distributors', text: distributor_unmanaged.name end scenario "creating a new order cycle" do @@ -476,57 +487,81 @@ feature %q{ fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00' fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00' - select 'First Supplier', from: 'new_supplier_id' + select 'Managed supplier', from: 'new_supplier_id' + click_button 'Add supplier' + select 'Permitted supplier', from: 'new_supplier_id' click_button 'Add supplier' - select 'First Distributor', from: 'order_cycle_coordinator_id' + select 'Managed distributor', from: 'order_cycle_coordinator_id' click_button 'Add coordinator fee' - select 'First Distributor Fee', from: 'order_cycle_coordinator_fee_0_id' + select 'Managed distributor fee', from: 'order_cycle_coordinator_fee_0_id' - select 'First Distributor', from: 'new_distributor_id' + select 'Managed distributor', from: 'new_distributor_id' + click_button 'Add distributor' + select 'Permitted distributor', from: 'new_distributor_id' click_button 'Add distributor' - # Should only have suppliers / distributors listed which the user can manage - within "#new_supplier_id" do - page.should_not have_content supplier2.name - end - within "#new_distributor_id" do - page.should_not have_content distributor2.name - end - within "#order_cycle_coordinator_id" do - page.should_not have_content distributor2.name - page.should_not have_content supplier1.name - page.should_not have_content supplier2.name + # Should only have suppliers / distributors listed which the user is managing or + # has E2E permission to add products to order cycles + page.should_not have_select 'new_supplier_id', with_options: [supplier_unmanaged.name] + page.should_not have_select 'new_distributor_id', with_options: [distributor_unmanaged.name] + + [distributor_unmanaged.name, supplier_managed.name, supplier_unmanaged.name].each do |enterprise_name| + page.should_not have_select 'order_cycle_coordinator_id', with_options: [enterprise_name] end click_button 'Create' flash_message.should == "Your order cycle has been created." order_cycle = OrderCycle.find_by_name('My order cycle') - order_cycle.coordinator.should == distributor1 + order_cycle.suppliers.sort.should == [supplier_managed, supplier_permitted].sort + order_cycle.coordinator.should == distributor_managed + order_cycle.distributors.sort.should == [distributor_managed, distributor_permitted].sort end - scenario "editing an order cycle" do - oc = create(:simple_order_cycle, { suppliers: [supplier1, supplier2], coordinator: supplier1, distributors: [distributor1, distributor2], name: 'Order Cycle 1' } ) + scenario "editing an order cycle does not affect exchanges we don't manage" do + oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: supplier_managed, distributors: [distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) visit edit_admin_order_cycle_path(oc) - # I should not see exchanges for supplier2 or distributor2 - page.all('tr.supplier').count.should == 1 - page.all('tr.distributor').count.should == 1 + # I should not see exchanges for supplier_unmanaged or distributor_unmanaged + page.all('tr.supplier').count.should == 2 + page.all('tr.distributor').count.should == 2 # When I save, then those exchanges should remain click_button 'Update' page.should have_content "Your order cycle has been updated." oc.reload - oc.suppliers.sort.should == [supplier1, supplier2] - oc.coordinator.should == supplier1 - oc.distributors.sort.should == [distributor1, distributor2] + oc.suppliers.sort.should == [supplier_managed, supplier_permitted, supplier_unmanaged].sort + oc.coordinator.should == supplier_managed + oc.distributors.sort.should == [distributor_managed, distributor_permitted, distributor_unmanaged].sort end + scenario "editing an order cycle" do + oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: supplier_managed, distributors: [distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) + + visit edit_admin_order_cycle_path(oc) + + # When I remove all the exchanges and save + page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").click + page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click + page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click + page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + click_button 'Update' + + # Then the exchanges should be removed + page.should have_content "Your order cycle has been updated." + + oc.reload + oc.suppliers.should == [supplier_unmanaged] + oc.coordinator.should == supplier_managed + oc.distributors.should == [distributor_unmanaged] + end + + scenario "cloning an order cycle" do - oc = create(:simple_order_cycle) + oc = create(:simple_order_cycle, coordinator: distributor_managed) click_link "Order Cycles" first('a.clone-order-cycle').click diff --git a/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee b/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee index 4e90f65335..9701377a73 100644 --- a/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee @@ -12,5 +12,5 @@ describe "enterprise relationships", -> EnterpriseRelationships = _EnterpriseRelationships_ it "presents permission names", -> - expect(EnterpriseRelationships.permission_presentation("add_products_to_order_cycle")).toEqual "can add products to order cycle from" + expect(EnterpriseRelationships.permission_presentation("add_to_order_cycle")).toEqual "can add to order cycle" expect(EnterpriseRelationships.permission_presentation("manage_products")).toEqual "can manage the products of" diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb new file mode 100644 index 0000000000..b0a004cbf1 --- /dev/null +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -0,0 +1,74 @@ +require 'open_food_network/permissions' + +module OpenFoodNetwork + describe Permissions do + let(:user) { double(:user) } + let(:permissions) { Permissions.new(user) } + let(:permission) { 'one' } + let(:e1) { create(:enterprise) } + let(:e2) { create(:enterprise) } + + describe "finding enterprises that can be added to an order cycle" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where('1=0') } + permissions.stub(:related_enterprises_with) { Enterprise.where('1=0') } + end + + it "returns managed enterprises" do + permissions.stub(:managed_enterprises) { Enterprise.where(id: e1) } + permissions.order_cycle_enterprises.should == [e1] + end + + it "returns permitted enterprises" do + permissions.stub(:related_enterprises_with) { Enterprise.where(id: e2) } + permissions.order_cycle_enterprises.should == [e2] + end + end + + describe "finding exchanges of an order cycle that an admin can manage" do + let(:oc) { create(:simple_order_cycle) } + let!(:ex) { create(:exchange, order_cycle: oc, sender: e1, receiver: e2) } + + before do + permissions.stub(:managed_enterprises) { [] } + permissions.stub(:related_enterprises_with) { [] } + end + + it "returns exchanges involving enterprises managed by the user" do + permissions.stub(:managed_enterprises) { [e1, e2] } + permissions.order_cycle_exchanges(oc).should == [ex] + end + + it "returns exchanges involving enterprises with E2E permission" do + permissions.stub(:related_enterprises_with) { [e1, e2] } + permissions.order_cycle_exchanges(oc).should == [ex] + end + + it "does not return exchanges involving only the sender" do + permissions.stub(:managed_enterprises) { [e1] } + permissions.order_cycle_exchanges(oc).should == [] + end + + it "does not return exchanges involving only the receiver" do + permissions.stub(:managed_enterprises) { [e2] } + permissions.order_cycle_exchanges(oc).should == [] + end + end + + ######################################## + + describe "finding related enterprises with a particular permission" do + let!(:er) { create(:enterprise_relationship, parent: e1, child: e2, permissions_list: [permission]) } + + it "returns the enterprises" do + permissions.stub(:managed_enterprises) { e2 } + permissions.send(:related_enterprises_with, permission).should == [e1] + end + + it "returns an empty array when there are none" do + permissions.stub(:managed_enterprises) { e1 } + permissions.send(:related_enterprises_with, permission).should == [] + end + end + end +end diff --git a/spec/models/enterprise_relationship_spec.rb b/spec/models/enterprise_relationship_spec.rb index 6cdf3db657..3a6c48e891 100644 --- a/spec/models/enterprise_relationship_spec.rb +++ b/spec/models/enterprise_relationship_spec.rb @@ -6,10 +6,10 @@ describe EnterpriseRelationship do let(:e2) { create(:enterprise, name: 'B') } let(:e3) { create(:enterprise, name: 'C') } - it "sorts by parent, child enterprise name" do - er1 = create(:enterprise_relationship, parent: e1, child: e3) - er2 = create(:enterprise_relationship, parent: e2, child: e1) - er3 = create(:enterprise_relationship, parent: e1, child: e2) + it "sorts by child, parent enterprise name" do + er1 = create(:enterprise_relationship, parent: e3, child: e1) + er2 = create(:enterprise_relationship, parent: e1, child: e2) + er3 = create(:enterprise_relationship, parent: e2, child: e1) EnterpriseRelationship.by_name.should == [er3, er1, er2] end @@ -43,5 +43,24 @@ describe EnterpriseRelationship do er.permissions.should be_empty end end + + it "finds relationships that grant permissions to some enterprises" do + er1 = create(:enterprise_relationship, parent: e2, child: e1) + er2 = create(:enterprise_relationship, parent: e3, child: e2) + er3 = create(:enterprise_relationship, parent: e1, child: e3) + + EnterpriseRelationship.permitting([e1, e2]).sort.should == [er1, er2] + end + + it "finds relationships that grant a particular permission" do + er1 = create(:enterprise_relationship, parent: e1, child: e2, + permissions_list: ['one', 'two']) + er2 = create(:enterprise_relationship, parent: e2, child: e3, + permissions_list: ['two', 'three']) + er3 = create(:enterprise_relationship, parent: e3, child: e1, + permissions_list: ['three', 'four']) + + EnterpriseRelationship.with_permission('two').sort.should == [er1, er2].sort + end end end From 57af658e7c2dba603159c0f6d008aabbbaafd324 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 28 Aug 2014 15:06:55 +1000 Subject: [PATCH 20/68] For OC, fetch all enterprises we have access to, including those via E2E relationships --- .../javascripts/admin/order_cycle.js.erb.coffee | 2 +- app/controllers/admin/enterprises_controller.rb | 7 ++++++- app/models/spree/ability_decorator.rb | 1 + .../{index.rabl => for_order_cycle.rabl} | 0 config/routes.rb | 7 +++++-- spec/features/admin/order_cycles_spec.rb | 16 ++++++++++++---- spec/javascripts/unit/order_cycle_spec.js.coffee | 2 +- spec/models/spree/ability_spec.rb | 2 +- 8 files changed, 27 insertions(+), 10 deletions(-) rename app/views/admin/enterprises/{index.rabl => for_order_cycle.rabl} (100%) diff --git a/app/assets/javascripts/admin/order_cycle.js.erb.coffee b/app/assets/javascripts/admin/order_cycle.js.erb.coffee index aadf66af60..b8068681ca 100644 --- a/app/assets/javascripts/admin/order_cycle.js.erb.coffee +++ b/app/assets/javascripts/admin/order_cycle.js.erb.coffee @@ -330,7 +330,7 @@ angular.module('order_cycle', ['ngResource']) }]) .factory('Enterprise', ['$resource', ($resource) -> - Enterprise = $resource('/admin/enterprises/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}}) + Enterprise = $resource('/admin/enterprises/for_order_cycle/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}}) { Enterprise: Enterprise diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 53e58d110b..85fe6831fb 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -6,6 +6,11 @@ module Admin create.after :grant_management helper 'spree/products' + include OrderCyclesHelper + + def for_order_cycle + @collection = order_cycle_permitted_enterprises + end def bulk_update @@ -53,7 +58,7 @@ module Admin end def collection_actions - [:index, :bulk_update] + [:index, :for_order_cycle, :bulk_update] end def load_methods_and_fees diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index b29aa6d5b3..68d98bd568 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -76,6 +76,7 @@ class AbilityDecorator can [:admin, :index, :read, :edit, :update, :bulk_update, :clone], OrderCycle do |order_cycle| user.enterprises.include? order_cycle.coordinator end + can [:for_order_cycle], Enterprise can [:index, :create], EnterpriseFee can [:admin, :read, :edit, :bulk_update, :destroy], EnterpriseFee do |enterprise_fee| diff --git a/app/views/admin/enterprises/index.rabl b/app/views/admin/enterprises/for_order_cycle.rabl similarity index 100% rename from app/views/admin/enterprises/index.rabl rename to app/views/admin/enterprises/for_order_cycle.rabl diff --git a/config/routes.rb b/config/routes.rb index 36213b2622..5230e798cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,12 +33,15 @@ Openfoodnetwork::Application.routes.draw do namespace :admin do resources :order_cycles do - post :bulk_update, :on => :collection, :as => :bulk_update + post :bulk_update, on: :collection, as: :bulk_update get :clone, on: :member end resources :enterprises do - post :bulk_update, :on => :collection, :as => :bulk_update + collection do + get :for_order_cycle + post :bulk_update, as: :bulk_update + end resources :producer_properties do post :update_positions, on: :collection diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 76ee3bdfc2..febd966109 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -451,12 +451,10 @@ feature %q{ create(:enterprise_relationship, parent: distributor_permitted, child: distributor_managed, permissions_list: [:add_to_order_cycle]) end + let!(:product_managed) { create(:product, supplier: supplier_managed) } + let!(:product_permitted) { create(:product, supplier: supplier_permitted) } before do - product = create(:product, supplier: supplier_managed) - product.distributors << distributor_managed - product.save! - @new_user = create_enterprise_user @new_user.enterprise_roles.build(enterprise: supplier_managed).save @new_user.enterprise_roles.build(enterprise: distributor_managed).save @@ -492,6 +490,9 @@ feature %q{ select 'Permitted supplier', from: 'new_supplier_id' click_button 'Add supplier' + select_incoming_variant supplier_managed, 0, product_managed.master + select_incoming_variant supplier_permitted, 1, product_permitted.master + select 'Managed distributor', from: 'order_cycle_coordinator_id' click_button 'Add coordinator fee' select 'Managed distributor fee', from: 'order_cycle_coordinator_fee_0_id' @@ -574,4 +575,11 @@ feature %q{ end + + private + + def select_incoming_variant(supplier, exchange_no, variant) + page.find("table.exchanges tr.supplier-#{supplier.id} td.products input").click + check "order_cycle_incoming_exchange_#{exchange_no}_variants_#{variant.id}" + end end diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 172b2cdb1a..0d5127314a 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -327,7 +327,7 @@ describe 'OrderCycle services', -> inject ($injector, _$httpBackend_)-> Enterprise = $injector.get('Enterprise') $httpBackend = _$httpBackend_ - $httpBackend.whenGET('/admin/enterprises.json').respond [ + $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json').respond [ {id: 1, name: 'One', supplied_products: [1, 2]} {id: 2, name: 'Two', supplied_products: [3, 4]} {id: 3, name: 'Three', supplied_products: [5, 6]} diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index a2d1fb5bdf..9e5ae49780 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -264,7 +264,7 @@ module Spree end it 'should have the ability administrate and create enterpises' do - should have_ability([:admin, :index, :create], for: Enterprise) + should have_ability([:admin, :index, :for_order_cycle, :create], for: Enterprise) end end end From 6ff6e4248c407937e5bd6fcbba2e60f45266130a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 Aug 2014 17:37:56 +1000 Subject: [PATCH 21/68] Rename spec to match view name change --- .../{index.rabl_spec.rb => for_order_cycle.rabl_spec.rb} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename spec/views/admin/enterprises/{index.rabl_spec.rb => for_order_cycle.rabl_spec.rb} (79%) diff --git a/spec/views/admin/enterprises/index.rabl_spec.rb b/spec/views/admin/enterprises/for_order_cycle.rabl_spec.rb similarity index 79% rename from spec/views/admin/enterprises/index.rabl_spec.rb rename to spec/views/admin/enterprises/for_order_cycle.rabl_spec.rb index 91463c2212..1585d08e43 100644 --- a/spec/views/admin/enterprises/index.rabl_spec.rb +++ b/spec/views/admin/enterprises/for_order_cycle.rabl_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -describe "admin/enterprises/index.rabl" do +describe "admin/enterprises/for_order_cycle.rabl" do let(:enterprise) { create(:distributor_enterprise) } let!(:product) { create(:simple_product, supplier: enterprise) } let!(:deleted_product) { create(:simple_product, supplier: enterprise, deleted_at: 1.day.ago) } - let(:render) { Rabl.render([enterprise], 'admin/enterprises/index', view_path: 'app/views', scope: RablHelper::FakeContext.instance) } + let(:render) { Rabl.render([enterprise], 'admin/enterprises/for_order_cycle', view_path: 'app/views', scope: RablHelper::FakeContext.instance) } describe "supplied products" do it "does not render deleted products" do From 503fb537500a40a249c05c51fccb91355985044e Mon Sep 17 00:00:00 2001 From: digital dreamer Date: Fri, 29 Aug 2014 13:42:27 +0200 Subject: [PATCH 22/68] Fix #242 - foreign key constraint error on load_sample_data --- lib/tasks/dev.rake | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake index 8479b08ba5..bfa57a07dd 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -32,7 +32,6 @@ namespace :openfoodnetwork do # -- Addresses unless Spree::Address.find_by_zipcode "3160" puts "[#{task_name}] Seeding addresses" - Spree::Address.delete_all FactoryGirl.create(:address, :address1 => "25 Myrtle Street", :zipcode => "3153", :city => "Bayswater") FactoryGirl.create(:address, :address1 => "6 Rollings Road", :zipcode => "3156", :city => "Upper Ferntree Gully") From 1ea63bca6e14e033d05aef64894df480436385ac Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 09:17:38 +1000 Subject: [PATCH 23/68] Name test enterprises semantically --- .../admin/bulk_product_update_spec.rb | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index ccddba5508..d1dfc2b129 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -729,20 +729,20 @@ feature %q{ end context "as an enterprise manager" do - let(:s1) { create(:supplier_enterprise, name: 'First Supplier') } - let(:s2) { create(:supplier_enterprise, name: 'Another Supplier') } - let(:s3) { create(:supplier_enterprise, name: 'Yet Another Supplier') } - let(:d1) { create(:distributor_enterprise, name: 'First Distributor') } - let(:d2) { create(:distributor_enterprise, name: 'Another Distributor') } - let!(:product_supplied) { create(:product, supplier: s1, price: 10.0, on_hand: 6) } - let!(:product_not_supplied) { create(:product, supplier: s3) } - let(:product_supplied_inactive) { create(:product, supplier: s1, price: 10.0, on_hand: 6, available_on: 1.week.from_now) } + let(:supplier_managed1) { create(:supplier_enterprise, name: 'Supplier Managed 1') } + let(:supplier_managed2) { create(:supplier_enterprise, name: 'Supplier Managed 2') } + let(:supplier_unmanaged) { create(:supplier_enterprise, name: 'Supplier Unmanaged') } + let(:distributor_managed) { create(:distributor_enterprise, name: 'Distributor Managed') } + let(:distributor_unmanaged) { create(:distributor_enterprise, name: 'Distributor Unmanaged') } + let!(:product_supplied) { create(:product, supplier: supplier_managed1, price: 10.0, on_hand: 6) } + let!(:product_not_supplied) { create(:product, supplier: supplier_unmanaged) } + let(:product_supplied_inactive) { create(:product, supplier: supplier_managed1, price: 10.0, on_hand: 6, available_on: 1.week.from_now) } - before(:each) do + before do @enterprise_user = create_enterprise_user - @enterprise_user.enterprise_roles.build(enterprise: s1).save - @enterprise_user.enterprise_roles.build(enterprise: s2).save - @enterprise_user.enterprise_roles.build(enterprise: d1).save + @enterprise_user.enterprise_roles.build(enterprise: supplier_managed1).save + @enterprise_user.enterprise_roles.build(enterprise: supplier_managed2).save + @enterprise_user.enterprise_roles.build(enterprise: distributor_managed).save login_to_admin_as @enterprise_user end @@ -757,8 +757,8 @@ feature %q{ it "shows only suppliers that I manage" do visit '/admin/products/bulk_edit' - expect(page).to have_select 'producer', with_options: [s1.name, s2.name], selected: s1.name - expect(page).to have_no_select 'producer', with_options: [s3.name] + expect(page).to have_select 'producer', with_options: [supplier_managed1.name, supplier_managed2.name], selected: supplier_managed1.name + expect(page).to have_no_select 'producer', with_options: [supplier_unmanaged.name] end it "shows inactive products that I supply" do @@ -777,13 +777,13 @@ feature %q{ first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click expect(page).to have_field "product_name", with: p.name - expect(page).to have_select "producer", selected: s1.name + expect(page).to have_select "producer", selected: supplier_managed1.name expect(page).to have_field "available_on", with: p.available_on.strftime("%F %T") expect(page).to have_field "price", with: "10.0" expect(page).to have_field "on_hand", with: "6" fill_in "product_name", with: "Big Bag Of Potatoes" - select(s2.name, :from => 'producer') + select(supplier_managed2.name, :from => 'producer') fill_in "available_on", with: (Date.today-3).strftime("%F %T") fill_in "price", with: "20" select "Weight (kg)", from: "variant_unit_with_scale" @@ -795,7 +795,7 @@ feature %q{ p.reload expect(p.name).to eq "Big Bag Of Potatoes" - expect(p.supplier).to eq s2 + expect(p.supplier).to eq supplier_managed2 expect(p.variant_unit).to eq "weight" expect(p.variant_unit_scale).to eq 1000 # Kg expect(p.available_on).to eq 3.days.ago.beginning_of_day From 4af704b1d0d63e69f9241b0907ff559d5655191b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 09:42:50 +1000 Subject: [PATCH 24/68] Fetch managed products via OpenFoodNetwork::Permissions --- .../spree/api/products_controller_decorator.rb | 8 +++++++- lib/open_food_network/permissions.rb | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/spree/api/products_controller_decorator.rb b/app/controllers/spree/api/products_controller_decorator.rb index 7e81e1f789..65765e03ca 100644 --- a/app/controllers/spree/api/products_controller_decorator.rb +++ b/app/controllers/spree/api/products_controller_decorator.rb @@ -1,3 +1,5 @@ +require 'open_food_network/permissions' + Spree::Api::ProductsController.class_eval do def managed authorize! :admin, Spree::Product @@ -8,7 +10,11 @@ Spree::Api::ProductsController.class_eval do end def bulk_products - @products = product_scope.ransack(params[:q]).result.managed_by(current_api_user).page(params[:page]).per(params[:per_page]) + @products = OpenFoodNetwork::Permissions.new(current_api_user).managed_products. + merge(product_scope). + ransack(params[:q]).result. + page(params[:page]).per(params[:per_page]) + render text: { products: ActiveModel::ArraySerializer.new(@products, each_serializer: Spree::Api::ProductSerializer), pages: @products.num_pages }.to_json end diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index 99c0aa0c15..fbd85d933b 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -18,6 +18,10 @@ module OpenFoodNetwork order_cycle.exchanges.to_enterprises(enterprises).from_enterprises(enterprises) end + def managed_products + Spree::Product.managed_by(@user) + end + private From e023a6616763c958bb5325e7d965251bc0be1ad4 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 10:13:59 +1000 Subject: [PATCH 25/68] Bulk product edit lists managed products --- lib/open_food_network/permissions.rb | 12 ++++++- .../admin/bulk_product_update_spec.rb | 26 +++++++++++----- .../lib/open_food_network/permissions_spec.rb | 31 +++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index fbd85d933b..f35a7db602 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -19,7 +19,9 @@ module OpenFoodNetwork end def managed_products - Spree::Product.managed_by(@user) + managed_enterprise_products_ids = managed_enterprise_products.pluck :id + permitted_enterprise_products_ids = related_enterprise_products.pluck :id + Spree::Product.where('id IN (?)', managed_enterprise_products_ids + permitted_enterprise_products_ids) end @@ -37,5 +39,13 @@ module OpenFoodNetwork Enterprise.where('id IN (?)', parent_ids) end + + def managed_enterprise_products + Spree::Product.managed_by(@user) + end + + def related_enterprise_products + Spree::Product.where('supplier_id IN (?)', related_enterprises_with(:manage_products)) + end end end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index d1dfc2b129..90f83bb26d 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -732,12 +732,21 @@ feature %q{ let(:supplier_managed1) { create(:supplier_enterprise, name: 'Supplier Managed 1') } let(:supplier_managed2) { create(:supplier_enterprise, name: 'Supplier Managed 2') } let(:supplier_unmanaged) { create(:supplier_enterprise, name: 'Supplier Unmanaged') } + let(:supplier_permitted) { create(:supplier_enterprise, name: 'Supplier Permitted') } let(:distributor_managed) { create(:distributor_enterprise, name: 'Distributor Managed') } let(:distributor_unmanaged) { create(:distributor_enterprise, name: 'Distributor Unmanaged') } let!(:product_supplied) { create(:product, supplier: supplier_managed1, price: 10.0, on_hand: 6) } let!(:product_not_supplied) { create(:product, supplier: supplier_unmanaged) } + let!(:product_supplied_permitted) { create(:product, name: 'Product Permitted', supplier: supplier_permitted, price: 10.0, on_hand: 6) } let(:product_supplied_inactive) { create(:product, supplier: supplier_managed1, price: 10.0, on_hand: 6, available_on: 1.week.from_now) } + let!(:supplier_permitted_relationship) do + create(:enterprise_relationship, parent: supplier_permitted, child: supplier_managed1, + permissions_list: [:manage_products]) + end + + use_short_wait + before do @enterprise_user = create_enterprise_user @enterprise_user.enterprise_roles.build(enterprise: supplier_managed1).save @@ -751,6 +760,7 @@ feature %q{ visit '/admin/products/bulk_edit' expect(page).to have_field 'product_name', with: product_supplied.name + expect(page).to have_field 'product_name', with: product_supplied_permitted.name expect(page).to have_no_field 'product_name', with: product_not_supplied.name end @@ -782,13 +792,15 @@ feature %q{ expect(page).to have_field "price", with: "10.0" expect(page).to have_field "on_hand", with: "6" - fill_in "product_name", with: "Big Bag Of Potatoes" - select(supplier_managed2.name, :from => 'producer') - fill_in "available_on", with: (Date.today-3).strftime("%F %T") - fill_in "price", with: "20" - select "Weight (kg)", from: "variant_unit_with_scale" - fill_in "on_hand", with: "18" - fill_in "display_as", with: "Big Bag" + within("tr#p_#{product_supplied.id}") do + fill_in "product_name", with: "Big Bag Of Potatoes" + select(supplier_managed2.name, :from => 'producer') + fill_in "available_on", with: (Date.today-3).strftime("%F %T") + fill_in "price", with: "20" + select "Weight (kg)", from: "variant_unit_with_scale" + fill_in "on_hand", with: "18" + fill_in "display_as", with: "Big Bag" + end click_button 'Save Changes' expect(page.find("#update-status-message")).to have_content "Changes saved." diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index b0a004cbf1..bbab0d7175 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -55,6 +55,26 @@ module OpenFoodNetwork end end + describe "finding managed products" do + let!(:p1) { create(:simple_product) } + let!(:p2) { create(:simple_product) } + + before do + permissions.stub(:managed_enterprise_products) { Spree::Product.where('1=0') } + permissions.stub(:related_enterprise_products) { Spree::Product.where('1=0') } + end + + it "returns products produced by managed enterprises" do + permissions.stub(:managed_enterprise_products) { Spree::Product.where(id: p1) } + permissions.managed_products.should == [p1] + end + + it "returns products produced by permitted enterprises" do + permissions.stub(:related_enterprise_products) { Spree::Product.where(id: p2) } + permissions.managed_products.should == [p2] + end + end + ######################################## describe "finding related enterprises with a particular permission" do @@ -70,5 +90,16 @@ module OpenFoodNetwork permissions.send(:related_enterprises_with, permission).should == [] end end + + describe "finding the supplied products of related enterprises" do + let!(:e) { create(:enterprise) } + let!(:p) { create(:simple_product, supplier: e) } + + it "returns supplied products" do + permissions.should_receive(:related_enterprises_with).with(:manage_products) { [e] } + + permissions.send(:related_enterprise_products).should == [p] + end + end end end From 9ec5dc6466930e14f446cf9003c6e9be24f33c27 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 10:36:59 +1000 Subject: [PATCH 26/68] Find enterprises that we manage products for --- lib/open_food_network/permissions.rb | 17 +++++-- .../lib/open_food_network/permissions_spec.rb | 49 ++++++++++++++----- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/lib/open_food_network/permissions.rb b/lib/open_food_network/permissions.rb index f35a7db602..3d5df1e804 100644 --- a/lib/open_food_network/permissions.rb +++ b/lib/open_food_network/permissions.rb @@ -6,10 +6,7 @@ module OpenFoodNetwork # Find enterprises that an admin is allowed to add to an order cycle def order_cycle_enterprises - managed_enterprise_ids = managed_enterprises.pluck :id - permitted_enterprise_ids = related_enterprises_with(:add_to_order_cycle).pluck :id - - Enterprise.where('id IN (?)', managed_enterprise_ids + permitted_enterprise_ids) + managed_and_related_enterprises_with :add_to_order_cycle end # Find the exchanges of an order cycle that an admin can manage @@ -24,6 +21,10 @@ module OpenFoodNetwork Spree::Product.where('id IN (?)', managed_enterprise_products_ids + permitted_enterprise_products_ids) end + def managed_product_enterprises + managed_and_related_enterprises_with :manage_products + end + private @@ -40,6 +41,14 @@ module OpenFoodNetwork Enterprise.where('id IN (?)', parent_ids) end + def managed_and_related_enterprises_with(permission) + managed_enterprise_ids = managed_enterprises.pluck :id + permitted_enterprise_ids = related_enterprises_with(permission).pluck :id + + Enterprise.where('id IN (?)', managed_enterprise_ids + permitted_enterprise_ids) + end + + def managed_enterprise_products Spree::Product.managed_by(@user) end diff --git a/spec/lib/open_food_network/permissions_spec.rb b/spec/lib/open_food_network/permissions_spec.rb index bbab0d7175..bcd1f8b742 100644 --- a/spec/lib/open_food_network/permissions_spec.rb +++ b/spec/lib/open_food_network/permissions_spec.rb @@ -9,19 +9,15 @@ module OpenFoodNetwork let(:e2) { create(:enterprise) } describe "finding enterprises that can be added to an order cycle" do - before do - permissions.stub(:managed_enterprises) { Enterprise.where('1=0') } - permissions.stub(:related_enterprises_with) { Enterprise.where('1=0') } - end + let(:e) { double(:enterprise) } - it "returns managed enterprises" do - permissions.stub(:managed_enterprises) { Enterprise.where(id: e1) } - permissions.order_cycle_enterprises.should == [e1] - end + it "returns managed and related enterprises with add_to_order_cycle permission" do + permissions. + should_receive(:managed_and_related_enterprises_with). + with(:add_to_order_cycle). + and_return([e]) - it "returns permitted enterprises" do - permissions.stub(:related_enterprises_with) { Enterprise.where(id: e2) } - permissions.order_cycle_enterprises.should == [e2] + permissions.order_cycle_enterprises.should == [e] end end @@ -75,6 +71,19 @@ module OpenFoodNetwork end end + describe "finding enterprises that we manage products for" do + let(:e) { double(:enterprise) } + + it "returns managed and related enterprises with manage_products permission" do + permissions. + should_receive(:managed_and_related_enterprises_with). + with(:manage_products). + and_return([e]) + + permissions.managed_product_enterprises.should == [e] + end + end + ######################################## describe "finding related enterprises with a particular permission" do @@ -91,6 +100,24 @@ module OpenFoodNetwork end end + describe "finding enterprises that are managed or with a particular permission" do + before do + permissions.stub(:managed_enterprises) { Enterprise.where('1=0') } + permissions.stub(:related_enterprises_with) { Enterprise.where('1=0') } + end + + it "returns managed enterprises" do + permissions.should_receive(:managed_enterprises) { Enterprise.where(id: e1) } + permissions.send(:managed_and_related_enterprises_with, permission).should == [e1] + end + + it "returns permitted enterprises" do + permissions.should_receive(:related_enterprises_with).with(permission). + and_return(Enterprise.where(id: e2)) + permissions.send(:managed_and_related_enterprises_with, permission).should == [e2] + end + end + describe "finding the supplied products of related enterprises" do let!(:e) { create(:enterprise) } let!(:p) { create(:simple_product, supplier: e) } From fbd4f98fa9daa68a8ce99fe8046ac5ae29bb8582 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 10:44:09 +1000 Subject: [PATCH 27/68] Include producers I have permission to in BPE producers choice --- app/controllers/spree/admin/products_controller_decorator.rb | 2 +- spec/features/admin/bulk_product_update_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index 9f4fdbe46b..7fc9984241 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -85,7 +85,7 @@ Spree::Admin::ProductsController.class_eval do def load_bpe_data current_user.generate_spree_api_key! unless spree_current_user.spree_api_key @spree_api_key = spree_current_user.spree_api_key - @producers = Enterprise.managed_by(spree_current_user).is_primary_producer.order(:name) + @producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name @taxons = Spree::Taxon.order(:name) end end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 90f83bb26d..c4dbba32fd 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -764,10 +764,10 @@ feature %q{ expect(page).to have_no_field 'product_name', with: product_not_supplied.name end - it "shows only suppliers that I manage" do + it "shows only suppliers that I manage or have permission to" do visit '/admin/products/bulk_edit' - expect(page).to have_select 'producer', with_options: [supplier_managed1.name, supplier_managed2.name], selected: supplier_managed1.name + expect(page).to have_select 'producer', with_options: [supplier_managed1.name, supplier_managed2.name, supplier_permitted.name], selected: supplier_managed1.name expect(page).to have_no_select 'producer', with_options: [supplier_unmanaged.name] end From b7708d750b5a4108af17cd4d101fa8e71a7f5a54 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 11:15:00 +1000 Subject: [PATCH 28/68] Check authorisation for bulk update products --- .../admin/products_controller_decorator.rb | 3 +++ .../spree/admin/products_controller_spec.rb | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index 7fc9984241..a5b38126e5 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -30,6 +30,9 @@ Spree::Admin::ProductsController.class_eval do "#{string}q[#{filter[:property][:db_column]}_#{filter[:predicate][:predicate]}]=#{filter[:value]};" end + # Ensure we're authorised to update all products + product_set.collection.each { |p| authorize! :update, p } + if product_set.save redirect_to "/api/products/bulk_products?page=1;per_page=500;#{bulk_index_query}" else diff --git a/spec/controllers/spree/admin/products_controller_spec.rb b/spec/controllers/spree/admin/products_controller_spec.rb index f05be2bb69..9746ffa0e2 100644 --- a/spec/controllers/spree/admin/products_controller_spec.rb +++ b/spec/controllers/spree/admin/products_controller_spec.rb @@ -1,11 +1,28 @@ require 'spec_helper' describe Spree::Admin::ProductsController do - context "Creating a new product" do + describe "updating a product we do not have access to" do + let(:s_managed) { create(:enterprise) } + let(:s_unmanaged) { create(:enterprise) } + let(:p) { create(:simple_product, supplier: s_unmanaged, name: 'Peas') } + before do - login_as_admin + login_as_enterprise_user [s_managed] + spree_post :bulk_update, {"products" => [{"id" => p.id, "name" => "Pine nuts"}]} end + it "denies access" do + response.should redirect_to "http://test.host/unauthorized" + end + + it "does not update any product" do + p.reload.name.should_not == "Pine nuts" + end + end + + context "creating a new product" do + before { login_as_admin } + it "redirects to bulk_edit when the user hits 'create'" do s = create(:supplier_enterprise) t = create(:taxon) From fe1c60ba4709352b2f47610f6eaeb7e1eaffb72f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 11:49:09 +1000 Subject: [PATCH 29/68] Enterprise manager can edit products from enterprises it has manage_products permission on --- app/models/spree/ability_decorator.rb | 4 ++-- spec/features/admin/bulk_product_update_spec.rb | 14 +++++++------- spec/models/spree/ability_spec.rb | 17 ++++++++++++++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 68d98bd568..9f9c96cb15 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -43,12 +43,12 @@ class AbilityDecorator # Enterprise User can only access products that they are a supplier for can [:create], Spree::Product can [:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], Spree::Product do |product| - user.enterprises.include? product.supplier + OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? product.supplier end can [:create], Spree::Variant can [:admin, :index, :read, :edit, :update, :search, :destroy], Spree::Variant do |variant| - user.enterprises.include? variant.product.supplier + OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier end can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], Spree::ProductProperty diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index c4dbba32fd..9f530f860c 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -780,19 +780,19 @@ feature %q{ end it "allows me to update a product" do - p = product_supplied + p = product_supplied_permitted visit '/admin/products/bulk_edit' first("div#columns_dropdown", :text => "COLUMNS").click first("div#columns_dropdown div.menu div.menu_item", text: "Available On").click - expect(page).to have_field "product_name", with: p.name - expect(page).to have_select "producer", selected: supplier_managed1.name - expect(page).to have_field "available_on", with: p.available_on.strftime("%F %T") - expect(page).to have_field "price", with: "10.0" - expect(page).to have_field "on_hand", with: "6" + within "tr#p_#{p.id}" do + expect(page).to have_field "product_name", with: p.name + expect(page).to have_select "producer", selected: supplier_permitted.name + expect(page).to have_field "available_on", with: p.available_on.strftime("%F %T") + expect(page).to have_field "price", with: "10.0" + expect(page).to have_field "on_hand", with: "6" - within("tr#p_#{product_supplied.id}") do fill_in "product_name", with: "Big Bag Of Potatoes" select(supplier_managed2.name, :from => 'producer') fill_in "available_on", with: (Date.today-3).strftime("%F %T") diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 9e5ae49780..cf17d7fe3a 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -51,14 +51,17 @@ module Spree # create enterprises let(:s1) { create(:supplier_enterprise) } let(:s2) { create(:supplier_enterprise) } + let(:s_related) { create(:supplier_enterprise) } let(:d1) { create(:distributor_enterprise) } let(:d2) { create(:distributor_enterprise) } let(:p1) { create(:product, supplier: s1, distributors:[d1, d2]) } let(:p2) { create(:product, supplier: s2, distributors:[d1, d2]) } + let(:p_related) { create(:product, supplier: s_related) } let(:er1) { create(:enterprise_relationship, parent: s1, child: d1) } let(:er2) { create(:enterprise_relationship, parent: d1, child: s1) } + let(:er_p) { create(:enterprise_relationship, parent: s_related, child: s1, permissions_list: [:manage_products]) } subject { user } let(:user) { nil } @@ -74,12 +77,20 @@ module Spree let(:order) {create(:order)} - it "should be able to read/write their enterprises' products" do + it "should be able to read/write their enterprises' products and variants" do should have_ability([:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], for: p1) + should have_ability([:admin, :index, :read, :edit, :update, :search, :destroy], for: p1.master) end - it "should not be able to read/write other enterprises' products" do + it "should be able to read/write related enterprises' products and variants with manage_products permission" do + er_p + should have_ability([:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], for: p_related) + should have_ability([:admin, :index, :read, :edit, :update, :search, :destroy], for: p_related.master) + end + + it "should not be able to read/write other enterprises' products and variants" do should_not have_ability([:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], for: p2) + should_not have_ability([:admin, :index, :read, :edit, :update, :search, :destroy], for: p2.master) end it "should not be able to access admin actions on orders" do @@ -247,7 +258,7 @@ module Spree end end - context 'Enterprise manager' do + context 'enterprise manager' do let (:user) do user = create(:user) user.spree_roles = [] From c43c35601bf319fb018c21fa7f5e7fd33faf716f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 1 Sep 2014 16:21:23 +1000 Subject: [PATCH 30/68] Set product.largeImage in JS, use for product modal --- .../darkswarm/services/products.js.coffee | 1 + .../javascripts/templates/product_modal.html.haml | 2 +- .../unit/darkswarm/services/product_spec.js.coffee | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index 5819c7b661..9da3187f82 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -48,3 +48,4 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car product.primaryImage = product.images[0]?.small_url if product.images product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png" + product.largeImage = product.images[0]?.large_url if product.images diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index 31f51c6655..82cda0a371 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -1,6 +1,6 @@ .row .columns.small-12.large-6 - %img.product-img{"ng-src" => "{{product.primaryImage}}", "ng-if" => "product.primaryImage"} + %img.product-img{"ng-src" => "{{product.largeImage}}", "ng-if" => "product.largeImage"} .columns.small-12.large-6.product-header %h2 / %render-svg{path: "{{product.primary_taxon.icon}}"} diff --git a/spec/javascripts/unit/darkswarm/services/product_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/product_spec.js.coffee index d7c5a20b91..27e3f5aff6 100644 --- a/spec/javascripts/unit/darkswarm/services/product_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/product_spec.js.coffee @@ -7,6 +7,7 @@ describe 'Products service', -> CurrentHubMock = {} currentOrder = null product = null + productWithImage = null beforeEach -> product = @@ -16,6 +17,14 @@ describe 'Products service', -> price: 11 master: {} variants: [] + productWithImage = + supplier: + id: 9 + master: {} + variants: [] + images: [ + large_url: 'foo.png' + ] currentOrder = line_items: [] @@ -62,6 +71,11 @@ describe 'Products service', -> expect(Products.products[0].primaryImage).toBeUndefined() expect(Products.products[0].primaryImageOrMissing).toEqual "/assets/noimage/small.png" + it "sets largeImage", -> + $httpBackend.expectGET("/shop/products").respond([productWithImage]) + $httpBackend.flush() + expect(Products.products[0].largeImage).toEqual("foo.png") + describe "determining the price to display for a product", -> it "displays the product price when the product does not have variants", -> $httpBackend.expectGET("/shop/products").respond([product]) From 4d766a29ab5f7fb1dc483b583965b20db6ff8cb8 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Tue, 2 Sep 2014 13:45:11 +1000 Subject: [PATCH 31/68] Convert admin login nav to haml --- app/views/spree/layouts/admin/_login_nav.html.erb | 8 -------- app/views/spree/layouts/admin/_login_nav.html.haml | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) delete mode 100644 app/views/spree/layouts/admin/_login_nav.html.erb create mode 100644 app/views/spree/layouts/admin/_login_nav.html.haml diff --git a/app/views/spree/layouts/admin/_login_nav.html.erb b/app/views/spree/layouts/admin/_login_nav.html.erb deleted file mode 100644 index 7ab28b3344..0000000000 --- a/app/views/spree/layouts/admin/_login_nav.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -<% if spree_current_user %> -
      -
    • <%= t(:logged_in_as) %>: <%= spree_current_user.email %>
    • -
    • <%= link_to t(:account), spree.edit_user_path(spree_current_user) %>
    • -
    • <%= link_to t(:logout), spree.logout_path %>
    • -
    • <%= link_to t(:store), spree.root_path, :target => '_blank' %>
    • -
    -<% end %> diff --git a/app/views/spree/layouts/admin/_login_nav.html.haml b/app/views/spree/layouts/admin/_login_nav.html.haml new file mode 100644 index 0000000000..4ecb72d148 --- /dev/null +++ b/app/views/spree/layouts/admin/_login_nav.html.haml @@ -0,0 +1,14 @@ +- if spree_current_user + %ul#login-nav.inline-menu + %li{"data-hook" => "user-logged-in-as"} + = t(:logged_in_as) + \: #{spree_current_user.email} + %li{"data-hook" => "user-account-link"} + %i.icon-user + = link_to t(:account), spree.edit_user_path(spree_current_user) + %li{"data-hook" => "user-logout-link"} + %i.icon-signout + = link_to t(:logout), spree.logout_path + %li{"data-hook" => "store-frontend-link"} + %i.icon-external-link + = link_to t(:store), spree.root_path, :target => '_blank' From 3a12f9a7c8bc4d2246f42a4fe00b42789d27f745 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Tue, 2 Sep 2014 15:27:33 +1000 Subject: [PATCH 32/68] Extract bulk product edit interface into partials --- .../spree/admin/products/bulk_edit.html.haml | 145 +----------------- .../products/bulk_edit/_actions.html.haml | 14 ++ .../admin/products/bulk_edit/_data.html.haml | 2 + .../products/bulk_edit/_filters.html.haml | 18 +++ .../products/bulk_edit/_header.html.haml | 12 ++ .../products/bulk_edit/_indicators.html.haml | 9 ++ .../products/bulk_edit/_products.html.haml | 9 ++ .../bulk_edit/_products_head.html.haml | 28 ++++ .../bulk_edit/_products_product.html.haml | 30 ++++ .../bulk_edit/_products_variant.html.haml | 23 +++ 10 files changed, 152 insertions(+), 138 deletions(-) create mode 100644 app/views/spree/admin/products/bulk_edit/_actions.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_data.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_filters.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_header.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_indicators.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_products.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_products_head.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_products_product.html.haml create mode 100644 app/views/spree/admin/products/bulk_edit/_products_variant.html.haml diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 206a336a5e..fa322931cd 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -1,141 +1,10 @@ -- content_for :page_title do - = "Bulk Edit Products" += render 'spree/admin/products/bulk_edit/header' += render 'spree/admin/products/bulk_edit/data' -- content_for :page_actions do - %div{ :class => "toolbar", 'data-hook' => "toolbar" } - %ul{ :class => "actions header-action-links inline-menu" } - %li#new_product_link - = button_link_to t(:new_product), new_object_url, { :remote => true, :icon => 'icon-plus', :id => 'admin_new_product' } - -= render :partial => 'spree/admin/shared/product_sub_menu' - -%div#new_product(data-hook) - - -=admin_inject_producers -=admin_inject_taxons %div{ 'ng-app' => 'ofn.admin', 'ng-controller' => 'AdminProductEditCtrl', 'ng-init' => "initialise('#{@spree_api_key}');loading=true;" } - %div.sixteen.columns.alpha - %div.quick_search{ :class => "four columns alpha" } - %label{ :for => 'quick_filter' } - %br - %input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } - .filter_select{ :class => "four columns" } - %label{ :for => 'producer_filter' }Producer - %br - %select{ :class => "four columns alpha", :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } - .filter_select{ :class => "four columns" } - %label{ :for => 'category_filter' }Category - %br - %select{ :class => "four columns alpha", :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} - %div{ :class => "one column" }   - .filter_clear{ :class => "three columns omega" } - %label{ :for => 'clear_all_filters' } - %br - %input.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" } - %hr.sixteen.columns.alpha - %div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" } - %div.four.columns.alpha - %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} - %div.nine.columns - %h6{ id: "update-status-message", ng: { style: 'updateStatusMessage.style' } } - {{ updateStatusMessage.text || " " }} - %div.three.columns.omega - %div.ofn_drop_down.three.columns.omega{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } - %span{ :class => 'icon-reorder' }   Columns - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } - %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} - %span{ :class => 'two columns omega' } {{column.name }} - %div{ 'ng-show' => '!spree_api_key_ok' } - {{ api_error_msg }} - %div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' } - %img.spinner{ src: "/assets/loading.gif" } - %h1 LOADING PRODUCTS - %div.sixteen.columns.alpha{ 'ng-show' => '!loading && filteredProducts.length == 0' } - %h1#no_results No products found. - %div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' } - %table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" } - %colgroup - %col.actions - %col.producer{ ng: { show: 'columns.producer.visible' } } - %col.name{ ng: { show: 'columns.name.visible' } } - %col.unit{ ng: { show: 'columns.unit.visible' } } - %col.display_as{ ng: { show: 'columns.unit.visible' } } - %col.price{ ng: { show: 'columns.price.visible'} } - %col.on_hand{ ng: { show: 'columns.on_hand.visible' } } - %col.category{ ng: { show: 'columns.category.visible' } } - %col.available_on{ ng: { show: 'columns.available_on.visible' } } - %col.actions - %col.actions - %col.actions - %thead - %tr - %th.left-actions - %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer - %th.name{ 'ng-show' => 'columns.name.visible' } Name - %th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value - %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As - %th.price{ 'ng-show' => 'columns.price.visible' } Price - %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand - %th.category{ 'ng-show' => 'columns.category.visible' } Category - %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On - %th.actions - %th.actions - %th.actions - %tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } - %tr.product{ :id => "p_{{product.id}}" } - %td.left-actions - %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' } - %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" } - %td.producer{ 'ng-show' => 'columns.producer.visible' } - %select.select2.fullwidth{ 'ng-model' => 'product.producer', :name => 'producer', 'ofn-track-product' => 'producer', 'ng-options' => 'producer.id as producer.name for producer in producers' } - %td.name{ 'ng-show' => 'columns.name.visible' } - %input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' } - %td.unit{ 'ng-show' => 'columns.unit.visible' } - %select.select2{ 'ng-model' => 'product.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-product' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } - %option{'value' => '', 'ng-hide' => "hasVariants(product) && hasUnit(product)"} - %input{ 'ng-model' => 'product.master.unit_value_with_description', :name => 'master_unit_value_with_description', 'ofn-track-master' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "!hasVariants(product) && hasUnit(product)", 'ofn-maintain-unit-scale' => true } - %input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' } - %td.display_as{ 'ng-show' => 'columns.unit.visible' } - %input{ 'ofn-display-as' => 'product.master', name: 'display_as', 'ofn-track-master' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}', ng: { hide: 'hasVariants(product)', model: 'product.master.display_as' } } - %td.price{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } - %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } - %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } - %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } - %td.category{ 'ng-if' => 'columns.category.visible' } - %input.fullwidth{ :type => 'text', id: "p{{product.id}}_category", 'ng-model' => 'product.category', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category' } - %td.available_on{ 'ng-show' => 'columns.available_on.visible' } - %input{ 'ng-model' => 'product.available_on', :name => 'available_on', 'ofn-track-product' => 'available_on', 'datetimepicker' => 'product.available_on', type: "text" } - %td.actions - %a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text" } - %td.actions - %a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text" } - %td.actions - %a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text" } - %tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'displayProperties[product.id].showVariants', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } - %td.left-actions - %a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" } - %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" } - %td{ 'ng-show' => 'columns.producer.visible' } - %td{ 'ng-show' => 'columns.name.visible' } - %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } - %td.unit_value{ 'ng-show' => 'columns.unit.visible' } - %input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', 'ofn-maintain-unit-scale' => true } - %td.display_as{ 'ng-show' => 'columns.unit.visible' } - %input{ 'ofn-display-as' => 'variant', 'ng-model' => 'variant.display_as', name: 'variant_display_as', 'ofn-track-variant' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}' } - %td{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } - %td{ 'ng-show' => 'columns.on_hand.visible' } - %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } - %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } - %td{ 'ng-show' => 'columns.category.visible' } - %td{ 'ng-show' => 'columns.available_on.visible' } - %td.actions - %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" } - %td.actions - %td.actions - %a{ 'ng-click' => 'deleteVariant(product,variant)', :class => "delete-variant icon-trash no-text" } + = render 'spree/admin/products/bulk_edit/filters' + %hr.sixteen.columns.alpha + = render 'spree/admin/products/bulk_edit/actions' + = render 'spree/admin/products/bulk_edit/indicators' + = render 'spree/admin/products/bulk_edit/products' diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml new file mode 100644 index 0000000000..d86f16bc09 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml @@ -0,0 +1,14 @@ +%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" } + %div.four.columns.alpha + %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} + %div.nine.columns + %h6{ id: "update-status-message", ng: { style: 'updateStatusMessage.style' } } + {{ updateStatusMessage.text || " " }} + %div.three.columns.omega + %div.ofn_drop_down.three.columns.omega{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' } + %span{ :class => 'icon-reorder' }   Columns + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true } + %span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }} + %span{ :class => 'two columns omega' } {{column.name }} diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml new file mode 100644 index 0000000000..25d595bda1 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml @@ -0,0 +1,2 @@ += admin_inject_producers += admin_inject_taxons diff --git a/app/views/spree/admin/products/bulk_edit/_filters.html.haml b/app/views/spree/admin/products/bulk_edit/_filters.html.haml new file mode 100644 index 0000000000..beee7d0a6a --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_filters.html.haml @@ -0,0 +1,18 @@ +%div.sixteen.columns.alpha + %div.quick_search{ :class => "four columns alpha" } + %label{ :for => 'quick_filter' } + %br + %input.search{ :class => "four columns alpha", 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => 'Quick Search' } + .filter_select{ :class => "four columns" } + %label{ :for => 'producer_filter' }Producer + %br + %select{ :class => "four columns alpha", :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } + .filter_select{ :class => "four columns" } + %label{ :for => 'category_filter' }Category + %br + %select{ :class => "four columns alpha", :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} + %div{ :class => "one column" }   + .filter_clear{ :class => "three columns omega" } + %label{ :for => 'clear_all_filters' } + %br + %input.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" } diff --git a/app/views/spree/admin/products/bulk_edit/_header.html.haml b/app/views/spree/admin/products/bulk_edit/_header.html.haml new file mode 100644 index 0000000000..09586b3e7c --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_header.html.haml @@ -0,0 +1,12 @@ +- content_for :page_title do + = "Bulk Edit Products" + +- content_for :page_actions do + %div{ :class => "toolbar", 'data-hook' => "toolbar" } + %ul{ :class => "actions header-action-links inline-menu" } + %li#new_product_link + = button_link_to t(:new_product), new_object_url, { :remote => true, :icon => 'icon-plus', :id => 'admin_new_product' } + += render :partial => 'spree/admin/shared/product_sub_menu' + +%div#new_product(data-hook) diff --git a/app/views/spree/admin/products/bulk_edit/_indicators.html.haml b/app/views/spree/admin/products/bulk_edit/_indicators.html.haml new file mode 100644 index 0000000000..97c8a45aa2 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_indicators.html.haml @@ -0,0 +1,9 @@ +%div{ 'ng-show' => '!spree_api_key_ok' } + {{ api_error_msg }} + +%div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' } + %img.spinner{ src: "/assets/loading.gif" } + %h1 LOADING PRODUCTS + +%div.sixteen.columns.alpha{ 'ng-show' => '!loading && filteredProducts.length == 0' } + %h1#no_results No products found. diff --git a/app/views/spree/admin/products/bulk_edit/_products.html.haml b/app/views/spree/admin/products/bulk_edit/_products.html.haml new file mode 100644 index 0000000000..81fb31573b --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_products.html.haml @@ -0,0 +1,9 @@ +%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' } + %table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" } + + = render 'spree/admin/products/bulk_edit/products_head' + + %tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } + + = render 'spree/admin/products/bulk_edit/products_product' + = render 'spree/admin/products/bulk_edit/products_variant' diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml new file mode 100644 index 0000000000..d6be2a88e0 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -0,0 +1,28 @@ +%colgroup + %col.actions + %col.producer{ ng: { show: 'columns.producer.visible' } } + %col.name{ ng: { show: 'columns.name.visible' } } + %col.unit{ ng: { show: 'columns.unit.visible' } } + %col.display_as{ ng: { show: 'columns.unit.visible' } } + %col.price{ ng: { show: 'columns.price.visible'} } + %col.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %col.category{ ng: { show: 'columns.category.visible' } } + %col.available_on{ ng: { show: 'columns.available_on.visible' } } + %col.actions + %col.actions + %col.actions + +%thead + %tr + %th.left-actions + %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer + %th.name{ 'ng-show' => 'columns.name.visible' } Name + %th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value + %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As + %th.price{ 'ng-show' => 'columns.price.visible' } Price + %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand + %th.category{ 'ng-show' => 'columns.category.visible' } Category + %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On + %th.actions + %th.actions + %th.actions diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml new file mode 100644 index 0000000000..ee2c1dc136 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -0,0 +1,30 @@ +%tr.product{ :id => "p_{{product.id}}" } + %td.left-actions + %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' } + %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" } + %td.producer{ 'ng-show' => 'columns.producer.visible' } + %select.select2.fullwidth{ 'ng-model' => 'product.producer', :name => 'producer', 'ofn-track-product' => 'producer', 'ng-options' => 'producer.id as producer.name for producer in producers' } + %td.name{ 'ng-show' => 'columns.name.visible' } + %input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' } + %td.unit{ 'ng-show' => 'columns.unit.visible' } + %select.select2{ 'ng-model' => 'product.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-product' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } + %option{'value' => '', 'ng-hide' => "hasVariants(product) && hasUnit(product)"} + %input{ 'ng-model' => 'product.master.unit_value_with_description', :name => 'master_unit_value_with_description', 'ofn-track-master' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "!hasVariants(product) && hasUnit(product)", 'ofn-maintain-unit-scale' => true } + %input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' } + %td.display_as{ 'ng-show' => 'columns.unit.visible' } + %input{ 'ofn-display-as' => 'product.master', name: 'display_as', 'ofn-track-master' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}', ng: { hide: 'hasVariants(product)', model: 'product.master.display_as' } } + %td.price{ 'ng-show' => 'columns.price.visible' } + %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } + %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } + %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } + %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } + %td.category{ 'ng-if' => 'columns.category.visible' } + %input.fullwidth{ :type => 'text', id: "p{{product.id}}_category", 'ng-model' => 'product.category', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category' } + %td.available_on{ 'ng-show' => 'columns.available_on.visible' } + %input{ 'ng-model' => 'product.available_on', :name => 'available_on', 'ofn-track-product' => 'available_on', 'datetimepicker' => 'product.available_on', type: "text" } + %td.actions + %a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text" } + %td.actions + %a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text" } + %td.actions + %a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text" } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml new file mode 100644 index 0000000000..6369da0d99 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -0,0 +1,23 @@ +%tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'displayProperties[product.id].showVariants', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } + %td.left-actions + %a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" } + %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" } + %td{ 'ng-show' => 'columns.producer.visible' } + %td{ 'ng-show' => 'columns.name.visible' } + %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } + %td.unit_value{ 'ng-show' => 'columns.unit.visible' } + %input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', 'ofn-maintain-unit-scale' => true } + %td.display_as{ 'ng-show' => 'columns.unit.visible' } + %input{ 'ofn-display-as' => 'variant', 'ng-model' => 'variant.display_as', name: 'variant_display_as', 'ofn-track-variant' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}' } + %td{ 'ng-show' => 'columns.price.visible' } + %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } + %td{ 'ng-show' => 'columns.on_hand.visible' } + %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } + %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } + %td{ 'ng-show' => 'columns.category.visible' } + %td{ 'ng-show' => 'columns.available_on.visible' } + %td.actions + %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" } + %td.actions + %td.actions + %a{ 'ng-click' => 'deleteVariant(product,variant)', :class => "delete-variant icon-trash no-text" } From 99fb9c19669dd2ed792baa34222bdb5b87250000 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 15:09:44 +1000 Subject: [PATCH 33/68] Adding in markup and styling for new layout of hubs list view --- .../stylesheets/darkswarm/hub_node.css.sass | 86 ++++++++++--------- app/views/home/_skinny.html.haml | 14 +-- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index 8906d2cfa2..d2a0de4383 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -10,23 +10,33 @@ overflow-x: hidden overflow-y: visible - //Hub icon styline + //Generic text link style + a:hover, a:active, a:focus + color: $clr-brick-bright + + //Hub and Producer icons // i.ofn-i_040-hub - i.ofn-i_063-hub + i.ofn-i_063-hub, i.ofn-i_064-hub-reversed, i.ofn-i_059-producer, i.ofn-i_060-producer-reversed font-size: 2rem display: inline-block margin-right: 0.25rem float: left @media all and (max-width: 768px) - font-size: 1rem + // font-size: 1rem - //Generic text link style - a:hover, a:active, a:focus - color: $clr-brick-bright + //Closed & Open column + .open_closed + i + font-size: 2rem + float: right + margin-left: 0.5rem - .hub span.hub-name-listing - margin-top: 0.5rem + span.margin-top + margin-top: 0.5rem display: inline-block + + //Hub Name + span.hub-name-listing float: left font-weight: 700 @@ -42,38 +52,35 @@ &.closed, &.open .active_table_row:first-child .skinny-head background-color: white - + &.current + &.closed, &.open + .active_table_row:first-child .skinny-head + background-color: $clr-brick-bright + &.current + &.inactive + &.closed, &.open + .active_table_row:first-child .skinny-head + background-color: #555 + //Inactive row &.inactive, &.inactive strong color: $disabled-dark - &, & * color: $disabled-dark a i.ofn-i_040-hub color: $disabled-dark - - &.current + &.current + &.inactive &.closed, &.open - - a, a strong, a span, a i - color: $disabled-dark - &:hover, &:focus, &:active - color: $disabled-dark - - a i.ofn-i_040-hub - color: white + .active_table_row:first-child background-color: $disabled-dark - a:hover, a:focus, a:active - color: $disabled-dark - i.ofn-i_040-hub - background-color: $disabled-dark - + &, & * + color: white &.closed &:hover, &:active, &:focus border: none color: $disabled-dark - &.open .active_table_row:first-child color: $disabled-dark @@ -86,7 +93,7 @@ .active_table_row:nth-child(2) background-color: rgba(255, 255, 255, 0) - //Open row + //Padding second row &.open .active_table_row:nth-child(2) padding-bottom: 0.75rem @@ -94,18 +101,19 @@ //Current selected row &.current //overwrites active_table - &.closed - &, & * - color: white - &.closed, &.open - div.active_table_row + .active_table_row:first-child background-color: $clr-brick - a:hover, a:focus, a:active - strong, span - color: $clr-brick-light - - - - + opacity: 1 + &:hover, &:focus, &:active + opacity: 0.9 + &, & * + color: white + &.open, &.closed + .active_table_row + border-color: $clr-brick + &.inactive + &.open, &.closed + .active_table_row + border-color: $disabled-dark diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index 36e3f7fc08..fa822e5358 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -3,21 +3,21 @@ %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i{ ng: { class: "{'ofn-i_063-hub': hub.can_aggregate, 'ofn-i_059-producer': !hub.can_aggregate}" } } / %i.ofn-i_063-hub - %span.hub-name-listing {{ hub.name | truncate:40}} + %span.margin-top.hub-name-listing {{ hub.name | truncate:40}} .columns.small-4.medium-2.large-2 - {{ hub.address.city }} + %span.margin-top {{ hub.address.city }} .columns.small-2.medium-1.large-1 - {{ hub.address.state_name | uppercase }} + %span.margin-top {{ hub.address.state_name | uppercase }} .columns.small-6.medium-3.large-4.text-right{"bo-if" => "hub.active"} - %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} + %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_033-open-sign - %span {{ hub.orders_close_at | sensible_timeframe }} + %span.margin-top {{ hub.orders_close_at | sensible_timeframe }} .columns.small-6.medium-3.large-4.text-right{"bo-if" => "!hub.active"} - %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} + %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_032-closed-sign - %span Orders closed + %span.margin-top Orders closed From 59adae5c9c6fba897fd58be5252cf7124da1c6fa Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 15:28:32 +1000 Subject: [PATCH 34/68] Working on space placeholders for fat view --- app/views/home/_fat.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index f3f74cb282..406431b9b7 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -6,7 +6,7 @@ %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"} %render-svg{path: "{{taxon.icon}}"} {{taxon.name}} - %div{"bo-if" => "!hub.taxons"} + %div.show-for-medium-up{"bo-if" => "hub.taxons.length==0"}   .columns.small-12.medium-3.large-2.fat %div{"bo-if" => "hub.pickup || hub.delivery"} @@ -27,5 +27,5 @@ %i.ofn-i_036-producers %span {{ enterprise.name }} - %div{"bo-if" => "!hub.producers"} + %div.show-for-medium-up{"bo-if" => "hub.producers.length==0"}   From 26bd1000de7e694eba059f2998c3b07ca14f5b3b Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 15:28:50 +1000 Subject: [PATCH 35/68] Changing placeholder for search input --- app/views/home/_hubs.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index 099e19a099..88c2909614 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -14,7 +14,7 @@ / %i.ofn-i_020-search %input{type: :text, "ng-model" => "query", - placeholder: "Search by Shop or Suburb name", + placeholder: "Search by Shop, Suburb or Postcode...", "ng-debounce" => "150", "ofn-disable-enter" => true} From a092e972946f13e31069b7bdbaadfa5e701052a7 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 15:47:14 +1000 Subject: [PATCH 36/68] Adding a little bit of spacing between each row for active table --- app/assets/stylesheets/darkswarm/active_table.css.sass | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/darkswarm/active_table.css.sass b/app/assets/stylesheets/darkswarm/active_table.css.sass index 131836da4e..4143da4f01 100644 --- a/app/assets/stylesheets/darkswarm/active_table.css.sass +++ b/app/assets/stylesheets/darkswarm/active_table.css.sass @@ -16,6 +16,7 @@ padding: 1rem 0 .active_table .active_table_node + margin-top: 0.25rem display: block border: 0 From 0d0ffa1cd06a6e6591def9d90a8fa82a7e33f729 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 15:47:41 +1000 Subject: [PATCH 37/68] Making CTA links consistent with modals, adding in logic to force space holding for producer fat view --- app/views/producers/_fat.html.haml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 042e39dba9..4cbcc431fe 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -6,16 +6,22 @@ %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} %render-svg{path: "{{taxon.icon}}"} {{taxon.name}} + %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"} +   .columns.small-12.medium-3.large-3.fat %div{"bo-if" => "producer.logo"} %img{src: "{{ producer.logo }}" } + %div.show-for-medium-up{"bo-if" => "!producer.logo"} +   .columns.small-12.medium-5.large-5.fat %div{"bo-if" => "producer.description"} %label About us %p.text-small {{ producer.description }} + %div.show-for-medium-up{"bo-if" => "!producer.description"} +   .row.active_table_row{"ng-show" => "open()", "bo-if" => "producer.hubs"} .columns.small-12 @@ -26,13 +32,14 @@ Shop for %span.turquoise {{ producer.name }} products at: + %div.show-for-medium-up{"bo-if" => "!producer.name"} +   .row.cta-container .columns.small-12 - %a.button.hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'", + %a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'", "bo-href" => "hub.path", "ofn-empties-cart" => "hub", "bo-class" => "{primary: hub.active, secondary: !hub.active}"} %i.ofn-i_033-open-sign{"bo-if" => "hub.active"} %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} - {{hub.name}} + .hub-name {{hub.name}} .button-address {{ [hub.address.city, hub.address.state_name] | printArray }} - %i.ofn-i_007-caret-right From 428e4c63d479b1e7e032cf806da169af4f7a26ac Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 16:18:25 +1000 Subject: [PATCH 38/68] Profile hubs don't show up in hubs list --- app/assets/javascripts/darkswarm/services/hubs.js.coffee | 2 +- app/views/home/_skinny.html.haml | 7 +++++-- app/views/shared/components/_filter_controls.html.haml | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/hubs.js.coffee b/app/assets/javascripts/darkswarm/services/hubs.js.coffee index ac7dc3a0eb..de9900866f 100644 --- a/app/assets/javascripts/darkswarm/services/hubs.js.coffee +++ b/app/assets/javascripts/darkswarm/services/hubs.js.coffee @@ -2,7 +2,7 @@ Darkswarm.factory 'Hubs', ($filter, Enterprises, visibleFilter) -> new class Hubs constructor: -> @hubs = @order Enterprises.enterprises.filter (hub)-> - hub.is_distributor + hub.is_distributor && hub.has_shopfront @visible = visibleFilter @hubs order: (hubs)-> diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index fa822e5358..3c5654437c 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,7 +1,10 @@ .row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}", bindonce: true} .columns.small-12.medium-6.large-5.skinny-head %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} - %i{ ng: { class: "{'ofn-i_063-hub': hub.can_aggregate, 'ofn-i_059-producer': !hub.can_aggregate}" } } + %i{ ng: { class: "{ 'ofn-i_063-hub': hub.can_aggregate && hub.has_shopfront, + 'ofn-i_059-producer': !hub.can_aggregate && hub.has_shopfront, + 'ofn-i_060-producer-reversed': !hub.can_aggregate && !hub.has_shopfront, + 'ofn-i_064-hub-reversed': hub.can_aggregate && !hub.has_shopfront }" } } / %i.ofn-i_063-hub %span.margin-top.hub-name-listing {{ hub.name | truncate:40}} .columns.small-4.medium-2.large-2 @@ -12,7 +15,7 @@ .columns.small-6.medium-3.large-4.text-right{"bo-if" => "hub.active"} %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_033-open-sign - %span.margin-top {{ hub.orders_close_at | sensible_timeframe }} + %span.margin-top {{ hub.orders_close_at | sensible_timeframe }} .columns.small-6.medium-3.large-4.text-right{"bo-if" => "!hub.active"} %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} diff --git a/app/views/shared/components/_filter_controls.html.haml b/app/views/shared/components/_filter_controls.html.haml index b4dff8a0ce..fb7e298bac 100644 --- a/app/views/shared/components/_filter_controls.html.haml +++ b/app/views/shared/components/_filter_controls.html.haml @@ -8,6 +8,6 @@ %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} No filters - .small-12.medium-6.columns.text-right - %input{type: "checkbox", name: "profile"}>< - %label Show profiles \ No newline at end of file + -# .small-12.medium-6.columns.text-right + -# %input{type: "checkbox", name: "profile"}>< + -# %label Show profiles \ No newline at end of file From 097367a9f82cea49491e9060eabc8b091196da31 Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 16:28:08 +1000 Subject: [PATCH 39/68] Map modals for 'profile' enterprises do not display shopping options --- app/assets/javascripts/templates/partials/hub_actions.html.haml | 2 +- app/assets/javascripts/templates/partials/hub_details.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/templates/partials/hub_actions.html.haml b/app/assets/javascripts/templates/partials/hub_actions.html.haml index 42a8b8cd28..fa9ff4e183 100644 --- a/app/assets/javascripts/templates/partials/hub_actions.html.haml +++ b/app/assets/javascripts/templates/partials/hub_actions.html.haml @@ -1,4 +1,4 @@ -.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0"} +.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0 && enterprise.has_shopfront"} .cta-container.small-12.columns %label Shop for diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml index 43e75dbbd2..27933cefcb 100644 --- a/app/assets/javascripts/templates/partials/hub_details.html.haml +++ b/app/assets/javascripts/templates/partials/hub_details.html.haml @@ -1,4 +1,4 @@ -.row.pad-top{bindonce: true} +.row.pad-top{bindonce: true, ng: { if: 'enterprise.has_shopfront' } } .cta-container.small-12.columns .row .small-4.columns From b3f97479d39998c8e8c085146582202d4330a57a Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 16:36:16 +1000 Subject: [PATCH 40/68] Hide profile filter from search for the mo --- .../shared/components/_filter_controls.html.haml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/views/shared/components/_filter_controls.html.haml b/app/views/shared/components/_filter_controls.html.haml index fb7e298bac..d35727c087 100644 --- a/app/views/shared/components/_filter_controls.html.haml +++ b/app/views/shared/components/_filter_controls.html.haml @@ -8,6 +8,12 @@ %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} No filters - -# .small-12.medium-6.columns.text-right - -# %input{type: "checkbox", name: "profile"}>< - -# %label Show profiles \ No newline at end of file + .small-12.medium-6.columns.text-right + .profile-checkbox + + / Hide until we're ready to work on this: + + / %input{type: "checkbox", name: "profile"}>< + / %label Show profiles + / %button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but they may have their own physical or online shop elsewhere", "popover-placement" => "left"}>< + / %i.ofn-i_013-help From adb8b6b02fbee63d4056138fb8c4773f71086789 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 16:37:49 +1000 Subject: [PATCH 41/68] Styling for help icon --- app/assets/stylesheets/darkswarm/ui.css.sass | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/assets/stylesheets/darkswarm/ui.css.sass b/app/assets/stylesheets/darkswarm/ui.css.sass index a4d70392bb..3bbce61552 100644 --- a/app/assets/stylesheets/darkswarm/ui.css.sass +++ b/app/assets/stylesheets/darkswarm/ui.css.sass @@ -63,6 +63,20 @@ button.success, .button.success .button.success:hover, .button.success:active, .button.success:focus, button.success:hover, button.success:active, button.success:focus background: #14b6cc +.button.help-btn + @include border-radius(999999px) + &.tiny + padding: 0rem + margin: 0 + float: right + i + font-size: 1.25rem + +.profile-checkbox + display: inline-block + input[type="checkbox"] + label + margin: 0 0.2rem + // Responsive @media screen and (min-width: 768px) [role="main"] From 60ac3f29a5ff45e73981f97a488d7f0513525cb5 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 16:38:05 +1000 Subject: [PATCH 42/68] Producers page WIP --- app/views/producers/_fat.html.haml | 42 +++++++++++++++++++-------- app/views/producers/_skinny.html.haml | 3 +- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 4cbcc431fe..22e7dc4589 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -1,12 +1,12 @@ .row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle()", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} + .columns.small-12.medium-4.large-4.fat - %div{"ng-if" => "producer.supplied_taxons"} - %label Shop for - %p.trans-sentence - %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} - %render-svg{path: "{{taxon.icon}}"} - {{taxon.name}} - %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"} + / No long description available because it spits out HTML formatting producer.long_description + %div{"bo-if" => "producer.long_description"} + %label About us + %p.text-small + {{ producer.long_description }} + %div.show-for-medium-up{"bo-if" => "!producer.description"}   .columns.small-12.medium-3.large-3.fat @@ -16,13 +16,31 @@   .columns.small-12.medium-5.large-5.fat - %div{"bo-if" => "producer.description"} - %label About us - %p.text-small - {{ producer.description }} - %div.show-for-medium-up{"bo-if" => "!producer.description"} + %div{"ng-if" => "producer.supplied_taxons"} + %label Shop for + %p.trans-sentence + %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} + %render-svg{path: "{{taxon.icon}}"} + {{taxon.name}} + %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}   + %div{"ng-if" => "producer.email || producer.website || producer.phone"} + %label Contact + %p{"ng-if" => "producer.phone"} + {{ enterprise.phone }} + + %p.word-wrap{"ng-if" => "producer.email"} + %a{"ng-href" => "{{producer.email | stripUrl}}", target: "_blank", mailto: true} + %span.email + {{ producer.email | stripUrl }} + + %p.word-wrap{"ng-if" => "producer.website"} + %a{"ng-href" => "http://{{producer.website | stripUrl}}", target: "_blank" } + {{ producer.website | stripUrl }} + + + .row.active_table_row{"ng-show" => "open()", "bo-if" => "producer.hubs"} .columns.small-12 .row diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index e1f26f0304..beded1a14b 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -1,6 +1,7 @@ .row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}"} .columns.small-12.medium-4.large-4.skinny-head - %i.ofn-i_036-producers + / This needs logic to show profile only icon when available %i.ofn-i_060-producer-reversed + %i.ofn-i_059-producer %strong {{ producer.name }} .columns.small-6.medium-3.large-3 {{ producer.address.city }} From 20bfcd6e48adeaa6de0cac2c6f968c0fcc1859e4 Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 17:11:57 +1000 Subject: [PATCH 43/68] Switching enterprise relationships form around --- .../enterprise_relationships.js.coffee | 4 ++-- .../_enterprise_relationship.html.haml | 18 ++++++++++-------- .../enterprise_relationships/_form.html.haml | 7 +++++-- .../enterprise_relationships/index.html.haml | 3 +-- .../admin/enterprise_relationships_spec.rb | 18 +++++++++--------- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee b/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee index cad556efd8..16c9c38fea 100644 --- a/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee +++ b/app/assets/javascripts/admin/services/enterprise_relationships.js.coffee @@ -24,5 +24,5 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris permission_presentation: (permission) -> switch permission - when "add_to_order_cycle" then "can add to order cycle" - when "manage_products" then "can manage the products of" + when "add_to_order_cycle" then "to add to order cycle" + when "manage_products" then "to manage products" diff --git a/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml b/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml index 6a7dcdc7f4..45e79b1877 100644 --- a/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml +++ b/app/views/admin/enterprise_relationships/_enterprise_relationship.html.haml @@ -1,8 +1,10 @@ -%td {{ enterprise_relationship.child_name }} -%td - %ul - %li{"ng-repeat" => "permission in enterprise_relationship.permissions"} - {{ EnterpriseRelationships.permission_presentation(permission.name) }} -%td {{ enterprise_relationship.parent_name }} -%td.actions - %a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'} +%tr{"ng-repeat" => "enterprise_relationship in EnterpriseRelationships.enterprise_relationships | filter:query"} + %td {{ enterprise_relationship.parent_name }} + %td permits + %td {{ enterprise_relationship.child_name }} + %td + %ul + %li{"ng-repeat" => "permission in enterprise_relationship.permissions"} + {{ EnterpriseRelationships.permission_presentation(permission.name) }} + %td.actions + %a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'} diff --git a/app/views/admin/enterprise_relationships/_form.html.haml b/app/views/admin/enterprise_relationships/_form.html.haml index 86086c8781..9d031a57c5 100644 --- a/app/views/admin/enterprise_relationships/_form.html.haml +++ b/app/views/admin/enterprise_relationships/_form.html.haml @@ -1,4 +1,9 @@ %tr + %td + %select{name: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"} + + %td + permits %td %select{name: "enterprise_relationship_child_id", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.all_enterprises"} %td @@ -6,8 +11,6 @@ %label %input{type: "checkbox", "ng-model" => "permissions[permission]"} {{ EnterpriseRelationships.permission_presentation(permission) }} - %td - %select{name: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"} %td.actions %input{type: "button", value: "Create", "ng-click" => "create()"} .errors {{ EnterpriseRelationships.create_errors }} diff --git a/app/views/admin/enterprise_relationships/index.html.haml b/app/views/admin/enterprise_relationships/index.html.haml index e0e289efcf..0807a37825 100644 --- a/app/views/admin/enterprise_relationships/index.html.haml +++ b/app/views/admin/enterprise_relationships/index.html.haml @@ -11,5 +11,4 @@ %table#enterprise-relationships %tbody = render 'form' - %tr{"ng-repeat" => "enterprise_relationship in EnterpriseRelationships.enterprise_relationships | filter:query"} - = render 'enterprise_relationship' + = render 'enterprise_relationship' diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index 7317f44066..007f663db0 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -24,10 +24,10 @@ feature %q{ # Then I should see the relationships within('table#enterprise-relationships') do - page.should have_relationship e1, e2, ['can add to order cycle'] - page.should have_relationship e2, e3, ['can manage the products of'] + page.should have_relationship e1, e2, ['to add to order cycle'] + page.should have_relationship e2, e3, ['to manage products'] page.should have_relationship e3, e4, - ['can add to order cycle', 'can manage the products of'] + ['to add to order cycle', 'to manage products'] end end @@ -39,13 +39,13 @@ feature %q{ visit admin_enterprise_relationships_path select 'One', from: 'enterprise_relationship_parent_id' - check 'can add to order cycle' - check 'can manage the products of' - uncheck 'can manage the products of' + check 'to add to order cycle' + check 'to manage products' + uncheck 'to manage products' select 'Two', from: 'enterprise_relationship_child_id' click_button 'Create' - page.should have_relationship e1, e2, ['can add to order cycle'] + page.should have_relationship e1, e2, ['to add to order cycle'] er = EnterpriseRelationship.where(parent_id: e1, child_id: e2).first er.should be_present er.permissions.map(&:name).should == ['add_to_order_cycle'] @@ -118,8 +118,8 @@ feature %q{ private def have_relationship(parent, child, perms=[]) - perms = perms.join(' ') || 'permits' + perms = perms.join(' ') - have_table_row [child.name, perms, parent.name, ''] + have_table_row [parent.name, 'permits', child.name, perms, ''] end end From a9619a6dca144b27194b61f62fc59867e66d445e Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 17:50:50 +1000 Subject: [PATCH 44/68] Changes to markup on producer page to adjust layout --- app/views/producers/_fat.html.haml | 47 ++++++++++++++++++--------- app/views/producers/_skinny.html.haml | 12 ++++--- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 22e7dc4589..a8512c9a22 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -1,47 +1,61 @@ .row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle()", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} - .columns.small-12.medium-4.large-4.fat + .columns.small-12.medium-7.large-7.fat / No long description available because it spits out HTML formatting producer.long_description %div{"bo-if" => "producer.long_description"} %label About us + %img.right.show-for-medium-up{src: "{{ producer.logo }}" } %p.text-small {{ producer.long_description }} - %div.show-for-medium-up{"bo-if" => "!producer.description"} -   - - .columns.small-12.medium-3.large-3.fat - %div{"bo-if" => "producer.logo"} - %img{src: "{{ producer.logo }}" } - %div.show-for-medium-up{"bo-if" => "!producer.logo"} -   + %div.show-for-medium-up{"bo-if" => "producer.long_description.length==0"} + %label   .columns.small-12.medium-5.large-5.fat + %div{"ng-if" => "producer.supplied_taxons"} %label Shop for %p.trans-sentence %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} %render-svg{path: "{{taxon.icon}}"} {{taxon.name}} + %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}   %div{"ng-if" => "producer.email || producer.website || producer.phone"} %label Contact - %p{"ng-if" => "producer.phone"} - {{ enterprise.phone }} + + %p.word-wrap{"ng-if" => "producer.phone"} + Call {{ producer.phone }} %p.word-wrap{"ng-if" => "producer.email"} %a{"ng-href" => "{{producer.email | stripUrl}}", target: "_blank", mailto: true} - %span.email - {{ producer.email | stripUrl }} + %span.email {{ producer.email | stripUrl }} %p.word-wrap{"ng-if" => "producer.website"} %a{"ng-href" => "http://{{producer.website | stripUrl}}", target: "_blank" } - {{ producer.website | stripUrl }} + %span {{ producer.website | stripUrl }} + %div{"ng-if" => "producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} + %label Follow + .follow-icons{bindonce: true} + %span{"ng-if" => "producer.twitter"} + %a{"ng-href" => "http://twitter.com/{{producer.twitter}}", target: "_blank"} + %i.ofn-i_041-twitter + + %span{"ng-if" => "producer.facebook"} + %a{"ng-href" => "http://{{producer.facebook | stripUrl}}", target: "_blank"} + %i.ofn-i_044-facebook + + %span{"ng-if" => "producer.linkedin"} + %a{"ng-href" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"} + %i.ofn-i_042-linkedin + + %span{"ng-if" => "producer.instagram"} + %a{"ng-href" => "http://instagram.com/{{producer.instagram}}", target: "_blank"} + %i.ofn-i_043-instagram - -.row.active_table_row{"ng-show" => "open()", "bo-if" => "producer.hubs"} +.row.active_table_row.pad-top{"ng-show" => "open()", "bo-if" => "producer.hubs"} .columns.small-12 .row .columns.small-12.fat @@ -61,3 +75,4 @@ %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} .hub-name {{hub.name}} .button-address {{ [hub.address.city, hub.address.state_name] | printArray }} + diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index beded1a14b..116fc970d8 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -2,12 +2,14 @@ .columns.small-12.medium-4.large-4.skinny-head / This needs logic to show profile only icon when available %i.ofn-i_060-producer-reversed %i.ofn-i_059-producer - %strong {{ producer.name }} + %span.margin-top + %strong {{ producer.name }} .columns.small-6.medium-3.large-3 - {{ producer.address.city }} + %span.margin-top {{ producer.address.city }} .columns.small-4.medium-3.large-4 - {{ producer.address.state_name | uppercase }} + %span.margin-top {{ producer.address.state_name | uppercase }} .columns.small-2.medium-2.large-1.text-right / This forces line-height to be triggered - %span   - %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} + %span.margin-top +   + %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} From ab440466586f5e844e2a07243fc46e1627a55577 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 17:52:36 +1000 Subject: [PATCH 45/68] skinny view producers remove unnecessary spacer element --- app/views/producers/_skinny.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index 116fc970d8..a5d6b790fc 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -9,7 +9,5 @@ .columns.small-4.medium-3.large-4 %span.margin-top {{ producer.address.state_name | uppercase }} .columns.small-2.medium-2.large-1.text-right - / This forces line-height to be triggered %span.margin-top -   %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} From 6e10c416e2aed908e739ebcbb4da2a5b1729d791 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 17:53:14 +1000 Subject: [PATCH 46/68] hub view styling --- app/assets/stylesheets/darkswarm/hub_node.css.sass | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index d2a0de4383..0ef4d83672 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -21,8 +21,6 @@ display: inline-block margin-right: 0.25rem float: left - @media all and (max-width: 768px) - // font-size: 1rem //Closed & Open column .open_closed @@ -31,10 +29,6 @@ float: right margin-left: 0.5rem - span.margin-top - margin-top: 0.5rem - display: inline-block - //Hub Name span.hub-name-listing float: left From bb82fc4b20fb5c47c28ec5e5fe80e82430aff423 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 17:53:27 +1000 Subject: [PATCH 47/68] move generic things into active table --- .../stylesheets/darkswarm/active_table.css.sass | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/active_table.css.sass b/app/assets/stylesheets/darkswarm/active_table.css.sass index 4143da4f01..695ee3039a 100644 --- a/app/assets/stylesheets/darkswarm/active_table.css.sass +++ b/app/assets/stylesheets/darkswarm/active_table.css.sass @@ -29,11 +29,17 @@ span text-decoration: underline + span.margin-top + margin-top: 0.5rem + display: inline-block + // Generic text resize @media all and (max-width: 640px) - font-size: 0.875rem &, & * font-size: 0.875rem + fat > div label + &, & * + font-size: 0.75rem .active_table_row // Inherits from active_table @@ -69,7 +75,7 @@ //Open row sections .fat > div - border-top: 1px solid $disabled-bright + border-top: 1px solid #aaa ul, ol font-size: 0.875rem @@ -82,7 +88,7 @@ font-size: 0.75rem margin-top: 0.25rem margin-bottom: 0.25rem - color: $disabled-dark + color: #777 p.trans-sentence text-transform: capitalize From 0b74242f4911ea5b268e513235884c47749fc3b9 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 17:53:52 +1000 Subject: [PATCH 48/68] make classes more generic for more use-cases --- app/assets/stylesheets/darkswarm/modal-enterprises.css.sass | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass index 2009381daa..9f08b38790 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass @@ -70,7 +70,7 @@ // FOLLOW Enterprise .follow-icons - text-align: center + // text-align: center span display: inline-block margin: 0 0.25rem 0.75rem 0.25rem @@ -104,8 +104,6 @@ margin-right: 2rem margin-top: 0rem margin-bottom: 0.75rem - // padding-left: 1rem - // padding-right: 1rem display: inline-block &.secondary From 94e3946d52bc392be4a5ab2bebd35a26d667d99f Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 17:54:16 +1000 Subject: [PATCH 49/68] producer list view styling --- .../darkswarm/producer_node.css.sass | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.sass b/app/assets/stylesheets/darkswarm/producer_node.css.sass index 1dcb49b528..25759d30e5 100644 --- a/app/assets/stylesheets/darkswarm/producer_node.css.sass +++ b/app/assets/stylesheets/darkswarm/producer_node.css.sass @@ -11,6 +11,42 @@ margin-bottom: 1rem padding-top: 1rem padding-bottom: 1rem + .follow-icons + &, & * + font-size: 1.5rem + + // Producer icons + i.ofn-i_059-producer, i.ofn-i_060-producer-reversed + font-size: 2rem + display: inline-block + margin-right: 0.25rem + float: left + color: $clr-turquoise + + a + &:hover, &:active, &:focus + color: $clr-turquoise-bright + span + text-decoration: underline + + a.cta-hub + &:hover, &:focus, &:active + &.secondary + color: #666 + .hub-name, .button-address + border-bottom: 1px solid #999 + &.primary + color: $clr-brick-bright + .hub-name, .button-address + border-bottom: 1px solid $clr-brick-bright + + p.word-wrap + margin-bottom: 0 + &:last-child + margin-bottom: 1rem + + .fat-taxons + background-color: $clr-turquoise-light //Open row &.open @@ -18,13 +54,17 @@ .active_table_row border-left: 1px solid $clr-turquoise-bright border-right: 1px solid $clr-turquoise-bright - background-color: rgba(255,255,255,0.4) + background-color: rgba(206,239,228,0.4) .cta-container background: none .columns img padding: 1rem 0 max-height: 160px width: auto + &.left + padding: 0.25rem 1rem 0.25rem 0 + &.right + padding: 0.25rem 0.5rem 0.25rem 2rem .active_table_row:first-child border-top: 1px solid $clr-turquoise-bright @@ -37,4 +77,7 @@ &.closed &:hover, &:active, &:focus .active_table_row.closed - border: 1px solid $clr-turquoise \ No newline at end of file + border: 1px solid $clr-turquoise + + + From 778cf0326bf78317e225bc74cd476e6c1ba4945d Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 18:04:02 +1000 Subject: [PATCH 50/68] Changing placeholder for --- app/views/home/_hubs.html.haml | 2 +- app/views/producers/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index 88c2909614..4f49f66b2c 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -2,7 +2,7 @@ #hubs.hubs{"ng-controller" => "HubsCtrl"} .row .small-12.columns - %h1 Shop your local area... + %h1 Shop your local area / %div / Shop a / %ofn-modal{title: "food hub"} diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index 1a2c827068..84f1d76781 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -13,7 +13,7 @@ .small-12.columns %input.animate-show{type: :text, "ng-model" => "query", - placeholder: "Search by Producer or Suburb name", + placeholder: "Search by Producer, Suburb or Postcode", "ng-debounce" => "150", "ofn-disable-enter" => true} From 6271b83fadf4cc42bcba399fa1471697d2148224 Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 18:07:28 +1000 Subject: [PATCH 51/68] Tweak placeholder messages for big inputs --- app/views/home/_hubs.html.haml | 2 +- app/views/producers/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index 4f49f66b2c..0bdab2f19a 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -14,7 +14,7 @@ / %i.ofn-i_020-search %input{type: :text, "ng-model" => "query", - placeholder: "Search by Shop, Suburb or Postcode...", + placeholder: "Search by Shop or Postcode...", "ng-debounce" => "150", "ofn-disable-enter" => true} diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index 84f1d76781..b8645f936a 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -13,7 +13,7 @@ .small-12.columns %input.animate-show{type: :text, "ng-model" => "query", - placeholder: "Search by Producer, Suburb or Postcode", + placeholder: "Search by Producer or Suburb...", "ng-debounce" => "150", "ofn-disable-enter" => true} From 2e8ec3df18d1c64bfa169b04d2757a98b534729b Mon Sep 17 00:00:00 2001 From: summerscope Date: Fri, 5 Sep 2014 18:07:50 +1000 Subject: [PATCH 52/68] Tweak placeholder messages for big inputs --- app/views/home/_hubs.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index 0bdab2f19a..e93e312fb7 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -14,7 +14,7 @@ / %i.ofn-i_020-search %input{type: :text, "ng-model" => "query", - placeholder: "Search by Shop or Postcode...", + placeholder: "Search by Shop or Suburb...", "ng-debounce" => "150", "ofn-disable-enter" => true} From 58dcdbd9c40abadbb0ede928e65b1ce216185619 Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 18:30:15 +1000 Subject: [PATCH 53/68] Restricting ability to change enterprise type at the controller level --- .../admin/enterprises_controller.rb | 6 ++++ .../admin/enterprises_controller_spec.rb | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 85fe6831fb..594ccc751c 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -4,6 +4,7 @@ module Admin before_filter :load_countries, :except => :index before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create] create.after :grant_management + before_filter :override_type, only: :update helper 'spree/products' include OrderCyclesHelper @@ -67,6 +68,11 @@ module Admin @enterprise_fees = EnterpriseFee.managed_by(spree_current_user).for_enterprise(@enterprise).order(:fee_type, :name).all end + def override_type + # TODO this should be done using CanCan, but our current version does not allow this level of fine grained control + params[:enterprise].delete :type unless spree_current_user.admin? + end + # Overriding method on Spree's resource controller def location_after_save if params[:enterprise].key? :producer_properties_attributes diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb index 20482bef09..e52f09a5a4 100644 --- a/spec/controllers/admin/enterprises_controller_spec.rb +++ b/spec/controllers/admin/enterprises_controller_spec.rb @@ -36,5 +36,34 @@ module Admin admin_user.enterprise_roles.where(enterprise_id: enterprise).should be_empty end end + + describe "updating an enterprise" do + let(:profile_enterprise) { create(:enterprise, type: 'profile') } + + context "as manager" do + it "does not allow 'type' to be changed" do + # TODO should be implemented and tested using cancan abilities, but can't do this using our current version + profile_enterprise.enterprise_roles.build(user: user).save + controller.stub spree_current_user: user + enterprise_params = { id: profile_enterprise.id, enterprise: { type: 'full' } } + + spree_put :update, enterprise_params + profile_enterprise.reload + expect(profile_enterprise.type).to eq 'profile' + end + end + + context "as super admin" do + it "allows 'type' to be changed" do + # TODO should be implemented and tested using cancan abilities, but can't do this using our current version + controller.stub spree_current_user: admin_user + enterprise_params = { id: profile_enterprise.id, enterprise: { type: 'full' } } + + spree_put :update, enterprise_params + profile_enterprise.reload + expect(profile_enterprise.type).to eq 'full' + end + end + end end end From 780df6bfe061f1deaad43c793fe48dc9ca56e8c4 Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 5 Sep 2014 18:38:43 +1000 Subject: [PATCH 54/68] Hide 'profile type' form element for non super-admin users --- app/views/admin/enterprises/_form.html.haml | 37 +++++++++++---------- spec/features/admin/enterprises_spec.rb | 2 +- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 07be41dd6e..dbf986c7aa 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -36,24 +36,25 @@ = f.check_box :is_primary_producer, 'ng-model' => 'Enterprise.is_primary_producer'   = f.label :is_primary_producer, 'Producer' - .row - .alpha.eleven.columns - .three.columns.alpha - = f.label :type, 'Profile type' - .with-tip{'data-powertip' => "Full - enterprise may have products and relationships.
    Single - enterprise may have products but no relationships.
    Profile - enterprise has a profile but no products or relationships.
    "} - %a What's this? - .two.columns - = f.radio_button :type, "full" -   - = f.label :type, "Full", value: "full" - .two.columns - = f.radio_button :type, "single" -   - = f.label :type, "Single", value: "single" - .four.columns.omega - = f.radio_button :type, "profile" -   - = f.label :type, "Profile", value: "profile" + - if spree_current_user.admin? + .row + .alpha.eleven.columns + .three.columns.alpha + = f.label :type, 'Profile type' + .with-tip{'data-powertip' => "Full - enterprise may have products and relationships.
    Single - enterprise may have products but no relationships.
    Profile - enterprise has a profile but no products or relationships.
    "} + %a What's this? + .two.columns + = f.radio_button :type, "full" +   + = f.label :type, "Full", value: "full" + .two.columns + = f.radio_button :type, "single" +   + = f.label :type, "Single", value: "single" + .four.columns.omega + = f.radio_button :type, "profile" +   + = f.label :type, "Profile", value: "profile" .row .three.columns.alpha %label Visible in search? diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index da32f92681..08bae3aec5 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -127,7 +127,7 @@ feature %q{ choose 'Single' fill_in 'enterprise_description', :with => 'Connecting farmers and eaters' fill_in 'enterprise_long_description', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' - + # Check Angularjs switching of sidebar elements uncheck 'enterprise_is_primary_producer' uncheck 'enterprise_is_distributor' From ee4a1925fe91c904138670eecbd06ce8e26da537 Mon Sep 17 00:00:00 2001 From: Rob H Date: Sat, 6 Sep 2014 00:34:27 +1000 Subject: [PATCH 55/68] Bulk Order Management works with navigation helper override --- .../spree/admin/navigation_helper_decorator.rb | 1 + spec/features/admin/bulk_order_management_spec.rb | 14 +++++++------- spec/helpers/navigation_helper_spec.rb | 4 ++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/helpers/spree/admin/navigation_helper_decorator.rb b/app/helpers/spree/admin/navigation_helper_decorator.rb index 024f467154..eb210ef482 100644 --- a/app/helpers/spree/admin/navigation_helper_decorator.rb +++ b/app/helpers/spree/admin/navigation_helper_decorator.rb @@ -8,6 +8,7 @@ module Spree klass = klass_for_without_sym_fallback(name) klass ||= name.singularize.to_sym klass = :overview if klass == :dashboard + klass = Spree::Order if klass == :bulk_order_management klass end alias_method_chain :klass_for, :sym_fallback diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index a8fe6cbda7..fe18f1a9bf 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -15,13 +15,6 @@ feature %q{ admin_user = quick_login_as_admin end - it "displays a Bulk Management Tab under the Orders item" do - visit '/admin/orders' - page.should have_link "Bulk Order Management" - click_link "Bulk Order Management" - page.should have_selector "h1.page-title", text: "Bulk Order Management" - end - it "displays a message when number of line items is zero" do visit '/admin/orders/bulk_management' page.should have_text "No orders found." @@ -586,6 +579,13 @@ feature %q{ quick_login_as @enterprise_user end + it "displays a Bulk Management Tab under the Orders item" do + visit '/admin/orders' + page.should have_link "Bulk Order Management" + click_link "Bulk Order Management" + page.should have_selector "h1.page-title", text: "Bulk Order Management" + end + it "shows only line item from orders that I distribute, and not those that I supply" do visit '/admin/orders/bulk_management' diff --git a/spec/helpers/navigation_helper_spec.rb b/spec/helpers/navigation_helper_spec.rb index 41decd6e5a..76fe9573f2 100644 --- a/spec/helpers/navigation_helper_spec.rb +++ b/spec/helpers/navigation_helper_spec.rb @@ -15,6 +15,10 @@ module Spree it "returns :overview for the dashboard" do helper.klass_for('dashboard').should == :overview end + + it "returns Spree::Order for bulk_order_management" do + helper.klass_for('bulk_order_management').should == Spree::Order + end end end end From 9dc2b248c7ef40482eccc2bf7dc609d5d113a295 Mon Sep 17 00:00:00 2001 From: Rob H Date: Sat, 6 Sep 2014 09:37:34 +1000 Subject: [PATCH 56/68] Bulk management permissions make more sense --- app/models/spree/ability_decorator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 9f9c96cb15..79019f3e8a 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -59,12 +59,12 @@ class AbilityDecorator # Enterprise User can only access orders that they are a distributor for can [:index, :create], Spree::Order - can [:read, :update, :bulk_management, :fire, :resend], Spree::Order do |order| + can [:read, :update, :fire, :resend], Spree::Order do |order| # We allow editing orders with a nil distributor as this state occurs # during the order creation process from the admin backend order.distributor.nil? || user.enterprises.include?(order.distributor) end - can [:admin], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor?) + can [:admin, :bulk_management], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor?) can [:admin, :create], Spree::LineItem can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Payment From b8fadb50ae6cbbae7178dd15654f0fc6e5d9a5b6 Mon Sep 17 00:00:00 2001 From: Rob H Date: Sat, 6 Sep 2014 12:00:27 +1000 Subject: [PATCH 57/68] Special Instructions in checkout are actually wired up --- .../checkout/checkout_controller.js.coffee | 4 ++-- .../darkswarm/services/checkout.js.coffee | 6 +++--- app/controllers/checkout_controller.rb | 6 +++--- app/views/checkout/_form.html.haml | 5 ++--- .../archive/features/consumer/checkout_spec.rb | 18 +++++++++--------- .../consumer/shopping/checkout_spec.rb | 18 +++++++++++++----- 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee index e77712eae6..9c953c4f4a 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee @@ -6,9 +6,9 @@ Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, Cu prefix = "order_#{Checkout.order.id}#{CurrentUser?.id}#{CurrentHub.hub.id}" for field in $scope.fieldsToBind - storage.bind $scope, "Checkout.order.#{field}", + storage.bind $scope, "Checkout.order.#{field}", storeName: "#{prefix}_#{field}" - storage.bind $scope, "Checkout.ship_address_same_as_billing", + storage.bind $scope, "Checkout.ship_address_same_as_billing", storeName: "#{prefix}_sameasbilling" defaultValue: true diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index cafd4d6718..a12ed1ae45 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -13,7 +13,7 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h Loading.clear() @errors = response.errors RailsFlashLoader.loadFlash(response.flash) - + # Rails wants our Spree::Address data to be provided with _attributes preprocess: -> munged_order = {} @@ -25,7 +25,7 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h munged_order["ship_address_attributes"] = value when "payment_method_id" munged_order["payments_attributes"] = [{payment_method_id: value}] - when "shipping_method_id", "payment_method_id", "email" + when "shipping_method_id", "payment_method_id", "email", "special_instructions" munged_order[name] = value else # Ignore everything else @@ -58,7 +58,7 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h shippingPrice: -> @shippingMethod()?.price || 0.0 - + paymentMethod: -> PaymentMethods.payment_methods_by_id[@order.payment_method_id] diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 8e2a33bd56..8e249e5a1f 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -9,7 +9,7 @@ class CheckoutController < Spree::CheckoutController include OrderCyclesHelper include EnterprisesHelper - + def edit # Because this controller doesn't inherit from our BaseController # We need to duplicate the code here @@ -56,7 +56,7 @@ class CheckoutController < Spree::CheckoutController private - + # Copied and modified from spree. Remove check for order state, since the state machine is # progressed all the way in one go with the one page checkout. def object_params @@ -94,7 +94,7 @@ class CheckoutController < Spree::CheckoutController def skip_state_validation? true end - + def load_order @order = current_order redirect_to main_app.shop_path and return unless @order and @order.checkout_allowed? diff --git a/app/views/checkout/_form.html.haml b/app/views/checkout/_form.html.haml index a64be0ac44..52f6f983cc 100644 --- a/app/views/checkout/_form.html.haml +++ b/app/views/checkout/_form.html.haml @@ -1,8 +1,8 @@ -= f_form_for current_order, url: main_app.update_checkout_path, += f_form_for current_order, html: {name: "checkout", id: "checkout_form", novalidate: true, - name: "checkout"} do |f| + "ng-submit" => "purchase($event)"} do |f| = inject_available_shipping_methods = inject_available_payment_methods @@ -20,7 +20,6 @@ = render partial: "checkout/payment", locals: {f: f} %p %button.button.primary{type: :submit, - "ng-click" => "purchase($event)", "ng-disabled" => "checkout.$invalid"} Place order now / {{ checkout.$valid }} diff --git a/spec/archive/features/consumer/checkout_spec.rb b/spec/archive/features/consumer/checkout_spec.rb index 9641085c39..74a2443b36 100644 --- a/spec/archive/features/consumer/checkout_spec.rb +++ b/spec/archive/features/consumer/checkout_spec.rb @@ -7,7 +7,7 @@ feature %q{ }, skip: true do include AuthenticationWorkflow include WebHelper - + background do set_feature_toggle :order_cycles, true @@ -24,8 +24,8 @@ feature %q{ :state => Spree::State.find_by_name('Victoria'), :country => Spree::Country.find_by_name('Australia')), :pickup_times => 'Tuesday, 4 PM') - - + + @distributor_alternative = create(:distributor_enterprise, :name => 'Alternative Distributor', :address => create(:address, :address1 => '1600 Rathdowne St', @@ -33,7 +33,7 @@ feature %q{ :zipcode => 3054, :state => Spree::State.find_by_name('Victoria'), :country => Spree::Country.find_by_name('Australia')), - :pickup_times => 'Tuesday, 4 PM') + :pickup_times => 'Tuesday, 4 PM') @enterprise_fee_1 = create(:enterprise_fee, :name => 'Enterprise Fee One', :calculator => Spree::Calculator::PerItem.new) @enterprise_fee_1.calculator.set_preference :amount, 1 @@ -347,16 +347,16 @@ feature %q{ login_to_consumer_section click_link 'FruitAndVeg' - visit enterprise_path @distributor1 + visit enterprise_path @distributor1 click_link 'Bananas' click_button 'Add To Cart' - visit enterprise_path @distributor1 + visit enterprise_path @distributor1 click_link 'Zucchini' click_button 'Add To Cart' find('#checkout-link').click - + # And manually visit the old checkout visit "/checkout" @@ -389,7 +389,7 @@ feature %q{ # -- Checkout: Delivery page.should have_content "DELIVERY METHOD" order_charges = page.all("tbody#summary-order-charges tr").map {|row| row.all('td').map(&:text)}.take(2) - order_charges.should == [["Distribution:", "$51.00"]] + order_charges.should == [["Distribution:", "$51.00"]] click_checkout_continue_button @@ -403,7 +403,7 @@ feature %q{ # -- Checkout: Order complete page.should have_content 'Your order has been processed successfully' page.should have_content @payment_method_distributor_oc.description - page.should have_content @distributor_oc.name + page.should have_content @distributor_oc.name page.should have_selector 'tfoot#order-charges tr.total td', text: 'Distribution' page.should have_selector 'tfoot#order-charges tr.total td', text: '51.00' diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index b26e924495..56eee61e4f 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -94,10 +94,6 @@ feature "As a consumer I want to check out my cart", js: true do describe "purchasing" do it "takes us to the order confirmation page when we submit a complete form" do - toggle_shipping - choose sm2.name - toggle_payment - choose pm1.name toggle_details within "#details" do fill_in "First Name", with: "Will" @@ -112,14 +108,25 @@ feature "As a consumer I want to check out my cart", js: true do select "Victoria", from: "State" fill_in "City", with: "Melbourne" fill_in "Postcode", with: "3066" - end + toggle_shipping + within "#shipping" do + choose sm2.name + fill_in 'Any notes or custom delivery instructions?', with: "SpEcIaL NoTeS" + end + toggle_payment + within "#payment" do + choose pm1.name + end + place_order page.should have_content "Your order has been processed successfully" ActionMailer::Base.deliveries.length.should == 2 email = ActionMailer::Base.deliveries.last site_name = Spree::Config[:site_name] email.subject.should include "#{site_name} Order Confirmation" + o = Spree::Order.complete.first + expect(o.special_instructions).to eq "SpEcIaL NoTeS" end context "with basic details filled" do @@ -157,6 +164,7 @@ feature "As a consumer I want to check out my cart", js: true do it "takes us to the order confirmation page when submitted with a valid credit card" do toggle_payment + save_screenshot '/Users/rob/Desktop/ss.png' fill_in 'Card Number', with: "4111111111111111" select 'February', from: 'secrets.card_month' select (Date.today.year+1).to_s, from: 'secrets.card_year' From 6540bb8efc84e62e32e7a2b5290274d3b1fdd5a0 Mon Sep 17 00:00:00 2001 From: Rob H Date: Sun, 7 Sep 2014 19:51:14 +1000 Subject: [PATCH 58/68] Adding select field for enterprise type to index when super admin --- .../admin/enterprises_controller.rb | 14 ++++-- app/views/admin/enterprises/index.html.haml | 8 +-- .../admin/enterprises_controller_spec.rb | 37 +++++++++++++- spec/features/admin/enterprises_spec.rb | 50 ++++++++++++------- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 594ccc751c..ceeb26abfc 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -4,7 +4,8 @@ module Admin before_filter :load_countries, :except => :index before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create] create.after :grant_management - before_filter :override_type, only: :update + before_filter :check_type, only: :update + before_filter :check_bulk_type, only: :bulk_update helper 'spree/products' include OrderCyclesHelper @@ -68,8 +69,15 @@ module Admin @enterprise_fees = EnterpriseFee.managed_by(spree_current_user).for_enterprise(@enterprise).order(:fee_type, :name).all end - def override_type - # TODO this should be done using CanCan, but our current version does not allow this level of fine grained control + def check_bulk_type + unless spree_current_user.admin? + params[:enterprise_set][:collection_attributes].each do |i, enterprise_params| + enterprise_params.delete :type + end + end + end + + def check_type params[:enterprise].delete :type unless spree_current_user.admin? end diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 7ab6ea4d0e..30dfc7a8f2 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -10,17 +10,17 @@ = form_for @enterprise_set, :url => main_app.bulk_update_admin_enterprises_path do |f| %table#listing_enterprises.index %colgroup - %col{style: "width: 20%;"}/ + %col{style: "width: 25%;"}/ %col{style: "width: 10%;"}/ %col{style: "width: 5%;"}/ - %col/ + %col{style: "width: 10%;"}/ %col{style: "width: 20%;"}/ %thead %tr{"data-hook" => "enterprises_header"} %th Name %th Role %th Visible? - %th Description + %th Type %th %tbody = f.fields_for :collection do |enterprise_form| @@ -37,7 +37,7 @@ - else %h1.icon-exclamation-sign.with-tip{"data-powertip" => "This enterprise does not have any roles", style: "text-align: center;color: #DA5354"} %td= enterprise_form.check_box :visible - %td= enterprise.description + %td= enterprise_form.select :type, Enterprise::TYPES, {}, class: 'select2 fullwidth' %td{"data-hook" => "admin_users_index_row_actions"} = render 'actions', enterprise: enterprise - if @enterprises.empty? diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb index e52f09a5a4..14faf26287 100644 --- a/spec/controllers/admin/enterprises_controller_spec.rb +++ b/spec/controllers/admin/enterprises_controller_spec.rb @@ -42,7 +42,6 @@ module Admin context "as manager" do it "does not allow 'type' to be changed" do - # TODO should be implemented and tested using cancan abilities, but can't do this using our current version profile_enterprise.enterprise_roles.build(user: user).save controller.stub spree_current_user: user enterprise_params = { id: profile_enterprise.id, enterprise: { type: 'full' } } @@ -55,7 +54,6 @@ module Admin context "as super admin" do it "allows 'type' to be changed" do - # TODO should be implemented and tested using cancan abilities, but can't do this using our current version controller.stub spree_current_user: admin_user enterprise_params = { id: profile_enterprise.id, enterprise: { type: 'full' } } @@ -65,5 +63,40 @@ module Admin end end end + + describe "bulk updating enterprises" do + let(:profile_enterprise1) { create(:enterprise, type: 'profile') } + let(:profile_enterprise2) { create(:enterprise, type: 'profile') } + + context "as manager" do + it "does not allow 'type' to be changed" do + profile_enterprise1.enterprise_roles.build(user: user).save + profile_enterprise2.enterprise_roles.build(user: user).save + controller.stub spree_current_user: user + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, type: 'full' }, '1' => { id: profile_enterprise2.id, type: 'full' } } } } + + spree_put :bulk_update, bulk_enterprise_params + profile_enterprise1.reload + profile_enterprise2.reload + expect(profile_enterprise1.type).to eq 'profile' + expect(profile_enterprise2.type).to eq 'profile' + end + end + + context "as super admin" do + it "allows 'type' to be changed" do + profile_enterprise1.enterprise_roles.build(user: user).save + profile_enterprise2.enterprise_roles.build(user: user).save + controller.stub spree_current_user: admin_user + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, type: 'full' }, '1' => { id: profile_enterprise2.id, type: 'full' } } } } + + spree_put :bulk_update, bulk_enterprise_params + profile_enterprise1.reload + profile_enterprise2.reload + expect(profile_enterprise1.type).to eq 'full' + expect(profile_enterprise2.type).to eq 'full' + end + end + end end end diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 08bae3aec5..00c15160f0 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -15,39 +15,43 @@ feature %q{ click_link 'Enterprises' within("tr.enterprise-#{s.id}") do - page.should have_content s.name - page.should have_content "Edit Profile" - page.should have_content "Delete" - page.should_not have_content "Payment Methods" - page.should_not have_content "Shipping Methods" - page.should have_content "Enterprise Fees" + expect(page).to have_content s.name + expect(page).to have_select "enterprise_set_collection_attributes_1_type" + expect(page).to have_content "Edit Profile" + expect(page).to have_content "Delete" + expect(page).to_not have_content "Payment Methods" + expect(page).to_not have_content "Shipping Methods" + expect(page).to have_content "Enterprise Fees" end within("tr.enterprise-#{d.id}") do - page.should have_content d.name - page.should have_content "Edit Profile" - page.should have_content "Delete" - page.should have_content "Payment Methods" - page.should have_content "Shipping Methods" - page.should have_content "Enterprise Fees" + expect(page).to have_content d.name + expect(page).to have_select "enterprise_set_collection_attributes_0_type" + expect(page).to have_content "Edit Profile" + expect(page).to have_content "Delete" + expect(page).to have_content "Payment Methods" + expect(page).to have_content "Shipping Methods" + expect(page).to have_content "Enterprise Fees" end end scenario "editing enterprises in bulk" do s = create(:supplier_enterprise) - d = create(:distributor_enterprise) + d = create(:distributor_enterprise, type: 'profile') login_to_admin_section click_link 'Enterprises' within("tr.enterprise-#{d.id}") do - page.should have_checked_field "enterprise_set_collection_attributes_0_visible" + expect(page).to have_checked_field "enterprise_set_collection_attributes_0_visible" uncheck "enterprise_set_collection_attributes_0_visible" + select 'full', from: "enterprise_set_collection_attributes_0_type" end click_button "Update" flash_message.should == 'Enterprises updated successfully' distributor = Enterprise.find(d.id) - distributor.visible.should == false + expect(distributor.visible).to eq false + expect(distributor.type).to eq 'full' end scenario "viewing an enterprise" do @@ -259,10 +263,18 @@ feature %q{ click_link "Enterprises" - page.should have_content supplier1.name - page.should have_content distributor1.name - page.should_not have_content supplier2.name - page.should_not have_content distributor2.name + within("tr.enterprise-#{supplier1.id}") do + expect(page).to have_content supplier1.name + expect(page).to have_select "enterprise_set_collection_attributes_1_type" + end + + within("tr.enterprise-#{distributor1.id}") do + expect(page).to have_content distributor1.name + expect(page).to have_select "enterprise_set_collection_attributes_0_type" + end + + expect(page).to_not have_content "supplier2.name" + expect(page).to_not have_content "distributor2.name" end scenario "creating an enterprise" do From 5fb4110328b62ff166b92a7ca7ddced9283ab943 Mon Sep 17 00:00:00 2001 From: Rob H Date: Sun, 7 Sep 2014 21:04:53 +1000 Subject: [PATCH 59/68] Adding distributor and producer checkboxes to enterprise index --- app/views/admin/enterprises/index.html.haml | 13 +++++-------- spec/features/admin/enterprises_spec.rb | 14 +++++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 30dfc7a8f2..c8b3cce3d4 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -28,14 +28,11 @@ %tr{class: "enterprise-#{enterprise.id}"} %td= link_to enterprise.name, main_app.edit_admin_enterprise_path(enterprise) %td - - if enterprise.is_primary_producer && enterprise.is_distributor - Producer & Distributor - - elsif enterprise.is_distributor - Distributor - - elsif enterprise.is_primary_producer - Producer - - else - %h1.icon-exclamation-sign.with-tip{"data-powertip" => "This enterprise does not have any roles", style: "text-align: center;color: #DA5354"} + = enterprise_form.check_box :is_primary_producer + Producer + %br/ + = enterprise_form.check_box :is_distributor + Hub %td= enterprise_form.check_box :visible %td= enterprise_form.select :type, Enterprise::TYPES, {}, class: 'select2 fullwidth' %td{"data-hook" => "admin_users_index_row_actions"} diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 00c15160f0..a824716d95 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -263,16 +263,20 @@ feature %q{ click_link "Enterprises" - within("tr.enterprise-#{supplier1.id}") do - expect(page).to have_content supplier1.name - expect(page).to have_select "enterprise_set_collection_attributes_1_type" - end - within("tr.enterprise-#{distributor1.id}") do expect(page).to have_content distributor1.name + expect(page).to have_checked_field "enterprise_set_collection_attributes_0_is_distributor" + expect(page).to have_unchecked_field "enterprise_set_collection_attributes_0_is_primary_producer" expect(page).to have_select "enterprise_set_collection_attributes_0_type" end + within("tr.enterprise-#{supplier1.id}") do + expect(page).to have_content supplier1.name + expect(page).to have_unchecked_field "enterprise_set_collection_attributes_1_is_distributor" + expect(page).to have_checked_field "enterprise_set_collection_attributes_1_is_primary_producer" + expect(page).to have_select "enterprise_set_collection_attributes_1_type" + end + expect(page).to_not have_content "supplier2.name" expect(page).to_not have_content "distributor2.name" end From a968aa9b9104c1e7957491e162d0a7345edd94ef Mon Sep 17 00:00:00 2001 From: Rob H Date: Mon, 8 Sep 2014 10:09:00 +1000 Subject: [PATCH 60/68] Removing float:left which weirdly breaks specs --- app/assets/stylesheets/darkswarm/hub_node.css.sass | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index 0ef4d83672..322da9b375 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -21,7 +21,7 @@ display: inline-block margin-right: 0.25rem float: left - + //Closed & Open column .open_closed i @@ -29,12 +29,11 @@ float: right margin-left: 0.5rem - //Hub Name + //Hub Name span.hub-name-listing - float: left font-weight: 700 - @media all and (max-width: 640px) + @media all and (max-width: 640px) &.closed, &.open .active_table_row:first-child .skinny-head background-color: $clr-brick-light @@ -91,7 +90,7 @@ &.open .active_table_row:nth-child(2) padding-bottom: 0.75rem - + //Current selected row &.current //overwrites active_table From b49a4cbc4fa0ef56a46866c8ca14f7328792131b Mon Sep 17 00:00:00 2001 From: Rob H Date: Mon, 8 Sep 2014 11:11:07 +1000 Subject: [PATCH 61/68] Fix intermittent failing spec --- spec/lib/spree/product_filters_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/lib/spree/product_filters_spec.rb b/spec/lib/spree/product_filters_spec.rb index 0e240a5479..5573ef0407 100644 --- a/spec/lib/spree/product_filters_spec.rb +++ b/spec/lib/spree/product_filters_spec.rb @@ -4,7 +4,9 @@ describe Spree::ProductFilters do context "distributor filter" do it "provides filtering for all distributors" do 3.times { create(:distributor_enterprise) } - Spree::ProductFilters.distributor_filter[:labels].should == Enterprise.is_distributor.sort.map { |d| [d.name, d.name] } + Enterprise.is_distributor.sort.map { |d| [d.name, d.name] }.each do |distributor| + expect(Spree::ProductFilters.distributor_filter[:labels]).to include distributor + end end end end From dfa837cac6acfc97a65eda963697e5b2017c1fd4 Mon Sep 17 00:00:00 2001 From: Rob H Date: Mon, 8 Sep 2014 15:01:34 +1000 Subject: [PATCH 62/68] Moved cached properties which should not be cached --- app/serializers/api/enterprise_serializer.rb | 70 ++++++++++---------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index d7cbc08ff2..6485687d2b 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -24,6 +24,42 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer def active @options[:active_distributors].andand.include? object end + + # TODO: Move this back to uncached section when relavant properties are defined on the Enterprise model + def icon + # TODO: Replace with object.has_shopfront when this property exists + if has_shopfront + if can_aggregate + "/assets/map_005-hub.svg" + else + if object.is_distributor + "/assets/map_003-producer-shop.svg" + else + "/assets/map_001-producer-only.svg" + end + end + else + if can_aggregate + "/assets/map_006-hub-profile.svg" + else + if object.is_distributor + "/assets/map_004-producer-shop-profile.svg" + else + "/assets/map_002-producer-only-profile.svg" + end + end + end + end + + # TODO: Remove this when flags on enterprises are switched over + def has_shopfront + object.type != 'profile' + end + + # TODO: Remove this when flags on enterprises are switched over + def can_aggregate + object.is_distributor && object.suppliers != [object] + end end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer @@ -68,40 +104,6 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer object.promo_image(:large) if object.promo_image.exists? end - def icon - if has_shopfront - if can_aggregate - "/assets/map_005-hub.svg" - else - if object.is_distributor - "/assets/map_003-producer-shop.svg" - else - "/assets/map_001-producer-only.svg" - end - end - else - if can_aggregate - "/assets/map_006-hub-profile.svg" - else - if object.is_distributor - "/assets/map_004-producer-shop-profile.svg" - else - "/assets/map_002-producer-only-profile.svg" - end - end - end - end - - # TODO: Remove this when flags on enterprises are switched over - def has_shopfront - object.type != 'profile' - end - - # TODO: Remove this when flags on enterprises are switched over - def can_aggregate - object.is_distributor && object.suppliers != [object] - end - # TODO when ActiveSerializers supports URL helpers # Then refactor. See readme https://github.com/rails-api/active_model_serializers def path From 78a70292ac3604b2f5abeae1307fb13b8a3b5d08 Mon Sep 17 00:00:00 2001 From: Rob H Date: Mon, 8 Sep 2014 15:39:17 +1000 Subject: [PATCH 63/68] Moved attributes are actually delcared in the right place for Enterprise Serializer --- app/serializers/api/enterprise_serializer.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 6485687d2b..c76cffd7d1 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -17,6 +17,9 @@ end class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active + #TODO: Remove these later + attributes :icon, :has_shopfront, :can_aggregate + def orders_close_at OrderCycle.first_closing_for(object).andand.orders_close_at end @@ -69,9 +72,8 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer attributes :name, :id, :description, :latitude, :longitude, :long_description, :website, :instagram, :linkedin, :twitter, :facebook, :is_primary_producer, :is_distributor, :phone, :visible, - :email, :hash, :logo, :promo_image, :icon, :path, + :email, :hash, :logo, :promo_image, :path, :pickup, :delivery - attributes :has_shopfront, :can_aggregate has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer has_many :supplied_taxons, serializer: Api::IdSerializer From b49eb8fe03c7023be63fa4f0566c820a0290f5b1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 10 Sep 2014 11:50:30 +1000 Subject: [PATCH 64/68] Hide angular templates on page load --- app/assets/stylesheets/darkswarm/angular.css.sass | 3 +++ app/views/layouts/darkswarm.html.haml | 2 +- app/views/shared/menu/_large_menu.html.haml | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 app/assets/stylesheets/darkswarm/angular.css.sass diff --git a/app/assets/stylesheets/darkswarm/angular.css.sass b/app/assets/stylesheets/darkswarm/angular.css.sass new file mode 100644 index 0000000000..0430dd1390 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/angular.css.sass @@ -0,0 +1,3 @@ +// https://docs.angularjs.org/api/ng/directive/ngCloak +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak + display: none !important diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 07b0db8dfa..304b049ccc 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -35,7 +35,7 @@ = render partial: "shared/menu/menu" %section{ role: "main" } - = yield + .ng-cloak= yield #footer %loading diff --git a/app/views/shared/menu/_large_menu.html.haml b/app/views/shared/menu/_large_menu.html.haml index 22618b50c4..71f297dede 100644 --- a/app/views/shared/menu/_large_menu.html.haml +++ b/app/views/shared/menu/_large_menu.html.haml @@ -32,10 +32,10 @@ - else = render 'shared/signed_in' %li.divider - %li.current_hub{"ng-controller" => "CurrentHubCtrl", "ng-show" => "CurrentHub.hub.id"} + %li.current_hub{"ng-controller" => "CurrentHubCtrl", "ng-show" => "CurrentHub.hub.id", "ng-cloak" => true} %a{href: main_app.shop_path} %em Shopping @ %span.nav-primary.nav-branded {{ CurrentHub.hub.name }} %li.divider - %li.cart + %li.cart{"ng-cloak" => true} = render partial: "shared/menu/cart" From 4168ea054b65f8a455e7b9ccdbec37b861ec5e81 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 10 Sep 2014 12:12:46 +1000 Subject: [PATCH 65/68] For body content, cloak the home page only. JS should be cached after that. --- app/views/home/index.html.haml | 11 ++++++----- app/views/layouts/darkswarm.html.haml | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 63d2216052..36ee67998f 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -4,16 +4,17 @@ %h1= image_tag "ofn_logo_beta.png", title: "Open Food Network (beta)" %h2 An open marketplace that makes it easy to find, buy, sell and move sustainable local food. - %ofn-modal{title: "Learn more"} + %ofn-modal{title: "Learn more", "ng-cloak" => true} = render partial: "modals/learn_more" -= render partial: "home/hubs" +.ng-cloak + = render partial: "home/hubs" -/ = render partial: "home/map" + / = render partial: "home/map" -/ = render partial: "home/producers" + / = render partial: "home/producers" -/ = render partial: "home/groups" + / = render partial: "home/groups" = render partial: "home/beta" diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 304b049ccc..07b0db8dfa 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -35,7 +35,7 @@ = render partial: "shared/menu/menu" %section{ role: "main" } - .ng-cloak= yield + = yield #footer %loading From c297b7014af967240a83795a460000044826f1fa Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 10 Sep 2014 14:01:36 +1000 Subject: [PATCH 66/68] Charge customers for their shipping fee --- app/controllers/checkout_controller.rb | 8 ++++++++ spec/features/consumer/shopping/checkout_spec.rb | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 8e249e5a1f..7e8b04afda 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -112,6 +112,14 @@ class CheckoutController < Spree::CheckoutController @order.ship_address ||= preferred_ship_address || last_used_ship_address || Spree::Address.default end + def after_payment + # object_params sets the payment amount to the order total, but it does this before + # the shipping method is set. This results in the customer not being charged for their + # order's shipping. To fix this, we refresh the payment amount here. + @order.update_totals + @order.payments.first.update_attribute :amount, @order.total + end + # Overriding Spree's methods def raise_insufficient_quantity flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 56eee61e4f..9e6803a669 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -159,6 +159,20 @@ feature "As a consumer I want to check out my cart", js: true do page.should have_content "Your order has been processed successfully" end + context "when we are charged a shipping fee" do + before { choose sm2.name } + + it "creates a payment for the full amount inclusive of shipping" do + place_order + page.should have_content "Your order has been processed successfully" + + # There are two orders - our order and our new cart + o = Spree::Order.first + o.adjustments.shipping.first.amount.should == 4.56 + o.payments.first.amount.should == 10 + 1.23 + 4.56 # items + fees + shipping + end + end + context "with a credit card payment method" do let!(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::Gateway::Bogus") } From 32a2e793ad9d1a74a844703e95edb4248ad710af Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 10 Sep 2014 14:49:15 +1000 Subject: [PATCH 67/68] When deleting enterprise relationships, delete dependent permissions --- app/models/enterprise_relationship.rb | 2 +- spec/features/admin/enterprise_relationships_spec.rb | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/enterprise_relationship.rb b/app/models/enterprise_relationship.rb index dba99cc7b3..ff2c1fe3fa 100644 --- a/app/models/enterprise_relationship.rb +++ b/app/models/enterprise_relationship.rb @@ -1,7 +1,7 @@ class EnterpriseRelationship < ActiveRecord::Base belongs_to :parent, class_name: 'Enterprise', touch: true belongs_to :child, class_name: 'Enterprise', touch: true - has_many :permissions, class_name: 'EnterpriseRelationshipPermission' + has_many :permissions, class_name: 'EnterpriseRelationshipPermission', dependent: :destroy validates_presence_of :parent_id, :child_id validates_uniqueness_of :child_id, scope: :parent_id, message: "^That relationship is already established." diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index 007f663db0..bfc624bce5 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -69,14 +69,13 @@ feature %q{ end.to change(EnterpriseRelationship, :count).by(0) end - scenario "deleting a relationship" do e1 = create(:enterprise, name: 'One') e2 = create(:enterprise, name: 'Two') - er = create(:enterprise_relationship, parent: e1, child: e2) + er = create(:enterprise_relationship, parent: e1, child: e2, permissions_list: [:add_to_order_cycle]) visit admin_enterprise_relationships_path - page.should have_relationship e1, e2 + page.should have_relationship e1, e2, ['to add to order cycle'] first("a.delete-enterprise-relationship").click From 69d1111c731d6cfeed866c846a8ae4a553408ad2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 10 Sep 2014 17:00:51 +1000 Subject: [PATCH 68/68] Fix expected payment amount --- spec/features/consumer/shopping/checkout_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 9e6803a669..ab53befda5 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -167,7 +167,7 @@ feature "As a consumer I want to check out my cart", js: true do page.should have_content "Your order has been processed successfully" # There are two orders - our order and our new cart - o = Spree::Order.first + o = Spree::Order.complete.first o.adjustments.shipping.first.amount.should == 4.56 o.payments.first.amount.should == 10 + 1.23 + 4.56 # items + fees + shipping end @@ -178,7 +178,6 @@ feature "As a consumer I want to check out my cart", js: true do it "takes us to the order confirmation page when submitted with a valid credit card" do toggle_payment - save_screenshot '/Users/rob/Desktop/ss.png' fill_in 'Card Number', with: "4111111111111111" select 'February', from: 'secrets.card_month' select (Date.today.year+1).to_s, from: 'secrets.card_year' @@ -189,7 +188,7 @@ feature "As a consumer I want to check out my cart", js: true do # Order should have a payment with the correct amount o = Spree::Order.complete.first - o.payments.first.amount.should == 11.23 + o.payments.first.amount.should == 15.79 end it "shows the payment processing failed message when submitted with an invalid credit card" do