Merge master

This commit is contained in:
Steve Pettitt
2016-04-15 08:29:52 +01:00
131 changed files with 2449 additions and 348 deletions

View File

@@ -13,9 +13,10 @@ gem 'spree', github: 'openfoodfoundation/spree', branch: '1-3-stable'
gem 'spree_i18n', github: 'spree/spree_i18n', branch: '1-3-stable'
gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '1-3-stable'
# Waiting on merge of PR #117
# https://github.com/spree-contrib/better_spree_paypal_express/pull/117
gem 'spree_paypal_express', :github => "openfoodfoundation/better_spree_paypal_express", :branch => "1-3-stable"
# Our branch contains two changes
# - Pass customer email and phone number to PayPal (merged to upstream master)
# - Change type of password from string to password to hide it in the form
gem 'spree_paypal_express', :github => "openfoodfoundation/better_spree_paypal_express", :branch => "hide-password"
#gem 'spree_paypal_express', :github => "spree-contrib/better_spree_paypal_express", :branch => "1-3-stable"
gem 'delayed_job_active_record'

View File

@@ -14,8 +14,8 @@ GIT
GIT
remote: git://github.com/openfoodfoundation/better_spree_paypal_express.git
revision: cdd61161ccd27cd8d183f9321422c7be113796b8
branch: 1-3-stable
revision: 840d973cd5bd3250b17674a624dad494aeb09eb3
branch: hide-password
specs:
spree_paypal_express (2.0.3)
paypal-sdk-merchant (= 1.106.1)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 300 104"
enable-background="new 0 0 300 104"
id="svg2"
version="1.1"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="logo-black.svg"
width="100%"
height="100%">
<metadata
id="metadata24">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs22" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1280"
inkscape:window-height="741"
id="namedview20"
showgrid="false"
inkscape:zoom="1.8101934"
inkscape:cx="126.57728"
inkscape:cy="62.030566"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<g
id="g4"
fill="#fff">
<path
d="M142.7 15.6c.4 3.2.4 7.5 0 10.7-.5 4.6-3.8 8.4-9.1 8.4s-8.5-3.8-9.1-8.4c-.4-3.2-.4-7.5 0-10.7.5-4.6 3.8-8.4 9.1-8.4s8.6 3.8 9.1 8.4m-5.3 10.7c.4-3.2.4-7.5 0-10.7-.2-1.7-1.4-3-3.7-3-2.3 0-3.5 1.4-3.7 3-.4 3.2-.4 7.5 0 10.7.2 1.7 1.4 3 3.7 3 2.3 0 3.5-1.3 3.7-3"
id="path6"
style="fill:#000000" />
<path
d="M156.4 7.6c4.4 0 8 3.6 8 8s-3.6 8-8 8H153c-.2 0-.4.2-.4.4v9.5c0 .4-.3.8-.8.8H148c-.4 0-.8-.3-.8-.8V8.4c0-.4.3-.8.8-.8h8.4zm-3.5 5.3c-.2 0-.4.2-.4.4v4.6c0 .2.2.4.4.4h3.4c1.4 0 2.7-1.2 2.7-2.7 0-1.4-1.2-2.7-2.7-2.7h-3.4zM172.9 28.6c0 .2.2.4.4.4h9c.4 0 .8.3.8.8v3.8c0 .4-.3.8-.8.8h-14c-.4 0-.8-.3-.8-.8V8.4c0-.4.3-.8.8-.8h13.8c.4 0 .8.3.8.8v3.8c0 .4-.3.8-.8.8h-8.9c-.2 0-.4.2-.4.4V18c0 .2.2.4.4.4h8.6c.4 0 .8.3.8.8V23c0 .4-.3.8-.8.8h-8.6c-.2 0-.4.2-.4.4v4.4zM199.4 34.3c-.6 0-.9-.3-1-.6l-5.2-13.4c-.1-.2-.3-.2-.3.1v13.2c0 .4-.3.8-.8.8h-3.8c-.4 0-.8-.3-.8-.8V8.4c0-.4.3-.8.8-.8h4.5c.6 0 .8.3 1 .7l5.2 14.5c.1.2.3.2.3-.1V8.4c0-.4.3-.8.8-.8h3.8c.4 0 .8.3.8.8v25.1c0 .4-.3.8-.8.8h-4.5zM224.4 24v9.5c0 .4-.3.8-.8.8h-3.8c-.4 0-.8-.3-.8-.8V8.4c0-.4.3-.8.8-.8h13.3c.4 0 .8.3.8.8v3.8c0 .4-.3.8-.8.8h-8.4c-.2 0-.4.2-.4.4V18c0 .2.2.4.4.4h8c.4 0 .8.3.8.8V23c0 .4-.3.8-.8.8h-8c-.1-.2-.3 0-.3.2M255.6 15.6c.4 3.2.4 7.5 0 10.7-.5 4.6-3.8 8.4-9.1 8.4s-8.5-3.8-9.1-8.4c-.4-3.2-.4-7.5 0-10.7.5-4.6 3.8-8.4 9.1-8.4s8.6 3.8 9.1 8.4m-5.3 10.7c.4-3.2.4-7.5 0-10.7-.2-1.7-1.4-3-3.7-3-2.3 0-3.5 1.4-3.7 3-.4 3.2-.4 7.5 0 10.7.2 1.7 1.4 3 3.7 3 2.3 0 3.5-1.3 3.7-3M278 15.6c.4 3.2.4 7.5 0 10.7-.5 4.6-3.8 8.4-9.1 8.4s-8.5-3.8-9.1-8.4c-.4-3.2-.4-7.5 0-10.7.5-4.6 3.8-8.4 9.1-8.4s8.5 3.8 9.1 8.4m-5.4 10.7c.4-3.2.4-7.5 0-10.7-.2-1.7-1.4-3-3.7-3-2.3 0-3.5 1.4-3.7 3-.4 3.2-.4 7.5 0 10.7.2 1.7 1.4 3 3.7 3 2.3 0 3.5-1.3 3.7-3"
id="path8"
style="fill:#000000" />
<path
d="M291.2 7.6c4.7 0 8 3.8 8.5 8.4.4 3.2.4 6.6 0 9.8-.5 4.6-3.8 8.4-8.5 8.4h-8c-.4 0-.8-.3-.8-.8v-25c0-.4.3-.8.8-.8h8zm-3 5.3c-.2 0-.4.2-.4.4v15.2c0 .2.2.4.4.4h3c1.8 0 2.9-1.4 3.1-3.1.4-3.2.4-6.6 0-9.8-.2-1.7-1.4-3.1-3.1-3.1h-3zM137.5 67.9c-.4 0-.6-.1-.8-.6l-9.1-20.8c-.1-.1-.3-.1-.3.1v20.6c0 .4-.3.7-.8.7h-.8c-.4 0-.8-.3-.8-.7V43.3c0-.4.3-.7.8-.7h2c.4 0 .6.1.8.6l9.1 20.6c.1.1.3.1.3-.1V43.3c0-.4.3-.7.8-.7h.8c.4 0 .8.3.8.7v23.9c0 .4-.3.7-.8.7h-2zM146.7 65.3c0 .2.2.4.4.4h9.9c.4 0 .8.3.8.7v.9c0 .4-.3.7-.8.7h-12c-.4 0-.8-.3-.8-.7v-24c0-.4.3-.7.8-.7h12c.4 0 .8.3.8.7v.9c0 .4-.3.7-.8.7h-9.9c-.2 0-.4.1-.4.4v8.2c0 .2.2.4.4.4h9.4c.4 0 .8.3.8.7v.8c0 .4-.3.7-.8.7h-9.4c-.2 0-.4.1-.4.4v8.8zM170 45.3v21.9c0 .4-.3.7-.8.7h-1c-.4 0-.8-.3-.8-.7V45.3c0-.2-.2-.4-.4-.4h-5.6c-.4 0-.8-.3-.8-.7v-.9c0-.4.3-.7.8-.7H176c.4 0 .8.3.8.7v.9c0 .4-.3.7-.8.7h-5.6c-.2 0-.4.2-.4.4M187 67.9h-2.1c-.3 0-.6-.2-.7-.5L179 43.3c-.1-.4.2-.7.8-.7h.9c.3 0 .6.1.7.5l4.3 20.7c0 .1.1.2.2.2h.1c.1 0 .2-.1.2-.2l4-20.7c.1-.4.4-.5.7-.5h1.3c.3 0 .6.2.7.5l4.3 20.7c0 .1.1.2.2.2h.1c.1 0 .2-.1.2-.2l4.6-20.7c.1-.4.4-.5.7-.5h.9c.6 0 .8.4.8.7l-5.5 24.1c-.1.4-.4.5-.7.5h-2.1c-.3 0-.6-.2-.7-.5l-4-19.3c0-.2-.3-.2-.3 0l-3.8 19.3c0 .4-.3.5-.6.5M223.9 49.8c.4 2.9.4 7.9 0 10.9-.6 4.7-3.9 7.5-8.5 7.5s-7.8-2.9-8.5-7.5c-.4-2.9-.4-8 0-10.9.6-3.9 3.5-7.5 8.5-7.5s7.9 3.6 8.5 7.5m-2.6 10.9c.5-3 .5-7.9 0-10.9-.4-2.6-2.3-5.2-5.9-5.2-3.6 0-5.5 2.6-5.9 5.2-.5 3-.5 7.9 0 10.9.3 2.1 1.8 5.2 5.9 5.2 4.1 0 5.6-3.1 5.9-5.2"
id="path10"
style="fill:#000000" />
<path
d="m 230.9,57.8 c -0.2,0 -0.4,0.1 -0.4,0.4 l 0,9 c 0,0.4 -0.3,0.7 -0.8,0.7 l -0.9,0 c -0.4,0 -0.8,-0.3 -0.8,-0.7 l 0,-23.9 c 0,-0.4 0.3,-0.7 0.8,-0.7 l 6.5,0 c 4.4,0 8,3.4 8,7.6 0,3.3 -1.8,5.2 -4.5,6.9 -0.5,0.3 -0.6,0.6 -0.3,1.2 l 4.8,8.6 c 0.3,0.5 0,1.1 -0.7,1.1 l -0.8,0 c -0.6,0 -0.9,-0.3 -1.1,-0.7 l -4.9,-9 c -0.1,-0.2 -0.3,-0.4 -0.7,-0.4 l -4.2,0 z m 0,-12.9 c -0.2,0 -0.4,0.1 -0.4,0.4 l 0,9.8 c 0,0.2 0.2,0.4 0.4,0.4 l 4.4,0 c 3,0 5.6,-2.4 5.6,-5.3 0,-2.9 -2.5,-5.3 -5.6,-5.3 z m 18.4,12.2 C 249.2,57 249,57 249,57.2 l 0,10 c 0,0.4 -0.3,0.7 -0.8,0.7 l -0.9,0 c -0.4,0 -0.8,-0.3 -0.8,-0.7 l 0,-23.9 c 0,-0.4 0.3,-0.7 0.8,-0.7 l 0.9,0 c 0.4,0 0.8,0.3 0.8,0.7 l 0,9.6 c 0,0.2 0.2,0.2 0.3,0.1 l 8.8,-10 c 0.1,-0.1 0.4,-0.4 1,-0.4 l 0.6,0 c 0.9,0 1.1,0.8 0.6,1.3 l -9.4,10.5 c -0.2,0.3 -0.3,0.5 0,0.9 L 261,66.6 c 0.4,0.5 0.3,1.3 -0.7,1.3 l -0.8,0 c -0.6,0 -0.8,-0.3 -1,-0.5 z"
id="path12"
style="fill:#000000"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cssssssssscccssccsccsssssssscssssssssssccssccccsscc" />
<path
d="m 3,44.3 0.5,0 c 1.2,0 2.2,-0.9 2.4,-2 0.5,-2.4 1.1,-4.7 2,-7 6.6,-18.2 23.5,-30.4 42,-30.4 18.5,0 35.4,12.2 42,30.3 0.8,2.2 1.5,4.6 2,6.9 0.3,1.3 1.6,2.2 2.9,1.9 1.3,-0.3 2.2,-1.6 1.9,-2.9 C 98.2,38.5 97.4,36 96.5,33.5 89.2,13.5 70.5,0 49.9,0 29.3,0 10.6,13.5 3.3,33.6 2.4,36.1 1.7,38.7 1.1,41.3 l 0,0.2 L 3.5,42 1.1,41.5 C 0.8,42.8 1.7,44.1 3,44.3 M 99.8,53 C 99.9,52 99.4,51 98.4,50.5 95,48.9 90.7,47.4 85.9,46.3 84.4,45.9 82.8,45.6 81.2,45.3 77.9,44.7 74.3,44.1 70.5,43.7 69.8,36.3 66.6,30.9 61,27.1 54.7,22.9 46.9,22.7 40.1,26.5 33.5,30.1 29.3,36.6 29,43.8 17.9,45.2 8.2,47.6 1.4,50.9 1.3,50.9 1.3,51 1.2,51 1.1,51 1.1,51.1 1,51.1 l -0.1,0.1 c -0.1,0.1 -0.2,0.1 -0.2,0.2 -0.4,0.4 -0.6,1 -0.6,1.6 0,1.4 0.1,2.7 0.2,4.1 0,0.4 0.1,0.7 0.1,1.1 0.3,2.6 0.8,5.2 1.5,7.7 0.2,0.7 0.3,1.4 0.5,2 0.3,0.9 0.6,1.7 0.9,2.6 0.7,1.8 1.4,3.6 2.2,5.2 0.8,1.6 1.7,3.1 2.6,4.6 0.7,1.2 1.5,2.3 2.4,3.5 1,1.3 2,2.6 3.2,3.8 1.5,1.7 3.1,3.2 4.8,4.6 1.4,1.2 2.9,2.3 4.4,3.3 1.9,1.3 3.8,2.4 5.9,3.4 2,0.9 4,1.8 6.1,2.4 2.1,0.7 4.3,1.2 6.5,1.6 2.8,0.5 5.7,0.8 8.7,0.8 l 0.1,0 c 13.6,0 26.5,-5.9 35.8,-15.9 1.8,-1.9 3.4,-3.9 4.9,-6.1 0.2,-0.2 0.3,-0.5 0.5,-0.7 1.5,-2.4 2.9,-4.8 4,-7.4 0.9,-2 1.6,-4 2.2,-6 1.3,-4.5 2.1,-9.5 2.2,-14.6 0,0.1 0,0.1 0,0 M 85.7,51.3 c 3.3,0.8 6.3,1.8 8.9,2.9 -3.4,1.6 -6.7,2.9 -9.9,4.1 -1.9,0.7 -3.7,1.2 -5.5,1.7 -3.5,1 -7.1,1.6 -10.7,2 1.3,-4.4 2,-8.8 2.2,-13.4 3.6,0.4 7,1 10.2,1.6 1.7,0.4 3.3,0.7 4.8,1.1 m -13,23.1 C 69.5,74.8 66.2,75 63,75 c 1.5,-2.5 2.7,-5.2 3.8,-7.8 3.6,-0.2 7,-0.7 10.4,-1.5 -1.3,2.9 -2.8,5.8 -4.5,8.7 m -9.8,12.5 c -2.8,0 -5.6,-0.1 -8.4,-0.5 1.9,-2.1 3.7,-4.3 5.4,-6.5 1.2,0.1 2.5,0.1 3.7,0.1 1.8,0 3.7,-0.1 5.5,-0.2 -1.9,2.4 -4,4.8 -6.2,7.1 M 40.2,82.4 c 1.6,-1.7 3,-3.4 4.4,-5.2 3.2,0.9 6.4,1.6 9.5,2 -1.7,2.1 -3.5,4.1 -5.4,6 -2.8,-0.7 -5.6,-1.6 -8.5,-2.8 M 6.6,65 C 6.1,63.1 5.7,61.2 5.4,59.2 c 7.2,-1.8 16.6,-1.4 26.2,1.3 -0.9,2 -2,3.9 -3.2,5.8 C 19.3,64 11.6,64.2 6.6,65 m 50.7,9.7 c -3.2,-0.3 -6.5,-0.9 -9.8,-1.7 1.4,-2.2 2.6,-4.5 3.7,-6.9 3.5,0.6 6.8,1 10.1,1.1 -1.1,2.5 -2.4,5.1 -4,7.5 M 42.7,71.4 c -1.5,-0.5 -3,-1.1 -4.5,-1.8 -1.7,-0.7 -3.3,-1.3 -4.9,-1.9 1.1,-1.9 2.2,-3.8 3.1,-5.8 3.5,1.2 6.8,2.2 10,3 -1.1,2.3 -2.4,4.5 -3.7,6.5 M 30.5,72 c 1.8,0.6 3.8,1.3 5.7,2.2 1.2,0.5 2.3,1 3.5,1.4 -1.3,1.7 -2.8,3.3 -4.3,4.8 -3.1,-1.2 -6.2,-2.3 -9.3,-3 1.5,-1.8 3,-3.6 4.4,-5.4 m 12,-41.3 c 5.2,-2.9 11.1,-2.7 15.8,0.4 4.2,2.8 6.5,6.7 7.3,12 -5,-0.4 -10.2,-0.6 -15.5,-0.6 -5.5,0 -10.9,0.3 -16,0.7 0.4,-5.1 3.5,-9.8 8.4,-12.5 m 7.5,16.8 2.1,0 c 1,0 2,0 3.1,0.1 3.6,0.1 7.2,0.3 10.6,0.6 -0.2,4.8 -1,9.5 -2.5,14.1 -3.2,0 -6.5,-0.3 -10,-0.9 -1.6,-0.3 -3.2,-0.6 -4.9,-1 -3.2,-0.7 -6.6,-1.7 -10.2,-3 -0.1,0 -0.2,-0.1 -0.3,-0.1 C 36.4,56.8 35,56.3 33.5,55.9 23.9,53.1 14.6,52.4 6.8,53.9 17.1,49.8 32.9,47.5 50,47.5 M 8.2,69.7 c 4.2,-0.5 10.1,-0.7 17.2,0.9 -1.5,1.9 -3.2,3.8 -5,5.5 -3.3,-0.5 -6.5,-0.8 -9.6,-0.7 -1,-1.8 -1.9,-3.7 -2.6,-5.7 m 9.5,15.2 c -1.1,-1.2 -2.2,-2.4 -3.2,-3.8 -0.2,-0.3 -0.4,-0.5 -0.6,-0.8 0.5,0 1,0 1.5,0.1 2,0.2 4.2,0.4 6.5,0.9 2.8,0.6 6,1.5 9.3,2.7 -2.5,2.1 -5.3,4.1 -8.2,5.8 -1.8,-1.4 -3.6,-3.1 -5.3,-4.9 m 9.9,8 c 3.2,-2 6.1,-4.3 8.8,-6.8 2.7,1.2 5.3,2.2 8,2.9 -3,2.6 -6.3,5.1 -10,7.1 -2.4,-0.8 -4.7,-1.9 -6.8,-3.2 m 13.1,5.2 c 3.5,-2.3 6.6,-5 9.4,-7.5 2.7,0.5 5.3,0.9 7.9,1.1 -3.2,3 -6.3,5.4 -9.4,7.4 -2.6,-0.2 -5.3,-0.5 -7.9,-1 m 17.1,0.2 c 2.4,-1.9 4.7,-4.1 7.2,-6.6 3.6,-0.1 7.2,-0.6 10.8,-1.3 -5.3,4 -11.5,6.7 -18,7.9 M 86.7,79.2 c -1.1,1.6 -2.3,3.1 -3.5,4.5 -4.4,1.2 -8.8,2.2 -13.2,2.7 2.1,-2.4 3.9,-4.9 5.6,-7.5 4.4,-0.7 8.8,-1.7 13.1,-3 -0.7,1.2 -1.3,2.2 -2,3.3 m -7.8,-5.9 c 1.6,-3.1 3,-6.2 4.1,-9.3 3.7,-1.2 7.4,-2.6 11.3,-4.3 -0.5,3.5 -1.5,6.9 -2.7,10.2 -4.2,1.4 -8.4,2.5 -12.7,3.4"
id="path18"
style="fill:#000000"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csccsccsccscccccccccccccccscscscccscsccccccccsscccccccccccccccccccccccsccccccccccccccccccccccccccccccccscccscccccccccccccccccccccccccccccccccccccccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -38,6 +38,7 @@
//= require ./products/products
//= require ./shipping_methods/shipping_methods
//= require ./side_menu/side_menu
//= require ./tag_rules/tag_rules
//= require ./taxons/taxons
//= require ./utils/utils
//= require ./users/users

View File

@@ -1,5 +1,5 @@
angular.module("admin.customers").controller "customersCtrl", ($scope, Customers, Columns, pendingChanges, shops) ->
$scope.shop = null
angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, Columns, pendingChanges, shops) ->
$scope.shop = {}
$scope.shops = shops
$scope.submitAll = pendingChanges.submitAll
@@ -8,10 +8,26 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, Customers
code: { name: "Code", visible: true }
tags: { name: "Tags", visible: true }
$scope.$watch "shop", ->
if $scope.shop?
Customers.loaded = false
$scope.customers = Customers.index(enterprise_id: $scope.shop.id)
$scope.$watch "shop.id", ->
if $scope.shop.id?
$scope.customers = index {enterprise_id: $scope.shop.id}
$scope.loaded = ->
Customers.loaded
$scope.add = (email) ->
params =
enterprise_id: $scope.shop.id
email: email
CustomerResource.create params, (customer) =>
if customer.id
$scope.customers.push customer
$scope.quickSearch = customer.email
$scope.deleteCustomer = (customer) ->
params = id: customer.id
CustomerResource.destroy params, ->
i = $scope.customers.indexOf customer
$scope.customers.splice i, 1 unless i < 0
index = (params) ->
$scope.loaded = false
CustomerResource.index params, =>
$scope.loaded = true

View File

@@ -1 +1 @@
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.dropdown'])
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])

View File

@@ -1,8 +0,0 @@
angular.module("admin.customers").directive "tagsWithTranslation", ->
restrict: "E"
template: "<tags-input ng-model='object.tags'>"
scope:
object: "="
link: (scope, element, attrs) ->
scope.$watchCollection "object.tags", ->
scope.object.tag_list = (tag.text for tag in scope.object.tags).join(",")

View File

@@ -1,8 +1,17 @@
angular.module("admin.customers").factory 'CustomerResource', ($resource) ->
$resource('/admin/customers.json', {}, {
$resource('/admin/customers/:id.json', {}, {
'index':
method: 'GET'
isArray: true
params:
enterprise_id: '@enterprise_id'
'create':
method: 'POST'
params:
enterprise_id: '@enterprise_id'
email: '@email'
'destroy':
method: 'DELETE'
params:
id: '@id'
})

View File

@@ -1,16 +0,0 @@
angular.module("admin.customers").factory 'Customers', (CustomerResource) ->
new class Customers
customers: []
customers_by_id: {}
loaded: false
index: (params={}, callback=null) ->
CustomerResource.index params, (data) =>
for customer in data
@customers.push customer
@customers_by_id[customer.id] = customer
@loaded = true
(callback || angular.noop)(@customers)
@customers

View File

@@ -17,8 +17,9 @@ angular.module("admin.enterprises")
{ name: t('shipping_methods'), icon_class: "icon-truck", show: "showShippingMethods()" }
{ name: t('payment_methods'), icon_class: "icon-money", show: "showPaymentMethods()" }
{ name: t('enterprise_fees'), icon_class: "icon-tasks", show: "showEnterpriseFees()" }
{ name: t('inventory_settings'), icon_class: "icon-list-ol", show: "showInventorySettings()" }
{ name: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "showShopPreferences()" }
{ name: t('inventory_settings'), icon_class: "icon-list-ol", show: "enterpriseIsShop()" }
{ name: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" }
{ name: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" }
]
$scope.select(0)
@@ -42,8 +43,5 @@ angular.module("admin.enterprises")
$scope.showEnterpriseFees = ->
enterprisePermissions.can_manage_enterprise_fees && ($scope.Enterprise.sells != "none" || $scope.Enterprise.is_primary_producer)
$scope.showInventorySettings = ->
$scope.Enterprise.sells != "none"
$scope.showShopPreferences = ->
$scope.enterpriseIsShop = ->
$scope.Enterprise.sells != "none"

View File

@@ -1 +1 @@
angular.module("admin.enterprises", [
angular.module("admin.enterprises", [

View File

@@ -15,8 +15,6 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout)
element.select2
minimumResultsForSearch: scope.minSearch || 0
data: { results: scope.data, text: scope.text }
initSelection: (element, callback) ->
callback scope.data[0]
formatSelection: (item) ->
item[scope.text]
formatResult: (item) ->

View File

@@ -1,4 +1,4 @@
angular.module("admin.payment_methods")
angular.module("admin.paymentMethods")
.controller "paymentMethodCtrl", ($scope, PaymentMethods) ->
$scope.findPaymentMethodByID = (id) ->
$scope.PaymentMethod = PaymentMethods.findByID(id)
$scope.PaymentMethod = PaymentMethods.findByID(id)

View File

@@ -1 +1 @@
angular.module("admin.payment_methods", [])
angular.module("admin.paymentMethods", [])

View File

@@ -1,4 +1,4 @@
angular.module("admin.payment_methods")
angular.module("admin.paymentMethods")
.factory "PaymentMethods", (paymentMethods) ->
new class PaymentMethods
paymentMethods: paymentMethods

View File

@@ -1,4 +1,2 @@
angular.module("admin.shipping_methods")
.controller "shippingMethodCtrl", ($scope, ShippingMethods) ->
$scope.findShippingMethodByID = (id) ->
$scope.ShippingMethod = ShippingMethods.findByID(id)
angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) ->
$scope.shippingMethod = shippingMethod

View File

@@ -0,0 +1,4 @@
angular.module("admin.shippingMethods")
.controller "shippingMethodsCtrl", ($scope, ShippingMethods) ->
$scope.findShippingMethodByID = (id) ->
$scope.ShippingMethod = ShippingMethods.findByID(id)

View File

@@ -1,4 +1,4 @@
angular.module("admin.shipping_methods")
angular.module("admin.shippingMethods")
.factory "ShippingMethods", (shippingMethods) ->
new class ShippingMethods
shippingMethods: shippingMethods

View File

@@ -1 +1 @@
angular.module("admin.shipping_methods", [])
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils'])

View File

@@ -0,0 +1,48 @@
angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, enterprise) ->
$scope.tagGroups = enterprise.tag_groups
$scope.visibilityOptions = [ { id: "visible", name: "VISIBLE" }, { id: "hidden", name: "NOT VISIBLE" } ]
updateRuleCounts = ->
index = 0
for tagGroup in $scope.tagGroups
tagGroup.startIndex = index
index = index + tagGroup.rules.length
updateRuleCounts()
$scope.updateTagsRulesFor = (tagGroup) ->
for tagRule in tagGroup.rules
tagRule.preferred_customer_tags = (tag.text for tag in tagGroup.tags).join(",")
$scope.addNewRuleTo = (tagGroup, ruleType) ->
newRule =
id: null
preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
type: "TagRule::#{ruleType}"
switch ruleType
when "DiscountOrder"
newRule.calculator = { preferred_flat_percent: 0 }
when "FilterShippingMethods"
newRule.peferred_shipping_method_tags = []
newRule.preferred_matched_shipping_methods_visibility = "visible"
tagGroup.rules.push(newRule)
updateRuleCounts()
$scope.addNewTag = ->
$scope.tagGroups.push { tags: [], rules: [] }
$scope.deleteTagRule = (tagGroup, tagRule) ->
index = tagGroup.rules.indexOf(tagRule)
return unless index >= 0
if tagRule.id is null
tagGroup.rules.splice(index, 1)
updateRuleCounts()
else
if confirm("Are you sure?")
$http
method: "DELETE"
url: "/admin/enterprises/#{enterprise.id}/tag_rules/#{tagRule.id}.json"
.success ->
tagGroup.rules.splice(index, 1)
updateRuleCounts()

View File

@@ -0,0 +1,11 @@
angular.module("admin.tagRules").directive "invertNumber", ->
restrict: "A"
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
return -parseInt(viewValue) unless isNaN(parseInt(viewValue))
viewValue
ngModel.$formatters.push (modelValue) ->
return -parseInt(modelValue) unless isNaN(parseInt(modelValue))
modelValue

View File

@@ -0,0 +1,34 @@
angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templateCache, $window) ->
restrict: 'A'
scope: true
link: (scope, element, attr) ->
# Compile modal template
template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
scope.ruleTypes = [
# { id: "DiscountOrder", name: 'Apply a discount to orders' }
{ id: "FilterShippingMethods", name: 'Show/Hide shipping methods' }
]
scope.ruleType = "DiscountOrder"
# Set Dialog options
template.dialog
show: { effect: "fade", duration: 400 }
hide: { effect: "fade", duration: 300 }
autoOpen: false
resizable: false
width: $window.innerWidth * 0.4;
modal: true
open: (event, ui) ->
$('.ui-widget-overlay').bind 'click', ->
$(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close')
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
template.dialog('open')
scope.addRule = (tagGroup, ruleType) ->
scope.addNewRuleTo(tagGroup, ruleType)
template.dialog('close')
return

View File

@@ -0,0 +1,4 @@
angular.module("admin.tagRules").directive "discountOrder", ->
restrict: "E"
replace: true
templateUrl: "admin/tag_rules/discount_order.html"

View File

@@ -0,0 +1,4 @@
angular.module("admin.tagRules").directive "filterShippingMethods", ->
restrict: "E"
replace: true
templateUrl: "admin/tag_rules/filter_shipping_methods.html"

View File

@@ -0,0 +1 @@
angular.module("admin.tagRules", ['ngTagsInput'])

View File

@@ -0,0 +1,15 @@
angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) ->
restrict: "E"
template: "<tags-input ng-model='object[tagsAttr]'>"
scope:
object: "="
tagsAttr: "@?"
tagListAttr: "@?"
link: (scope, element, attrs) ->
$timeout ->
scope.tagsAttr ||= "tags"
scope.tagListAttr ||= "tag_list"
watchString = "object.#{scope.tagsAttr}"
scope.$watchCollection watchString, ->
scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",")

View File

@@ -8,6 +8,7 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterpris
$scope.show_profiles = false
$scope.filtersActive = false
$scope.distanceMatchesShown = false
$scope.filterExpression = {active: true}
$scope.$watch "query", (query)->
@@ -44,8 +45,8 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterpris
$scope.filterEnterprises = ->
es = Enterprises.hubs
$scope.nameMatches = enterpriseMatchesNameQueryFilter(es, true)
$scope.distanceMatches = enterpriseMatchesNameQueryFilter(es, false)
$scope.distanceMatches = distanceWithinKmFilter($scope.distanceMatches, 50)
noNameMatches = enterpriseMatchesNameQueryFilter(es, false)
$scope.distanceMatches = distanceWithinKmFilter(noNameMatches, 50)
$scope.updateVisibleMatches = ->
@@ -65,3 +66,9 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterpris
$scope.nameMatchesFiltered[0]
else
undefined
$scope.showClosedShops = ->
delete $scope.filterExpression['active']
$scope.hideClosedShops = ->
$scope.filterExpression['active'] = true

View File

@@ -0,0 +1,5 @@
Darkswarm.directive 'auth', (AuthenticationService) ->
restrict: 'A'
link: (scope, elem, attrs) ->
elem.bind "click", ->
AuthenticationService.open '/' + attrs.auth

View File

@@ -0,0 +1,10 @@
#new-tag-rule-dialog
.text-normal.margin-bottom-30.text-center
Select a rule type:
.text-center.margin-bottom-30
-# %select.fullwidth{ 'select2-min-search' => 5, 'ng-model' => 'newRuleType', 'ng-options' => 'ruleType.id as ruleType.name for ruleType in availableRuleTypes' }
%input.ofn-select2.fullwidth{ :id => 'rule_type_selector', ng: { model: "ruleType" }, data: "ruleTypes", 'min-search' => "5" }
.text-center
%input.button.red.icon-plus{ type: 'button', value: "Add Rule", ng: { click: 'addRule(tagGroup, ruleType)' } }

View File

@@ -0,0 +1,37 @@
%div
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
ng: { value: "rule.id" } }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
value: "TagRule::DiscountOrder" }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
ng: { value: "rule.preferred_customer_tags" } }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_type",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_type]",
value: "Spree::Calculator::FlatPercentItemTotal" }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_id",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][id]",
ng: { value: "rule.calculator.id" } }
%input{ type: "hidden",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][preferred_flat_percent]",
ng: { value: "rule.calculator.preferred_flat_percent" } }
%span.text-normal {{ $index + 1 }}. Orders are discounted by
%input{ type: "number",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent",
min: -100,
max: 100,
ng: { model: "rule.calculator.preferred_flat_percent" }, 'invert-number' => true }
%span.text-normal %

View File

@@ -0,0 +1,27 @@
%div
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
ng: { value: "rule.id" } }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
value: "TagRule::FilterShippingMethods" }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
ng: { value: "rule.preferred_customer_tags" } }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_shipping_method_tags",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_shipping_method_tags]",
ng: { value: "rule.preferred_customer_tags" } }
%span.text-normal {{ $index + 1 }}. Shipping methods with matching tags are
%input.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]",
ng: { model: "rule.preferred_matched_shipping_methods_visibility"},
data: 'visibilityOptions', "min-search" => 5 }
-# %tags-with-translation{ object: "rule", "tags-attr" => "shipping_method_tags", "tag-list-attr" => "preferred_shipping_method_tags" }

View File

@@ -0,0 +1,88 @@
/**
Main colors:
dark: #545454
light: #ccc
*/
.ui-dialog {
border: 2px solid #4a4a4a;
border-radius:3px;
padding:0px;
-moz-box-shadow: 3px 3px 4px #797979;
-webkit-box-shadow: 3px 3px 4px #797979;
box-shadow: 3px 3px 4px #797979;
/* For IE 8 */
-ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#545454')";
/* For IE 5.5 - 7 */
filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#545454');
}
.ui-dialog .ui-dialog-titlebar{
border-radius: 3px;
}
.ui-dialog .ui-state-hover {
&.ui-dialog-titlebar-close{
}
}
/*.ui-dialog .ui-icon-closethick{background:url(/static/assets/dialogCloseButton.png);}*/
.ui-dialog .ui-widget-header{
background-image: none;
background-color: #ffffff;
border:0px;
border-radius: 3px;
padding: 0px 5px 0px 5px;
}
.ui-dialog .ui-widget-content{
border: none;
border-radius: 3px;
padding: 0px 50px 30px 50px;
}
.ui-dialog .ui-corner-all{
border-radius:0px;
}
.ui-dialog {
.ui-state-hover, .ui-state-focus{
border: none;
background: none;
color: #545454;
}
}
.ui-state-hover, .ui-widget-header .ui-state-hover, .ui-widget-content .ui-state-hover {
background-color: #ffffff;
background: none;
}
.ui-dialog-titlebar-close {
float: right;
&:before {
color: #000000;
font-size: 2em;
font-weight: 400;
content: '\00d7';
display: inline;
}
&:hover {
&:before {
color: #da5354;
}
}
.ui-icon {
&.ui-icon-closethick {
display: none;
}
}
}
.ui-widget-overlay {
background: #e9e9e9;
opacity: 0.6;
}

View File

@@ -2,6 +2,10 @@
margin-bottom: 20px;
}
.margin-bottom-30 {
margin-bottom: 30px;
}
.margin-bottom-50 {
margin-bottom: 50px;
}

View File

@@ -69,6 +69,21 @@ div#group_buy_calculation {
text-indent:1em;
}
}
&.after {
span {
position: absolute;
transform: translate(0,-55%);
top:50%;
right: 0.5em;
pointer-events:none;
}
input {
padding-right: 1.2em;
}
}
}
th.actions {

View File

@@ -1,5 +1,8 @@
.select2-container {
.select2-choice {
.select2-search-choice-close {
display: none;
}
.select2-arrow {
width: 22px;
border: none;
@@ -7,4 +10,36 @@
background-color: transparent;
}
}
&.light {
.select2-choice{
background-color: #ffffff;
font-weight: normal;
border: 1px solid #5498da !important;
color: #5498da !important;
.select2-arrow {
&:before {
color: #5498da;
font-size: 1rem;
font-weight: 400;
content: '\25be';
display: inline;
}
}
}
&:hover, &.select2-container-active {
.select2-choice{
color: #ffffff !important;
background-color: #5498da !important;
.select2-arrow {
&:before {
color: #ffffff;
}
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
.no_tags {
margin-bottom: 40px;
color: #aeaeae;
font-size: 1rem;
font-weight: bold;
}
.customer_tag {
border: 1px solid #cee1f4;
margin-bottom: 40px;
.header {
padding: 8px 10px;
background-color: #eff5fc;
border-bottom: 1px solid #cee1f4;
table {
padding: 0px;
margin: 0px 0px 0px 0px;
tr {
td {
border: none;
}
}
}
}
.no_rules {
padding: 8px 10px;
margin-bottom: 10px;
color: #aeaeae;
font-size: 1rem;
font-weight: bold;
}
table {
padding: 0px;
margin: 0px 0px 10px 0px;
tr.tag_rule {
border: none;
padding: 0px;
margin: 0px;
td {
border: none;
padding: 4px 10px 10px 10px;
margin: 0px;
input {
width: auto;
}
}
}
}
.add_rule {
padding: 8px 10px;
margin-bottom: 10px;
}
}
#new-tag-rule-dialog{
.select2-chosen, .select2-result-label{
font-size: 1rem;
font-weight: lighter;
}
}

View File

@@ -7,3 +7,12 @@
font-size: 1.2rem;
font-weight: 300;
}
.text-red {
color: #DA5354;
}
input.text-big {
font-size: 1.1rem;
}

View File

@@ -30,7 +30,7 @@ footer
background-color: transparent
border: none
padding: 0
a.big-alert
a.alert-cta
@include csstrans
width: 100%
border: 1px solid rgba($dark-grey, 0.35)

View File

@@ -7,4 +7,7 @@
@include sidepaddingSm
.name-matches, .distance-matches
margin-top: 4em
margin-top: 4em
.more-controls
text-align: center

View File

@@ -84,9 +84,11 @@
padding-right: 0rem
font-size: 0.8rem
.shopfront_message, .shopfront_closed_message
.shopfront_message, .shopfront_closed_message, .shopfront_hidden_message
padding: 15px
border-radius: 5px
.shopfront_message, .shopfront_closed_message
border: 2px solid #eb4c46
.shopfront_message
@@ -94,3 +96,7 @@
.shopfront_closed_message
margin: 2em 0em
.shopfront_hidden_message
border: 2px solid #db4
margin: 2em 0em

View File

@@ -7,13 +7,25 @@ module Admin
respond_to do |format|
format.html
format.json do
render json: ActiveModel::ArraySerializer.new( @collection,
each_serializer: Api::Admin::CustomerSerializer, spree_current_user: spree_current_user
).to_json
serialised = ActiveModel::ArraySerializer.new(
@collection,
each_serializer: Api::Admin::CustomerSerializer,
spree_current_user: spree_current_user)
render json: serialised.to_json
end
end
end
def create
@customer = Customer.new(params[:customer])
if user_can_create_customer?
@customer.save
render json: Api::Admin::CustomerSerializer.new(@customer).to_json
else
redirect_to '/unauthorized'
end
end
private
def collection
@@ -25,5 +37,10 @@ module Admin
def load_managed_shops
@shops = Enterprise.managed_by(spree_current_user).is_distributor
end
def user_can_create_customer?
spree_current_user.admin? ||
spree_current_user.enterprises.include?(@customer.enterprise)
end
end
end

View File

@@ -4,7 +4,7 @@ module Admin
class EnterprisesController < ResourceController
before_filter :load_enterprise_set, :only => :index
before_filter :load_countries, :except => [:index, :register, :check_permalink]
before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create]
before_filter :load_methods_and_fees, :only => [:edit, :update]
before_filter :load_groups, :only => [:new, :edit, :update, :create]
before_filter :load_taxons, :only => [:new, :edit, :update, :create]
before_filter :check_can_change_sells, only: :update
@@ -35,6 +35,8 @@ module Admin
def update
invoke_callbacks(:update, :before)
tag_rules_attributes = params[object_name].delete :tag_rules_attributes
update_tag_rules(tag_rules_attributes) if tag_rules_attributes.present?
if @object.update_attributes(params[object_name])
invoke_callbacks(:update, :after)
flash[:success] = flash_message_for(@object, :successfully_updated)
@@ -180,6 +182,27 @@ module Admin
@taxons = Spree::Taxon.order(:name)
end
def update_tag_rules(tag_rules_attributes)
# Due to the combination of trying to use nested attributes and type inheritance
# we cannot apply all attributes to tag rules in one hit because mass assignment
# methods that are specific to each class do not become available until after the
# record is persisted. This problem is compounded by the use of calculators.
@object.transaction do
tag_rules_attributes.select{ |i, attrs| attrs[:type].present? }.each do |i, attrs|
rule = @object.tag_rules.find_by_id(attrs.delete :id) || attrs[:type].constantize.new(enterprise: @object)
create_calculator_for(rule, attrs) if rule.type == "TagRule::DiscountOrder" && rule.calculator.nil?
rule.update_attributes(attrs)
end
end
end
def create_calculator_for(rule, attrs)
if attrs[:calculator_type].present? && attrs[:calculator_attributes].present?
rule.update_attributes(calculator_type: attrs[:calculator_type])
attrs[:calculator_attributes].merge!( { id: rule.calculator.id } )
end
end
def check_can_change_bulk_sells
unless spree_current_user.admin?
params[:enterprise_set][:collection_attributes].each do |i, enterprise_params|

View File

@@ -0,0 +1,10 @@
module Admin
class TagRulesController < ResourceController
respond_to :json
respond_override destroy: { json: {
success: lambda { render nothing: true, :status => 204 }
} }
end
end

View File

@@ -0,0 +1,17 @@
module Api
class StatusesController < BaseController
respond_to :json
def job_queue
render json: {alive: job_queue_alive?}
end
private
def job_queue_alive?
Spree::Config.last_job_queue_heartbeat_at.present? &&
Time.parse(Spree::Config.last_job_queue_heartbeat_at) > 6.minutes.ago
end
end
end

View File

@@ -20,11 +20,15 @@ module Admin
end
def admin_inject_payment_methods
admin_inject_json_ams_array "admin.payment_methods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
admin_inject_json_ams_array "admin.paymentMethods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
end
def admin_inject_shipping_methods
admin_inject_json_ams_array "admin.shipping_methods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
admin_inject_json_ams_array "admin.shippingMethods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
end
def admin_inject_shipping_method
admin_inject_json_ams "admin.shippingMethods", "shippingMethod", @shipping_method, Api::Admin::ShippingMethodSerializer
end
def admin_inject_shops(ngModule='admin.customers')

View File

@@ -4,7 +4,11 @@ module EnterprisesHelper
end
def available_shipping_methods
current_distributor.shipping_methods.uniq
shipping_methods = current_distributor.shipping_methods
if current_distributor.present?
current_distributor.apply_tag_rules_to(shipping_methods, customer: current_order.customer)
end
shipping_methods.uniq
end
def managed_enterprises

View File

@@ -7,4 +7,16 @@ module ShopHelper
]
end
end
def require_customer?
current_distributor.require_login? && !user_is_related_to_distributor?
end
def user_is_related_to_distributor?
spree_current_user.present? && (
spree_current_user.admin? ||
spree_current_user.enterprises.include?(current_distributor) ||
spree_current_user.customer_of(current_distributor)
)
end
end

View File

@@ -11,11 +11,17 @@ module Spree
end
def report_payment_method_options(orders)
orders.map { |o| o.payments.first.payment_method.andand.name }.uniq
orders.map do |o|
pm = o.payments.first.payment_method
[pm.andand.name, pm.andand.id]
end.uniq
end
def report_shipping_method_options(orders)
orders.map { |o| o.shipping_method.andand.name }.uniq
orders.map do |o|
sm = o.shipping_method
[sm.andand.name, sm.andand.id]
end.uniq
end
def xero_report_types

View File

@@ -0,0 +1,5 @@
class HeartbeatJob
def perform
Spree::Config.last_job_queue_heartbeat_at = Time.now
end
end

View File

@@ -7,14 +7,14 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
preference :logo, :file
preference :logo_mobile, :file
preference :logo_mobile_svg, :file
has_attached_file :logo
has_attached_file :logo, default_url: "/assets/ofn-logo.png"
has_attached_file :logo_mobile
has_attached_file :logo_mobile_svg
has_attached_file :logo_mobile_svg, default_url: "/assets/ofn-logo-mobile.svg"
# Home page
preference :home_hero, :file
preference :home_show_stats, :boolean, default: true
has_attached_file :home_hero
has_attached_file :home_hero, default_url: "/assets/home/home.jpg"
# Producer sign-up page
preference :producer_signup_pricing_table_html, :text, default: "(TODO: Pricing table)"
@@ -33,7 +33,7 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
# Footer
preference :footer_logo, :file
has_attached_file :footer_logo
has_attached_file :footer_logo, default_url: "/assets/ofn-logo-footer.png"
preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet"
preference :footer_twitter_url, :string, default: "https://twitter.com/OpenFoodNet"
preference :footer_instagram_url, :string, default: ""

View File

@@ -4,6 +4,8 @@ class Customer < ActiveRecord::Base
belongs_to :enterprise
belongs_to :user, class_name: Spree.user_class
before_validation :downcase_email
validates :code, uniqueness: { scope: :enterprise_id, allow_blank: true, allow_nil: true }
validates :email, presence: true, uniqueness: { scope: :enterprise_id, message: I18n.t('validation_msg_is_associated_with_an_exising_customer') }
validates :enterprise_id, presence: true
@@ -14,6 +16,10 @@ class Customer < ActiveRecord::Base
private
def downcase_email
email.andand.downcase!
end
def associate_user
self.user = user || Spree::User.find_by_email(email)
end

View File

@@ -42,11 +42,13 @@ class Enterprise < ActiveRecord::Base
has_many :customers
has_many :billable_periods
has_many :inventory_items
has_many :tag_rules
delegate :latitude, :longitude, :city, :state_name, :to => :address
accepts_nested_attributes_for :address
accepts_nested_attributes_for :producer_properties, allow_destroy: true, reject_if: lambda { |pp| pp[:property_name].blank? }
accepts_nested_attributes_for :tag_rules, allow_destroy: true, reject_if: lambda { |tag_rule| tag_rule[:preferred_customer_tags].blank? }
has_attached_file :logo,
styles: { medium: "300x300>", small: "180x180>", thumb: "100x100>" },
@@ -176,17 +178,6 @@ class Enterprise < ActiveRecord::Base
end
}
def self.find_near(suburb)
enterprises = []
unless suburb.nil?
addresses = Spree::Address.near([suburb.latitude, suburb.longitude], ENTERPRISE_SEARCH_RADIUS, :units => :km).joins(:enterprise).limit(10)
enterprises = addresses.collect(&:enterprise)
end
enterprises
end
# Force a distinct count to work around relation count issue https://github.com/rails/rails/issues/5554
def self.distinct_count
count(distinct: true)
@@ -354,6 +345,13 @@ class Enterprise < ActiveRecord::Base
abn.present?
end
def apply_tag_rules_to(subject, context)
tag_rules.each do |rule|
rule.set_context(subject,context)
rule.apply
end
end
protected
def devise_mailer

View File

@@ -72,6 +72,10 @@ class AbilityDecorator
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
can [:admin, :destroy], TagRule do |tag_rule|
user.enterprises.include? tag_rule.enterprise
end
can [:admin, :index, :create], Enterprise
can [:read, :edit, :update, :bulk_update, :resend_confirmation], Enterprise do |enterprise|
OpenFoodNetwork::Permissions.new(user).editable_enterprises.include? enterprise
@@ -97,6 +101,11 @@ class AbilityDecorator
can [:print], Spree::Order do |order|
order.user == user
end
can [:create], Customer
can [:destroy], Customer do |customer|
user.enterprises.include? customer.enterprise
end
end
def add_product_management_abilities(user)

View File

@@ -20,4 +20,7 @@ Spree::AppConfiguration.class_eval do
preference :account_invoices_monthly_rate, :decimal, default: 0
preference :account_invoices_monthly_cap, :decimal, default: 0
preference :account_invoices_tax_rate, :decimal, default: 0
# Monitoring
preference :last_job_queue_heartbeat_at, :string, default: nil
end

View File

@@ -17,7 +17,8 @@ Spree::Order.class_eval do
attr_accessible :order_cycle_id, :distributor_id
before_validation :shipping_address_from_distributor
before_validation :associate_customer, unless: :customer_is_valid?
before_validation :associate_customer, unless: :customer_id?
before_validation :ensure_customer, unless: :customer_is_valid?
checkout_flow do
go_to_state :address
@@ -69,13 +70,6 @@ Spree::Order.class_eval do
where("state != ?", state)
}
scope :with_payment_method_name, lambda { |payment_method_name|
joins(:payments => :payment_method).
where('spree_payment_methods.name IN (?)', payment_method_name).
select('DISTINCT spree_orders.*')
}
# -- Methods
def products_available_from_new_distribution
# Check that the line_items in the current order are available from a newly selected distribution
@@ -179,6 +173,10 @@ Spree::Order.class_eval do
if order_cycle
OpenFoodNetwork::EnterpriseFeeCalculator.new.create_order_adjustments_for self
end
if distributor.present? && customer.present?
distributor.apply_tag_rules_to(self, customer: customer)
end
end
end
@@ -286,17 +284,21 @@ Spree::Order.class_eval do
def customer_is_valid?
return true unless require_customer?
customer.present? && customer.enterprise_id == distributor_id && customer.email == (user.andand.email || email)
customer.present? && customer.enterprise_id == distributor_id && customer.email == email_for_customer
end
def email_for_customer
(user.andand.email || email).andand.downcase
end
def associate_customer
email_for_customer = user.andand.email || email
existing_customer = Customer.of(distributor).find_by_email(email_for_customer)
if existing_customer
self.customer = existing_customer
else
new_customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user)
self.customer = new_customer
return customer if customer.present?
self.customer = Customer.of(distributor).find_by_email(email_for_customer)
end
def ensure_customer
unless associate_customer
self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user)
end
end
end

View File

@@ -1,10 +1,12 @@
Spree::ShippingMethod.class_eval do
acts_as_taggable
has_many :distributor_shipping_methods
has_many :distributors, through: :distributor_shipping_methods, class_name: 'Enterprise', foreign_key: 'distributor_id'
after_save :touch_distributors
attr_accessible :distributor_ids, :description
attr_accessible :require_ship_address
attr_accessible :require_ship_address, :tag_list
validates :distributors, presence: { message: "^At least one hub must be selected" }

View File

@@ -15,6 +15,7 @@ Spree.user_class.class_eval do
accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true
attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit
after_create :associate_customers
after_create :send_signup_confirmation
validate :limit_owned_enterprises
@@ -41,6 +42,10 @@ Spree.user_class.class_eval do
customers.of(enterprise).first
end
def associate_customers
Customer.update_all({ user_id: id }, { user_id: nil, email: email })
end
def send_signup_confirmation
Delayed::Job.enqueue ConfirmSignupJob.new(id)
end

42
app/models/tag_rule.rb Normal file
View File

@@ -0,0 +1,42 @@
class TagRule < ActiveRecord::Base
attr_accessor :subject, :context
belongs_to :enterprise
preference :customer_tags, :string, default: ""
validates :enterprise, presence: true
attr_accessible :enterprise, :enterprise_id, :preferred_customer_tags
def set_context(subject, context)
@subject = subject
@context = context
end
def apply
if relevant?
if customer_tags_match?
apply!
else
apply_default! if respond_to?(:apply_default!,true)
end
end
end
private
def relevant?
return false unless subject_class_matches?
if respond_to?(:additional_requirements_met?, true)
return false unless additional_requirements_met?
end
true
end
def customer_tags_match?
context_customer_tags = context.andand[:customer].andand.tag_list || []
preferred_tags = preferred_customer_tags.split(",")
( context_customer_tags & preferred_tags ).any?
end
end

View File

@@ -0,0 +1,23 @@
class TagRule::DiscountOrder < TagRule
calculated_adjustments
private
# Warning: this should only EVER be called via TagRule#apply
def apply!
create_adjustment(I18n.t("discount"), subject, subject)
end
def subject_class_matches?
subject.class == Spree::Order
end
def additional_requirements_met?
return false if already_applied?
true
end
def already_applied?
subject.adjustments.where(originator_id: id, originator_type: "TagRule").any?
end
end

View File

@@ -0,0 +1,32 @@
class TagRule::FilterShippingMethods < TagRule
preference :matched_shipping_methods_visibility, :string, default: "visible"
preference :shipping_method_tags, :string, default: ""
attr_accessible :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags
private
# Warning: this should only EVER be called via TagRule#apply
def apply!
unless preferred_matched_shipping_methods_visibility == "visible"
subject.reject!{ |sm| tags_match?(sm) }
end
end
def apply_default!
if preferred_matched_shipping_methods_visibility == "visible"
subject.reject!{ |sm| tags_match?(sm) }
end
end
def tags_match?(shipping_method)
shipping_method_tags = shipping_method.andand.tag_list || []
preferred_tags = preferred_shipping_method_tags.split(",")
( shipping_method_tags & preferred_tags ).any?
end
def subject_class_matches?
subject.class == Array &&
subject.all? { |i| i.class == Spree::ShippingMethod }
end
end

View File

@@ -1,4 +1,4 @@
// insert_bottom "[data-hook='admin_configurations_sidebar_menu']"
%li
= link_to 'Accounts & Billing', main_app.edit_admin_accounts_and_billing_settings_path
= link_to t(:accounts_and_billing), main_app.edit_admin_accounts_and_billing_settings_path

View File

@@ -1,6 +1,9 @@
/ replace "div[data-hook='admin_shipping_method_form_fields']"
.alpha.eleven.columns{"data-hook" => "admin_shipping_method_form_fields"}
=admin_inject_shipping_method
.alpha.eleven.columns{ "data-hook" => "admin_shipping_method_form_fields",
"ng-app" => "admin.shippingMethods",
"ng-controller" => "shippingMethodCtrl" }
.row
.alpha.three.columns
= f.label :name, t(:name)
@@ -46,6 +49,13 @@
&nbsp;
= f.label :pick_up, t(:pick_up)
.row
.alpha.three.columns
= f.label :tags, t(:tags)
.omega.eight.columns
= f.hidden_field :tag_list, "ng-value" => "shippingMethod.tag_list"
%tags-with-translation#something{ object: "shippingMethod" }
.row
.alpha.eleven.columns
= render :partial => 'spree/admin/shared/calculator_fields', :locals => { :f => f }
= render :partial => 'spree/admin/shared/calculator_fields', :locals => { :f => f }

View File

@@ -0,0 +1,7 @@
class Api::Admin::Calculator::FlatPercentItemTotalSerializer < ActiveModel::Serializer
attributes :id, :preferred_flat_percent
def preferred_flat_percent
object.preferred_flat_percent.to_i
end
end

View File

@@ -3,8 +3,25 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :producer_profile_only, :email, :long_description, :permalink
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
attributes :preferred_product_selection_from_inventory_only
attributes :owner, :users
attributes :owner, :users, :tag_groups
has_one :owner, serializer: Api::Admin::UserSerializer
has_many :users, serializer: Api::Admin::UserSerializer
def tag_groups
tag_groups = []
object.tag_rules.each do |tag_rule|
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(",").map{ |t| { text: t } })
tag_groups << tag_group if tag_group[:rules].empty?
tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash
end
tag_groups
end
def find_match(tag_groups, tags)
tag_groups.each do |tag_group|
return tag_group if tag_group[:tags].length == tags.length && (tag_group[:tags] & tags) == tag_group[:tags]
end
return { tags: tags, rules: [] }
end
end

View File

@@ -0,0 +1,7 @@
class Api::Admin::ShippingMethodSerializer < ActiveModel::Serializer
attributes :id, :name, :tags
def tags
object.tag_list.map{ |t| { text: t } }
end
end

View File

@@ -0,0 +1,27 @@
class Api::Admin::TagRuleSerializer < ActiveModel::Serializer
def serializable_hash
rule_specific_serializer.serializable_hash
end
def rule_specific_serializer
"Api::Admin::#{object.class.to_s}Serializer".constantize.new(object)
end
end
module Api::Admin::TagRule
class BaseSerializer < ActiveModel::Serializer
attributes :id, :enterprise_id, :type, :preferred_customer_tags
end
class DiscountOrderSerializer < BaseSerializer
has_one :calculator, serializer: Api::Admin::Calculator::FlatPercentItemTotalSerializer
end
class FilterShippingMethodsSerializer < BaseSerializer
attributes :preferred_matched_shipping_methods_visibility, :shipping_method_tags
def shipping_method_tags
object.preferred_shipping_method_tags.split(",")
end
end
end

View File

@@ -1,7 +1,7 @@
= render :partial => 'spree/admin/shared/configuration_menu'
- content_for :page_title do
= t(:accounts_and_billing_settings)
= t(:accounts_and_billing)
= render 'spree/shared/error_messages', target: @settings
@@ -11,7 +11,7 @@
%legend
=t :admin_settings
= form_for @settings, as: :settings, url: main_app.admin_accounts_and_billing_settings_path, :method => :put do |f|
.row{ ng: { app: t(:admin_accounts_and_billing) } }
.row{ ng: { app: 'admin.accounts_and_billing_settings' } }
.twelve.columns.alpha.omega
.field
= f.label :accounts_distributor_id, t(:accounts_administration_distributor)

View File

@@ -5,7 +5,7 @@
= admin_inject_shops
%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } }
.row{ ng: { hide: "loaded() && filteredCustomers.length > 0" } }
.row{ ng: { hide: "loaded && customers.length > 0" } }
.five.columns.alpha
%h3
=t :please_select_hub
@@ -13,25 +13,23 @@
%select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' }
.seven.columns.omega &nbsp;
.row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' }
.row{ 'ng-hide' => '!loaded || customers.length == 0' }
.controls.sixteen.columns.alpha.omega
.five.columns.alpha
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
.five.columns &nbsp;
-# =render 'admin/shared/bulk_actions_dropdown'
.three.columns &nbsp;
.eight.columns &nbsp;
= render 'admin/shared/columns_dropdown'
.row{ 'ng-if' => 'shop && !loaded()' }
.row{ 'ng-if' => 'shop.id && !loaded' }
.sixteen.columns.alpha#loading
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1
=t :loading_customers
.row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded() && filteredCustomers.length == 0'}
.row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded && filteredCustomers.length == 0'}
%h1#no_results
=t :no_customers_found
.row{ ng: { show: "loaded() && filteredCustomers.length > 0" } }
.row{ ng: { show: "loaded && filteredCustomers.length > 0" } }
%form{ name: "customers" }
%table.index#customers
%col.email{ width: "20%"}
@@ -62,3 +60,11 @@
%td.actions
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }
%input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' }
%form{ng: {show: "loaded", submit: 'add(newCustomerEmail)'}}
%h2= t '.add_new_customer'
.row
.five.columns.alpha
%input.fullwidth{type: "text", placeholder: t('.customer_placeholder'), ng: {model: 'newCustomerEmail'}}
.eleven.columns.omega
%input{type: "submit", value: t('.add_customer')}

View File

@@ -54,3 +54,7 @@
%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shop Preferences'" } }
%legend Shop Preferences
= render 'admin/enterprises/form/shop_preferences', f: f
%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='Tag Rules'" } }
%legend Tag Rules
= render 'admin/enterprises/form/tag_rules', f: f

View File

@@ -7,6 +7,7 @@
- content_for :page_actions do
%li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left'
= render 'admin/enterprises/form_data'
= render 'admin/enterprises/ng_form', action: 'edit'

View File

@@ -22,7 +22,6 @@
%a What's this?
.five.columns.omega
= f.check_box :is_primary_producer, 'ng-model' => 'Enterprise.is_primary_producer'
&nbsp;
= f.label :is_primary_producer, 'Producer'
- if spree_current_user.admin?
.row
@@ -33,15 +32,12 @@
%a What's this?
.two.columns
= f.radio_button :sells, "none", 'ng-model' => 'Enterprise.sells'
&nbsp;
= f.label :sells, "None", value: "none"
.two.columns
= f.radio_button :sells, "own", 'ng-model' => 'Enterprise.sells'
&nbsp;
= f.label :sells, "Own", value: "own"
.four.columns.omega
= f.radio_button :sells, "any", 'ng-model' => 'Enterprise.sells'
&nbsp;
= f.label :sells, "Any", value: "any"
.row
.three.columns.alpha
@@ -50,12 +46,21 @@
%a What's this?
.two.columns
= f.radio_button :visible, true
&nbsp;
= f.label :visible, "Visible", :value => "true"
.five.columns.omega
= f.radio_button :visible, false
&nbsp;
= f.label :visible, "Not Visible", :value => "false"
.row
.three.columns.alpha
%label= t '.shopfront_requires_login'
%div{'ofn-with-tip' => t('.shopfront_requires_login_tip')}
%a= t 'admin.whats_this'
.two.columns
= f.radio_button :require_login, false
= f.label :require_login, t('.shopfront_requires_login_false'), value: :false
.five.columns.omega
= f.radio_button :require_login, true
= f.label :require_login, t('.shopfront_requires_login_true'), value: :true
.permalink{ ng: { controller: "permalinkCtrl" } }
.row{ ng: { show: "Enterprise.sells == 'own' || Enterprise.sells == 'any'" } }
.three.columns.alpha

View File

@@ -7,7 +7,7 @@
%th
%tbody
- @shipping_methods.each do |shipping_method|
%tr{ ng: { controller: 'shippingMethodCtrl', init: "findShippingMethodByID(#{shipping_method.id})" } }
%tr{ ng: { controller: 'shippingMethodsCtrl', init: "findShippingMethodByID(#{shipping_method.id})" } }
%td= shipping_method.name
%td= f.check_box :shipping_method_ids, { :multiple => true, 'ng-model' => 'ShippingMethod.selected' }, shipping_method.id, nil
%td= link_to "Edit", edit_admin_shipping_method_path(shipping_method)

View File

@@ -0,0 +1,33 @@
.row{ ng: { controller: "TagRulesCtrl" } }
.eleven.columns.alpha.omega
.eleven.columns.alpha.omega
.no_tags{ ng: { show: "tagGroups.length == 0" } }
No tags apply to this enterprise yet
.customer_tag{ ng: { repeat: "tagGroup in tagGroups" }, bindonce: true }
.header
%table
%colgroup
%col{width: '35%'}
%col{width: '65%'}
%tr
%td
%h5
For customers tagged:
%td
%tags-input{ ng: { model: 'tagGroup.tags'},
min: { tags: "1" },
on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } }
.no_rules{ ng: { show: "tagGroup.rules.length == 0" } }
No rules apply to this tag yet
%table
%tr.tag_rule{ id: "tr_{{rule.id}}", ng: { repeat: "rule in tagGroup.rules" } }
%td
%discount-order{ bo: { if: "rule.type == 'TagRule::DiscountOrder'" } }
%filter-shipping-methods{ bo: { if: "rule.type == 'TagRule::FilterShippingMethods'" } }
%td.actions
%a{ ng: { click: "deleteTagRule(tagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" }
.add_rule.text-center
%input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "new-tag-rule-dialog" => true }
.add_tage
%input.button.red.icon-plus{ type: 'button', value: "+ Add A New Tag", ng: { click: 'addNewTag()' } }

View File

@@ -1,4 +1,4 @@
.filters.sixteen.columns.alpha
.filters.sixteen.columns.alpha.omega
.filter.four.columns.alpha
%label{ :for => 'query', ng: {class: '{disabled: !hub_id}'} }=t('admin.quick_search')
%br

View File

@@ -1,4 +1,4 @@
.sixteen.columns.alpha.omega.text-center{ ng: {show: 'productLimit < filteredProducts.length'}}
.text-center
%input{ type: 'button', value: 'Show More', ng: { click: 'productLimit = productLimit + 10' } }
or
%input{ type: 'button', value: "Show All ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } }

View File

@@ -1,11 +1,11 @@
%section{"ng-show" => "!enabled"}
.row
.small-12.columns.text-center{"ng-controller" => "AuthenticationCtrl"}
.small-12.columns.text-center
%h3.pad-top
= t :checkout_headline
.row.pad-top
.small-5.columns.text-center{"ng-controller" => "AuthenticationCtrl"}
%button.primary.expand{"ng-click" => "open()"}
.small-5.columns.text-center
%button.primary.expand{"auth" => "login"}
= t :label_login
.small-2.columns.text-center
%p.pad-top= "-#{t :action_or}-"

View File

@@ -22,6 +22,7 @@
%select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
"ofn-change-order-cycle" => true,
"disabled" => require_customer?,
"ng-options" => "oc.id as oc.time for oc in #{@order_cycles.map {|oc| {time: pickup_time(oc), id: oc.id}}.to_json}",
"popover-placement" => "left", "popover" => t(:enterprises_choose), "popover-trigger" => "openTrigger"}
@@ -31,7 +32,7 @@
= render partial: 'shop/messages'
.row
= render partial: "shop/products/form"
- unless require_customer?
.row= render partial: "shop/products/form"
= render partial: "shared/footer"

View File

@@ -27,3 +27,9 @@
.show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"}
%a{href: "", "ng-click" => "showDistanceMatches()"}
= t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}"
.more-controls
%a.button{href: "", ng: {click: "showClosedShops()", show: "filterExpression.active"}}
= t '.show_closed_shops'
%a.button{href: "", ng: {click: "hideClosedShops()", show: "!filterExpression.active"}}
= t '.hide_closed_shops'
%a.button{href: main_app.map_path}= t '.show_on_map'

View File

@@ -1,5 +1,5 @@
.active_table
%hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | visible | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])",
%hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | filter:filterExpression | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])",
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
"ng-controller" => "HubNodeCtrl",
id: "{{hub.hash}}"}

View File

@@ -7,13 +7,7 @@
.row
.small-12.medium-8.medium-offset-2.columns.text-center
.alert-box
%a.big-alert{href: "http://www.openfoodnetwork.org", target: "_blank"}
%h6
= t :alert_selling_on_ofn
&nbsp;
%strong
= t :alert_start_here
%i.ofn-i_054-point-right
= render 'shared/register_call'
.row
.small-12.medium-4.medium-offset-2.columns.text-center
%h6

View File

@@ -0,0 +1,7 @@
%a.alert-cta{href: registration_path, target: "_blank"}
%h6
= t '.selling_on_ofn'
&nbsp;
%strong
= t '.register'
%i.ofn-i_054-point-right

View File

@@ -1,5 +1,5 @@
%li#login-link{"ng-controller" => "AuthenticationCtrl"}
%a{"ng-click" => "open()"}
%li#login-link
%a{"auth" => "login"}
%i.ofn-i_017-locked
%span
= t 'label_login'

View File

@@ -1,10 +1,4 @@
.text-center.page-alert.fixed{ "ofn-page-alert" => true }
.alert-box
%a.alert-cta{href: registration_path, target: "_blank"}
%h6
= t 'alert_selling_on_ofn'
&nbsp;
%strong
= t 'alert_start_here'
%i.ofn-i_054-point-right
= render 'shared/register_call'
%a.close{ ng: { click: "close()" } } &times;

View File

@@ -1,5 +1,18 @@
- if @order_cycles and @order_cycles.empty?
- if require_customer?
.row.footer-pad
.small-12.columns
.shopfront_hidden_message
= t '.require_customer_login'
- if spree_current_user.nil?
= t '.require_login_html',
{login: ('<a auth="login">' + t('.login') + '</a>').html_safe,
register: ('<a auth="signup">' + t('.register') + '</a>').html_safe}
- else
= t '.require_customer_html',
{contact: ('<a href="##contact">' + t('.contact') + '</a>').html_safe,
enterprise: current_distributor.name}
- elsif @order_cycles and @order_cycles.empty?
- if current_distributor.preferred_shopfront_closed_message.present?
.row
.small-12.columns

View File

@@ -33,7 +33,7 @@
%br
%table#listing_customers.index
%thead
%tr{'data-hook' => "orders_header" }
%tr{'data-hook' => "orders_header"}
- @report.header.each do |heading|
%th=heading
%tbody

View File

@@ -33,7 +33,7 @@
%br
%table#listing_ocm_orders.index
%thead
%tr{'data-hook' => "orders_header" }
%tr{'data-hook' => "orders_header"}
- @report.header.each do |heading|
%th=heading
%tbody

View File

@@ -10,7 +10,7 @@
%br
%table#listing_orders.index
%thead
%tr{'data-hook' => t(:report_customers_header)}
%tr{'data-hook' => 'orders_header'}
- @report.header.each do |heading|
%th=heading
%tbody
@@ -21,4 +21,3 @@
- if @report.table.empty?
%tr
%td{:colspan => "2"}= t(:none)

View File

@@ -1,7 +1,7 @@
- if enterprise
-if shop_trial_in_progress?(enterprise)
#trial_progress_bar
= t(:shop_trial_in_progress)
= t(:shop_trial_in_progress, days: remaining_trial_days(enterprise))
-elsif shop_trial_expired?(enterprise)
#trial_progress_bar
= t(:shop_trial_expired)
= t(:shop_trial_expired)

View File

@@ -0,0 +1 @@
ActsAsTaggableOn.force_lowercase = true

View File

@@ -10,6 +10,12 @@ en-GB:
password:
confirmation: you have successfully registered
too_short: pick a longer name
# Overridden here due to a bug in spree i18n (Issue #870)
attributes:
spree/order:
payment_state: Payment State
shipment_state: Shipment State
devise:
failure:
invalid: |

View File

@@ -15,6 +15,12 @@
# See http://community.openfoodnetwork.org/t/localisation-ofn-in-your-language/397
en:
activerecord:
# Overridden here due to a bug in spree i18n (Issue #870)
attributes:
spree/order:
payment_state: Payment State
shipment_state: Shipment State
devise:
failure:
invalid: |
@@ -74,6 +80,10 @@ en:
whats_this: What's this?
customers:
index:
add_customer: "Add customer"
customer_placeholder: "customer@example.org"
inventory:
title: Inventory
description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
@@ -105,6 +115,32 @@ en:
enterprise:
select_outgoing_oc_products_from: Select outgoing OC products from
enterprises:
form:
primary_details:
shopfront_requires_login: "Shopfront requires login?"
shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront."
shopfront_requires_login_false: "Public"
shopfront_requires_login_true: "Require customers to login"
home:
hubs:
show_closed_shops: "Show closed shops"
hide_closed_shops: "Hide closed shops"
show_on_map: "Show all on the map"
shared:
register_call:
selling_on_ofn: "Interested in getting on the Open Food Network?"
register: "Register here"
shop:
messages:
login: "login"
register: "register"
contact: "contact"
require_customer_login: "This shop is for customers only."
require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer."
require_customer_html: "Please %{contact} %{enterprise} to become a customer."
# Printable Invoice Columns
invoice_column_item: "Item"
invoice_column_qty: "Qty"
@@ -145,8 +181,6 @@ en:
on_demand: On demand
none: None
alert_selling_on_ofn: "Interested in selling food on the Open Food Network?"
alert_start_here: "Start here"
label_shops: "Shops"
label_map: "Map"
label_producers: "Producers"
@@ -748,7 +782,7 @@ Please follow the instructions there to make your enterprise visible on the Open
shop_variant_quantity_max: "max"
contact: "Contact"
follow: "Follow"
shop_for_products_html: "Shop for <span class=\"turquoise\">%{enterprise}</span> products at:" #FIXME
shop_for_products_html: "Shop for <span class=\"turquoise\">%{enterprise}</span> products at:"
change_shop: "Change shop to:"
shop_at: "Shop now at:"
price_breakdown: "Full price breakdown"
@@ -840,13 +874,12 @@ Please follow the instructions there to make your enterprise visible on the Open
go: "Go"
hub: "Hub"
accounts_administration_distributor: "accounts administration distributor"
admin_accounts_and_billing: "admin.accounts_and_billing_settings" #FIXME
accounts_and_billing: "Accounts & Billing"
producer: "Producer"
product: "Product"
price: "Price"
on_hand: "On hand"
save_changes: "Save Changes"
update_action: "update()" #FIXME
spree_admin_overview_enterprises_header: "My Enterprises"
spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES"
spree_admin_enterprises_hubs_name: "Name"
@@ -882,8 +915,8 @@ Please follow the instructions there to make your enterprise visible on the Open
live: "live"
manage: "Manage"
resend: "Resend"
add_and_manage_products: "Add &amp; manage products"
add_and_manage_order_cycles: "Add &amp; manage order cycles"
add_and_manage_products: "Add & manage products"
add_and_manage_order_cycles: "Add & manage order cycles"
manage_order_cycles: "Manage order cycles"
manage_products: "Manage products"
edit_profile_details: "Edit profile details"
@@ -916,14 +949,13 @@ Please follow the instructions there to make your enterprise visible on the Open
hub_sidebar_at_least: "At least one hub must be selected"
hub_sidebar_blue: "blue"
hub_sidebar_red: "red"
shop_trial_in_progress: "Your shopfront trial expires in #{remaining_trial_days(enterprise)}." #FIXME
shop_trial_in_progress: "Your shopfront trial expires in %{days}."
shop_trial_expired: "Good news! We have decided to extend shopfront trials until further notice (probably around March 2015)." #FIXME
report_customers_distributor: "Distributor"
report_customers_supplier: "Supplier"
report_customers_cycle: "Order Cycle"
report_customers_type: "Report Type"
report_customers_csv: "Download as csv"
report_customers_header: "orders header"
report_producers: "Producers: "
report_type: "Report Type: "
report_hubs: "Hubs: "
@@ -934,8 +966,6 @@ Please follow the instructions there to make your enterprise visible on the Open
report_payment_totals: 'Payment Totals'
report_all: 'all'
report_order_cycle: "Order Cycle: "
report_product_header: "products_header"
report_order_header: "orders_header"
report_entreprises: "Enterprises: "
report_users: "Users: "
initial_invoice_number: "Initial invoice number:"
@@ -944,6 +974,7 @@ Please follow the instructions there to make your enterprise visible on the Open
account_code: "Account code:"
equals: "Equals"
contains: "contains"
discount: "Discount"
filter_products: "Filter Products"
delete_product_variant: "The last variant cannot be deleted!"
progress: "progress"
@@ -972,6 +1003,7 @@ Please follow the instructions there to make your enterprise visible on the Open
payment_methods: "Payment Methods"
enterprise_fees: "Enterprise Fees"
inventory_settings: "Inventory Settings"
tag_rules: "Tag Rules"
shop_preferences: "Shop Preferences"
validation_msg_relationship_already_established: "^That relationship is already established."
validation_msg_at_least_one_hub: "^At least one hub must be selected"

View File

@@ -87,6 +87,8 @@ Openfoodnetwork::Application.routes.draw do
resources :producer_properties do
post :update_positions, on: :collection
end
resources :tag_rules, only: [:destroy]
end
resources :enterprise_relationships
@@ -113,7 +115,7 @@ Openfoodnetwork::Application.routes.draw do
resources :inventory_items, only: [:create, :update]
resources :customers, only: [:index, :update]
resources :customers, only: [:index, :create, :update, :destroy]
resource :content
@@ -141,6 +143,10 @@ Openfoodnetwork::Application.routes.draw do
get :managed, on: :collection
get :accessible, on: :collection
end
resource :status do
get :job_queue
end
end
namespace :open_food_network do

View File

@@ -4,8 +4,11 @@ require 'whenever'
env "MAILTO", "rohan@rohanmitchell.com"
# If we use -e with a file containing specs, rspec interprets it and filters out our examples
job_type :run_file, "cd :path; :environment_variable=:environment bundle exec script/rails runner :task :output"
job_type :enqueue_job, "cd :path; :environment_variable=:environment bundle exec script/enqueue :task :priority :output"
every 1.hour do
rake 'openfoodnetwork:cache:check_products_integrity'
@@ -23,6 +26,10 @@ every 4.hours do
rake 'db2fog:backup'
end
every 5.minutes do
enqueue_job 'HeartbeatJob', priority: 0
end
every 1.day, at: '1:00am' do
rake 'openfoodnetwork:billing:update_account_invoices'
end

View File

@@ -0,0 +1,10 @@
class CreateTagRules < ActiveRecord::Migration
def change
create_table :tag_rules do |t|
t.references :enterprise, null: false, index: true
t.string :type, null: false
t.timestamps
end
end
end

View File

@@ -0,0 +1,5 @@
class AddRequireLoginToEnterprise < ActiveRecord::Migration
def change
add_column :enterprises, :require_login, :boolean, default: false, null: false
end
end

View File

@@ -0,0 +1,15 @@
class ChangeValueTypeOfPaypalPasswords < ActiveRecord::Migration
def up
Spree::Preference
.where("key like ?", "spree/gateway/pay_pal_express/password/%")
.where(value_type: "string")
.update_all(value_type: "password")
end
def down
Spree::Preference
.where("key like ?", "spree/gateway/pay_pal_express/password/%")
.where(value_type: "password")
.update_all(value_type: "string")
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20160302044850) do
ActiveRecord::Schema.define(:version => 20160401043927) do
create_table "account_invoices", :force => true do |t|
t.integer "user_id", :null => false
@@ -348,6 +348,7 @@ ActiveRecord::Schema.define(:version => 20160302044850) do
t.string "permalink", :null => false
t.boolean "charges_sales_tax", :default => false, :null => false
t.string "email_address"
t.boolean "require_login", :default => false, :null => false
end
add_index "enterprises", ["address_id"], :name => "index_enterprises_on_address_id"
@@ -1146,6 +1147,13 @@ ActiveRecord::Schema.define(:version => 20160302044850) do
t.integer "state_id"
end
create_table "tag_rules", :force => true do |t|
t.integer "enterprise_id", :null => false
t.string "type", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "taggings", :force => true do |t|
t.integer "tag_id"
t.integer "taggable_id"

View File

@@ -31,7 +31,7 @@ module OpenFoodNetwork
ba.phone,
order.distributor.andand.name,
[da.andand.address1, da.andand.address2, da.andand.city].join(" "),
order.shipping_method.andand.name
order.shipping_method.andand.name
]
end
end
@@ -78,4 +78,3 @@ module OpenFoodNetwork
end
end
end

Some files were not shown because too many files have changed in this diff Show More