mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-02 21:57:17 +00:00
Merge branch 'uk/trial-length' of https://github.com/openfoodfoundation/openfoodnetwork into uk/trial-length
This commit is contained in:
@@ -38,7 +38,7 @@ before_script:
|
||||
script:
|
||||
- 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi'
|
||||
#- "KNAPSACK_GENERATE_REPORT=true bundle exec rspec spec"
|
||||
- "bundle exec rake knapsack:rspec"
|
||||
- "bundle exec rake 'knapsack:rspec[--tag ~performance]'"
|
||||
|
||||
after_success:
|
||||
- >
|
||||
|
||||
11
Gemfile
11
Gemfile
@@ -5,16 +5,18 @@ gem 'rails', '3.2.21'
|
||||
gem 'rails-i18n', '~> 3.0.0'
|
||||
gem 'i18n', '~> 0.6.11'
|
||||
|
||||
gem 'nokogiri'
|
||||
# Patched version. See http://rubysec.com/advisories/CVE-2015-5312/.
|
||||
gem 'nokogiri', '>= 1.6.7.1'
|
||||
|
||||
gem 'pg'
|
||||
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'
|
||||
@@ -55,6 +57,7 @@ gem 'figaro'
|
||||
gem 'blockenspiel'
|
||||
gem 'acts-as-taggable-on', '~> 3.4'
|
||||
gem 'paper_trail', '~> 3.0.8'
|
||||
gem 'diffy'
|
||||
|
||||
gem 'wicked_pdf'
|
||||
gem 'wkhtmltopdf-binary'
|
||||
|
||||
17
Gemfile.lock
17
Gemfile.lock
@@ -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)
|
||||
@@ -248,6 +248,7 @@ GEM
|
||||
devise-encryptable (0.1.2)
|
||||
devise (>= 2.1.0)
|
||||
diff-lcs (1.2.4)
|
||||
diffy (3.1.0)
|
||||
em-websocket (0.5.0)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.5.3)
|
||||
@@ -449,7 +450,7 @@ GEM
|
||||
treetop (~> 1.4.8)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.6.2)
|
||||
mini_portile2 (2.0.0)
|
||||
momentjs-rails (2.5.1)
|
||||
railties (>= 3.1)
|
||||
money (5.1.1)
|
||||
@@ -457,8 +458,8 @@ GEM
|
||||
multi_json (1.11.2)
|
||||
multi_xml (0.5.5)
|
||||
newrelic_rpm (3.12.0.288)
|
||||
nokogiri (1.6.6.4)
|
||||
mini_portile (~> 0.6.0)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
oj (2.1.2)
|
||||
orm_adapter (0.5.0)
|
||||
paper_trail (3.0.8)
|
||||
@@ -668,6 +669,7 @@ DEPENDENCIES
|
||||
debugger-linecache
|
||||
deface!
|
||||
delayed_job_active_record
|
||||
diffy
|
||||
factory_girl_rails
|
||||
figaro
|
||||
foreigner
|
||||
@@ -691,7 +693,7 @@ DEPENDENCIES
|
||||
letter_opener
|
||||
momentjs-rails
|
||||
newrelic_rpm
|
||||
nokogiri
|
||||
nokogiri (>= 1.6.7.1)
|
||||
oj
|
||||
paper_trail (~> 3.0.8)
|
||||
paperclip
|
||||
@@ -730,3 +732,6 @@ DEPENDENCIES
|
||||
whenever
|
||||
wicked_pdf
|
||||
wkhtmltopdf-binary
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
||||
|
||||
BIN
app/assets/images/ofn-logo-footer.png
Normal file
BIN
app/assets/images/ofn-logo-footer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
80
app/assets/images/ofn-logo-mobile.svg
Normal file
80
app/assets/images/ofn-logo-mobile.svg
Normal 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 |
BIN
app/assets/images/ofn-logo.png
Normal file
BIN
app/assets/images/ofn-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
@@ -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
|
||||
@@ -45,5 +46,8 @@
|
||||
//= require textAngular.min.js
|
||||
//= require textAngular-sanitize.min.js
|
||||
//= require ../shared/bindonce.min.js
|
||||
//= require darkswarm/i18n.js
|
||||
//= require darkswarm/i18n.translate.js
|
||||
|
||||
|
||||
//= require_tree .
|
||||
|
||||
@@ -12,25 +12,25 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
|
||||
$scope.startDate = formatDate start
|
||||
$scope.endDate = formatDate end
|
||||
$scope.quickSearch = ""
|
||||
$scope.bulkActions = [ { name: "Delete Selected", callback: $scope.deleteLineItems } ]
|
||||
$scope.bulkActions = [ { name: t("bom_actions_delete"), callback: $scope.deleteLineItems } ]
|
||||
$scope.selectedBulkAction = $scope.bulkActions[0]
|
||||
$scope.selectedUnitsProduct = {};
|
||||
$scope.selectedUnitsVariant = {};
|
||||
$scope.sharedResource = false
|
||||
$scope.columns = Columns.setColumns
|
||||
order_no: { name: "Order No.", visible: false }
|
||||
full_name: { name: "Name", visible: true }
|
||||
email: { name: "Email", visible: false }
|
||||
phone: { name: "Phone", visible: false }
|
||||
order_date: { name: "Order Date", visible: true }
|
||||
producer: { name: "Producer", visible: true }
|
||||
order_cycle: { name: "Order Cycle", visible: false }
|
||||
hub: { name: "Hub", visible: false }
|
||||
variant: { name: "Variant", visible: true }
|
||||
quantity: { name: "Quantity", visible: true }
|
||||
max: { name: "Max", visible: true }
|
||||
final_weight_volume: { name: "Weight/Volume", visible: false }
|
||||
price: { name: "Price", visible: false }
|
||||
order_no: { name: t("bom_no"), visible: false }
|
||||
full_name: { name: t("name"), visible: true }
|
||||
email: { name: t("email"), visible: false }
|
||||
phone: { name: t("phone"), visible: false }
|
||||
order_date: { name: t("bom_date"), visible: true }
|
||||
producer: { name: t("producer"), visible: true }
|
||||
order_cycle: { name: t("bom_cycle"), visible: false }
|
||||
hub: { name: t("bom_hub"), visible: false }
|
||||
variant: { name: t("bom_variant"), visible: true }
|
||||
quantity: { name: t("bom_quantity"), visible: true }
|
||||
max: { name: t("bom_max"), visible: true }
|
||||
final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
|
||||
price: { name: t("price"), visible: false }
|
||||
$scope.initialise = ->
|
||||
$scope.initialiseVariables()
|
||||
authorise_api_reponse = ""
|
||||
|
||||
@@ -4,32 +4,32 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.columns = Columns.setColumns
|
||||
producer: {name: "Producer", visible: true}
|
||||
sku: {name: "SKU", visible: false}
|
||||
name: {name: "Name", visible: true}
|
||||
unit: {name: "Unit", visible: true}
|
||||
price: {name: "Price", visible: true}
|
||||
on_hand: {name: "On Hand", visible: true}
|
||||
on_demand: {name: "On Demand", visible: false}
|
||||
category: {name: "Category", visible: false}
|
||||
tax_category: {name: "Tax Category", visible: false}
|
||||
inherits_properties: {name: "Inherits Properties?", visible: false}
|
||||
available_on: {name: "Available On", visible: false}
|
||||
producer: {name: t("products_producer"), visible: true}
|
||||
sku: {name: t("products_sku"), visible: false}
|
||||
name: {name: t("products_name"), visible: true}
|
||||
unit: {name: t("products_unit"), visible: true}
|
||||
price: {name: t("products_price"), visible: true}
|
||||
on_hand: {name: t("products_on_hand"), visible: true}
|
||||
on_demand: {name: t("products_on_demand"), visible: false}
|
||||
category: {name: t("products_category"), visible: false}
|
||||
tax_category: {name: t("products_tax_category"), visible: false}
|
||||
inherits_properties: {name: t("products_inherits_properties"), visible: false}
|
||||
available_on: {name: t("products_available_on"), visible: false}
|
||||
|
||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||
|
||||
$scope.filterableColumns = [
|
||||
{ name: "Producer", db_column: "producer_name" },
|
||||
{ name: "Name", db_column: "name" }
|
||||
{ name: t("label_producers"), db_column: "producer_name" },
|
||||
{ name: t("name"), db_column: "name" }
|
||||
]
|
||||
|
||||
$scope.filterTypes = [
|
||||
{ name: "Equals", predicate: "eq" },
|
||||
{ name: "Contains", predicate: "cont" }
|
||||
{ name: t("equals"), predicate: "eq" },
|
||||
{ name: t("contains"), predicate: "cont" }
|
||||
]
|
||||
|
||||
$scope.optionTabs =
|
||||
filters: { title: "Filter Products", visible: false }
|
||||
filters: { title: t("filter_products"), visible: false }
|
||||
|
||||
|
||||
$scope.producers = producers
|
||||
@@ -105,7 +105,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
$scope.categoryFilter = "0"
|
||||
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or (DirtyProducts.count() == 0)
|
||||
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
|
||||
|
||||
@@ -150,14 +150,14 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
if !$scope.variantSaved(variant)
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
if confirm("Are you sure?")
|
||||
if confirm(t("are_you_sure"))
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
|
||||
).success (data) ->
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
alert("The last variant cannot be deleted!")
|
||||
alert(t("delete_product_variant"))
|
||||
|
||||
$scope.removeVariant = (product, variant) ->
|
||||
product.variants.splice product.variants.indexOf(variant), 1
|
||||
@@ -194,7 +194,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
if productsToSubmit.length > 0
|
||||
$scope.updateProducts productsToSubmit # Don't submit an empty list
|
||||
else
|
||||
StatusMessage.display 'alert', 'No changes to save.'
|
||||
StatusMessage.display 'alert', t("products_change")
|
||||
|
||||
|
||||
$scope.updateProducts = (productsToSubmit) ->
|
||||
@@ -212,10 +212,10 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
).error (data, status) ->
|
||||
if status == 400 && data.errors? && data.errors.length > 0
|
||||
errors = error + "\n" for error in data.errors
|
||||
alert "Saving failed with the following error(s):\n" + errors
|
||||
$scope.displayFailure "Save failed due to invalid data"
|
||||
alert t("products_update_error") + "\n" + errors
|
||||
$scope.displayFailure t("products_update_error")
|
||||
else
|
||||
$scope.displayFailure "Server returned with error status: " + status
|
||||
$scope.displayFailure t("products_update_error_data") + status
|
||||
|
||||
|
||||
$scope.packProduct = (product) ->
|
||||
@@ -253,23 +253,23 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
|
||||
|
||||
$scope.displayUpdating = ->
|
||||
StatusMessage.display 'progress', 'Saving...'
|
||||
StatusMessage.display 'progress', t("saving")
|
||||
|
||||
|
||||
$scope.displaySuccess = ->
|
||||
StatusMessage.display 'success', 'Changes saved.'
|
||||
StatusMessage.display 'success',t("products_changes_saved")
|
||||
|
||||
|
||||
$scope.displayFailure = (failMessage) ->
|
||||
StatusMessage.display 'failure', "Saving failed. #{failMessage}"
|
||||
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
|
||||
|
||||
|
||||
$scope.displayDirtyProducts = ->
|
||||
if DirtyProducts.count() > 0
|
||||
message = if DirtyProducts.count() == 1 then "one product" else DirtyProducts.count() + " products"
|
||||
StatusMessage.display 'notice', "Changes to #{message} remain unsaved."
|
||||
else
|
||||
StatusMessage.clear()
|
||||
count = DirtyProducts.count()
|
||||
switch count
|
||||
when 0 then StatusMessage.clear()
|
||||
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
|
||||
else StatusMessage.display 'notice', t("products_unsaved", n: count)
|
||||
|
||||
|
||||
filterSubmitProducts = (productsToFilter) ->
|
||||
|
||||
@@ -7,7 +7,7 @@ angular.module("ofn.admin").controller "AdminEnterpriseRelationshipsCtrl", ($sco
|
||||
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id, $scope.permissions)
|
||||
|
||||
$scope.delete = (enterprise_relationship) ->
|
||||
if confirm("Are you sure?")
|
||||
if confirm(t("are_you_sure"))
|
||||
$scope.EnterpriseRelationships.delete enterprise_relationship
|
||||
|
||||
$scope.toggleKeyword = (string, key) ->
|
||||
|
||||
@@ -7,5 +7,5 @@ angular.module("ofn.admin").controller "AdminEnterpriseRolesCtrl", ($scope, Ente
|
||||
$scope.EnterpriseRoles.create($scope.user_id, $scope.enterprise_id)
|
||||
|
||||
$scope.delete = (enterprise_role) ->
|
||||
if confirm("Are you sure?")
|
||||
if confirm(t('are_you_sure'))
|
||||
$scope.EnterpriseRoles.delete enterprise_role
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.dropdown'])
|
||||
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])
|
||||
@@ -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(",")
|
||||
@@ -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'
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -5,11 +5,11 @@ angular.module("admin.enterprise_groups")
|
||||
|
||||
$scope.menu.setItems [
|
||||
{ name: 'Primary Details', icon_class: "icon-user" }
|
||||
{ name: 'Users', icon_class: "icon-user" }
|
||||
{ name: 'About', icon_class: "icon-pencil" }
|
||||
{ name: 'Images', icon_class: "icon-picture" }
|
||||
{ name: 'Contact', icon_class: "icon-phone" }
|
||||
{ name: 'Web', icon_class: "icon-globe" }
|
||||
{ name: (t('users')), icon_class: "icon-user" }
|
||||
{ name: (t('about')), icon_class: "icon-pencil" }
|
||||
{ name: (t('images')), icon_class: "icon-picture" }
|
||||
{ name: (t('contact')), icon_class: "icon-phone" }
|
||||
{ name: (t('web')), icon_class: "icon-globe" }
|
||||
]
|
||||
|
||||
$scope.select(0)
|
||||
|
||||
@@ -6,7 +6,7 @@ angular.module("admin.enterprises")
|
||||
$scope.navClear = NavigationCheck.clear
|
||||
$scope.pristineEmail = $scope.Enterprise.email
|
||||
$scope.menu = SideMenu
|
||||
$scope.newManager = { id: '', email: 'Add a manager...' }
|
||||
$scope.newManager = { id: '', email: (t('add_manager')) }
|
||||
|
||||
# Provide a callback for generating warning messages displayed before leaving the page. This is passed in
|
||||
# from a directive "nav-check" in the page - if we pass it here it will be called in the test suite,
|
||||
@@ -31,4 +31,4 @@ angular.module("admin.enterprises")
|
||||
if (user for user in $scope.Enterprise.users when user.id == manager.id).length == 0
|
||||
$scope.Enterprise.users.push manager
|
||||
else
|
||||
alert "#{manager.email} is already a manager!"
|
||||
alert ("#{manager.email}" + " " + t("is_already_manager"))
|
||||
|
||||
@@ -5,20 +5,21 @@ angular.module("admin.enterprises")
|
||||
$scope.select = SideMenu.select
|
||||
|
||||
$scope.menu.setItems [
|
||||
{ name: 'Primary Details', icon_class: "icon-home" }
|
||||
{ name: 'Users', icon_class: "icon-user" }
|
||||
{ name: 'Address', icon_class: "icon-map-marker" }
|
||||
{ name: 'Contact', icon_class: "icon-phone" }
|
||||
{ name: 'Social', icon_class: "icon-twitter" }
|
||||
{ name: 'About', icon_class: "icon-pencil" }
|
||||
{ name: 'Business Details', icon_class: "icon-briefcase" }
|
||||
{ name: 'Images', icon_class: "icon-picture" }
|
||||
{ name: "Properties", icon_class: "icon-tags", show: "showProperties()" }
|
||||
{ name: "Shipping Methods", icon_class: "icon-truck", show: "showShippingMethods()" }
|
||||
{ name: "Payment Methods", icon_class: "icon-money", show: "showPaymentMethods()" }
|
||||
{ name: "Enterprise Fees", icon_class: "icon-tasks", show: "showEnterpriseFees()" }
|
||||
{ name: "Inventory Settings", icon_class: "icon-list-ol", show: "showInventorySettings()" }
|
||||
{ name: "Shop Preferences", icon_class: "icon-shopping-cart", show: "showShopPreferences()" }
|
||||
{ name: t('primary_details'), icon_class: "icon-home" }
|
||||
{ name: t('users'), icon_class: "icon-user" }
|
||||
{ name: t('address'), icon_class: "icon-map-marker" }
|
||||
{ name: t('contact'), icon_class: "icon-phone" }
|
||||
{ name: t('social'), icon_class: "icon-twitter" }
|
||||
{ name: t('about'), icon_class: "icon-pencil" }
|
||||
{ name: t('business_details'), icon_class: "icon-briefcase" }
|
||||
{ name: t('images'), icon_class: "icon-picture" }
|
||||
{ name: t('properties'), icon_class: "icon-tags", show: "showProperties()" }
|
||||
{ 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: "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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.enterprises", [
|
||||
angular.module("admin.enterprises", [
|
||||
7
app/assets/javascripts/admin/filters/translate.js.coffee
Normal file
7
app/assets/javascripts/admin/filters/translate.js.coffee
Normal file
@@ -0,0 +1,7 @@
|
||||
angular.module('ofn.admin').filter "translate", ->
|
||||
(key, options) ->
|
||||
t(key, options)
|
||||
|
||||
angular.module('ofn.admin').filter "t", ->
|
||||
(key, options) ->
|
||||
t(key, options)
|
||||
@@ -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) ->
|
||||
|
||||
@@ -5,27 +5,27 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.confirmDelete = true
|
||||
$scope.startDate = formatDate daysFromToday -7
|
||||
$scope.endDate = formatDate daysFromToday 1
|
||||
$scope.bulkActions = [ { name: "Delete Selected", callback: 'deleteLineItems' } ]
|
||||
$scope.selectedUnitsProduct = {};
|
||||
$scope.selectedUnitsVariant = {};
|
||||
$scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ]
|
||||
$scope.selectedUnitsProduct = {}
|
||||
$scope.selectedUnitsVariant = {}
|
||||
$scope.sharedResource = false
|
||||
$scope.columns = Columns.setColumns
|
||||
order_no: { name: "Order No.", visible: false }
|
||||
full_name: { name: "Name", visible: true }
|
||||
email: { name: "Email", visible: false }
|
||||
phone: { name: "Phone", visible: false }
|
||||
order_date: { name: "Order Date", visible: true }
|
||||
producer: { name: "Producer", visible: true }
|
||||
order_cycle: { name: "Order Cycle", visible: false }
|
||||
hub: { name: "Hub", visible: false }
|
||||
variant: { name: "Variant", visible: true }
|
||||
quantity: { name: "Quantity", visible: true }
|
||||
max: { name: "Max", visible: true }
|
||||
final_weight_volume: { name: "Weight/Volume", visible: false }
|
||||
price: { name: "Price", visible: false }
|
||||
order_no: { name: t("bom_no"), visible: false }
|
||||
full_name: { name: t("name"), visible: true }
|
||||
email: { name: t("email"), visible: false }
|
||||
phone: { name: t("phone"), visible: false }
|
||||
order_date: { name: t("bom_date"), visible: true }
|
||||
producer: { name: t("producer"), visible: true }
|
||||
order_cycle: { name: t("bom_cycle"), visible: false }
|
||||
hub: { name: t("bom_hub"), visible: false }
|
||||
variant: { name: t("bom_variant"), visible: true }
|
||||
quantity: { name: t("bom_quantity"), visible: true }
|
||||
max: { name: t("bom_max"), visible: true }
|
||||
final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
|
||||
price: { name: t("price"), visible: false }
|
||||
|
||||
$scope.confirmRefresh = ->
|
||||
LineItems.allSaved() || confirm("Unsaved changes exist and will be lost if you continue.")
|
||||
LineItems.allSaved() || confirm(t "unsaved_changes_warning")
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.distributorFilter = blankOption().id
|
||||
@@ -73,12 +73,12 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
StatusMessage.display 'success', "All changes saved"
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
).catch ->
|
||||
StatusMessage.display 'failure', "Fields with red borders contain errors."
|
||||
StatusMessage.display 'failure', t "unsaved_changes_error"
|
||||
else
|
||||
StatusMessage.display 'failure', "Fields with red borders contain errors."
|
||||
StatusMessage.display 'failure', t "unsaved_changes_error"
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete
|
||||
if ($scope.confirmDelete && confirm(t "are_you_sure")) || !$scope.confirmDelete
|
||||
LineItems.delete lineItem, =>
|
||||
$scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.payment_methods", [])
|
||||
angular.module("admin.paymentMethods", [])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("admin.payment_methods")
|
||||
angular.module("admin.paymentMethods")
|
||||
.factory "PaymentMethods", (paymentMethods) ->
|
||||
new class PaymentMethods
|
||||
paymentMethods: paymentMethods
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("admin.shippingMethods")
|
||||
.controller "shippingMethodsCtrl", ($scope, ShippingMethods) ->
|
||||
$scope.findShippingMethodByID = (id) ->
|
||||
$scope.ShippingMethod = ShippingMethods.findByID(id)
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("admin.shipping_methods")
|
||||
angular.module("admin.shippingMethods")
|
||||
.factory "ShippingMethods", (shippingMethods) ->
|
||||
new class ShippingMethods
|
||||
shippingMethods: shippingMethods
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.shipping_methods", [])
|
||||
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils'])
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("admin.tagRules").directive "discountOrder", ->
|
||||
restrict: "E"
|
||||
replace: true
|
||||
templateUrl: "admin/tag_rules/discount_order.html"
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("admin.tagRules").directive "filterShippingMethods", ->
|
||||
restrict: "E"
|
||||
replace: true
|
||||
templateUrl: "admin/tag_rules/filter_shipping_methods.html"
|
||||
@@ -0,0 +1 @@
|
||||
angular.module("admin.tagRules", ['ngTagsInput'])
|
||||
@@ -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(",")
|
||||
@@ -0,0 +1,9 @@
|
||||
Darkswarm.controller "DistributorNodeCtrl", ($scope, HashNavigation, $anchorScroll) ->
|
||||
$scope.toggle = ->
|
||||
HashNavigation.toggle $scope.distributor.hash
|
||||
|
||||
$scope.open = ->
|
||||
HashNavigation.active($scope.distributor.hash)
|
||||
|
||||
if $scope.open()
|
||||
$anchorScroll()
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Darkswarm.controller "OrdersCtrl", ($scope, $rootScope, $timeout, Orders, Search, $document, HashNavigation, FilterSelectorsService, EnterpriseModal, enterpriseMatchesNameQueryFilter, distanceWithinKmFilter) ->
|
||||
$scope.Orders = Orders
|
||||
@@ -0,0 +1,5 @@
|
||||
Darkswarm.directive 'auth', (AuthenticationService) ->
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attrs) ->
|
||||
elem.bind "click", ->
|
||||
AuthenticationService.open '/' + attrs.auth
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.directive "logoFallback", () ->
|
||||
restrict: "A"
|
||||
link: (scope, elm, attr)->
|
||||
elm.bind('error', ->
|
||||
elm.replaceWith("<i class='ofn-i_059-producer'></i>")
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
Darkswarm.filter "formatBalance", (localizeCurrencyFilter, tFilter)->
|
||||
# Convert number to string currency using injected currency configuration.
|
||||
(balance) ->
|
||||
if balance < 0
|
||||
tFilter('credit') + ": " + localizeCurrencyFilter(Math.abs(balance))
|
||||
else
|
||||
tFilter('balance_due') + ": " + localizeCurrencyFilter(Math.abs(balance))
|
||||
@@ -4,8 +4,13 @@ window.translate = (key, options = {}) ->
|
||||
unless 'I18n' of window
|
||||
console.log 'The I18n object is undefined. Cannot translate text.'
|
||||
return key
|
||||
return key unless key of I18n
|
||||
text = I18n[key]
|
||||
dict = I18n
|
||||
parts = key.split '.'
|
||||
while (parts.length)
|
||||
part = parts.shift()
|
||||
return key unless part of dict
|
||||
dict = dict[part]
|
||||
text = dict
|
||||
for name, value of options
|
||||
text = text.split("%{#{name}}").join(value)
|
||||
text
|
||||
|
||||
@@ -2,6 +2,8 @@ Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter) ->
|
||||
new class OfnMap
|
||||
constructor: ->
|
||||
@enterprises = @enterprise_markers(Enterprises.enterprises)
|
||||
@enterprises = @enterprises.filter (enterprise) ->
|
||||
enterprise.latitude != null || enterprise.longitude != null # Remove enterprises w/o lat or long
|
||||
|
||||
enterprise_markers: (enterprises) ->
|
||||
@extend(enterprise) for enterprise in visibleFilter(enterprises)
|
||||
|
||||
16
app/assets/javascripts/darkswarm/services/orders.js.coffee
Normal file
16
app/assets/javascripts/darkswarm/services/orders.js.coffee
Normal file
@@ -0,0 +1,16 @@
|
||||
Darkswarm.factory 'Orders', (orders_by_distributor, currencyConfig, CurrentHub, Taxons, Dereferencer, visibleFilter, Matcher, Geo, $rootScope)->
|
||||
new class Orders
|
||||
constructor: ->
|
||||
# Populate Orders.orders from json in page.
|
||||
@orders_by_distributor = orders_by_distributor
|
||||
@currency_symbol = currencyConfig.symbol
|
||||
|
||||
for distributor in @orders_by_distributor
|
||||
@updateRunningBalance(distributor.distributed_orders)
|
||||
|
||||
|
||||
updateRunningBalance: (orders) ->
|
||||
for order, i in orders
|
||||
balances = orders.slice(i,orders.length).map (o) -> parseFloat(o.outstanding_balance)
|
||||
running_balance = balances.reduce (a,b) -> a+b
|
||||
order.running_balance = running_balance.toFixed(2)
|
||||
@@ -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)' } }
|
||||
@@ -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 %
|
||||
@@ -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" }
|
||||
88
app/assets/stylesheets/admin/components/jquery_dialog.scss
Normal file
88
app/assets/stylesheets/admin/components/jquery_dialog.scss
Normal 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;
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-bottom-30 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.margin-bottom-50 {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
@@ -69,4 +69,23 @@ 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 {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
68
app/assets/stylesheets/admin/tag_rules.css.scss
Normal file
68
app/assets/stylesheets/admin/tag_rules.css.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,12 @@
|
||||
font-size: 1.2rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
color: #DA5354;
|
||||
}
|
||||
|
||||
|
||||
input.text-big {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
63
app/assets/stylesheets/darkswarm/account.css.sass
Normal file
63
app/assets/stylesheets/darkswarm/account.css.sass
Normal file
@@ -0,0 +1,63 @@
|
||||
@import branding
|
||||
@import mixins
|
||||
|
||||
.orders
|
||||
@include sidepaddingSm
|
||||
@include panepadding
|
||||
padding-top: 10px
|
||||
|
||||
h3
|
||||
padding-top: 2em
|
||||
|
||||
a
|
||||
color: $clr-brick
|
||||
&:hover, &:active, &:focus
|
||||
color: $clr-brick-med-bright
|
||||
|
||||
img
|
||||
display: block
|
||||
width: 80px
|
||||
height: auto
|
||||
|
||||
i.ofn-i_059-producer, i.ofn-i_060-producer-reversed
|
||||
font-size: 3rem
|
||||
display: inline-block
|
||||
margin-right: 0.25rem
|
||||
float: left
|
||||
color: $clr-turquoise
|
||||
|
||||
.credit
|
||||
color: green
|
||||
|
||||
.debit
|
||||
color: $clr-brick
|
||||
|
||||
.distributor-balance.paid
|
||||
visibility: hidden
|
||||
|
||||
.transaction-group
|
||||
|
||||
|
||||
table
|
||||
border-radius: 0.5em 0.5em 0 0
|
||||
tr:nth-of-type(even)
|
||||
background: transparent // clear previous
|
||||
tbody.odd
|
||||
tr
|
||||
background-color: #f9f9f9
|
||||
border: none
|
||||
// Column widths for order table
|
||||
.order1
|
||||
width: 20%
|
||||
.order2
|
||||
width: 20%
|
||||
.order3
|
||||
width: 20%
|
||||
.order4
|
||||
width: 10%
|
||||
.order5
|
||||
width: 10%
|
||||
.order6
|
||||
width: 10%
|
||||
.order7
|
||||
width: 10%
|
||||
@@ -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)
|
||||
|
||||
@@ -7,4 +7,7 @@
|
||||
@include sidepaddingSm
|
||||
|
||||
.name-matches, .distance-matches
|
||||
margin-top: 4em
|
||||
margin-top: 4em
|
||||
|
||||
.more-controls
|
||||
text-align: center
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
color: $clr-turquoise-bright
|
||||
a.button.primary
|
||||
&:hover, &:active, &:focus
|
||||
color: white
|
||||
color: white
|
||||
|
||||
@@ -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
|
||||
|
||||
13
app/controllers/admin/cache_settings_controller.rb
Normal file
13
app/controllers/admin/cache_settings_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
require 'open_food_network/products_cache_integrity_checker'
|
||||
|
||||
class Admin::CacheSettingsController < Spree::Admin::BaseController
|
||||
|
||||
def show
|
||||
@results = Exchange.cachable.map do |exchange|
|
||||
checker = OpenFoodNetwork::ProductsCacheIntegrityChecker.new(exchange.receiver, exchange.order_cycle)
|
||||
|
||||
{distributor: exchange.receiver, order_cycle: exchange.order_cycle, status: checker.ok?, diff: checker.diff}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
10
app/controllers/admin/tag_rules_controller.rb
Normal file
10
app/controllers/admin/tag_rules_controller.rb
Normal 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
|
||||
17
app/controllers/api/statuses_controller.rb
Normal file
17
app/controllers/api/statuses_controller.rb
Normal 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
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'open_food_network/products_renderer'
|
||||
require 'open_food_network/cached_products_renderer'
|
||||
|
||||
class ShopController < BaseController
|
||||
layout "darkswarm"
|
||||
@@ -11,11 +11,11 @@ class ShopController < BaseController
|
||||
|
||||
def products
|
||||
begin
|
||||
products_json = OpenFoodNetwork::ProductsRenderer.new(current_distributor, current_order_cycle).products
|
||||
products_json = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor, current_order_cycle).products_json
|
||||
|
||||
render json: products_json
|
||||
|
||||
rescue OpenFoodNetwork::ProductsRenderer::NoProducts
|
||||
rescue OpenFoodNetwork::CachedProductsRenderer::NoProducts
|
||||
render status: 404, json: ''
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'open_food_network/enterprise_injection_data'
|
||||
|
||||
module InjectionHelper
|
||||
def inject_enterprises
|
||||
inject_json_ams "enterprises", Enterprise.activated.includes(:address).all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
inject_json_ams "enterprises", Enterprise.activated.includes(address: :state).all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_group_enterprises
|
||||
@@ -51,6 +51,11 @@ module InjectionHelper
|
||||
render partial: "json/injection_ams", locals: {name: 'enterpriseAttributes', json: "#{@enterprise_attributes.to_json}"}
|
||||
end
|
||||
|
||||
def inject_orders_by_distributor
|
||||
data_array = spree_current_user.orders_by_distributor
|
||||
inject_json_ams "orders_by_distributor", data_array, Api::OrdersByDistributorSerializer
|
||||
end
|
||||
|
||||
def inject_json(name, partial, opts = {})
|
||||
render partial: "json/injection", locals: {name: name, partial: partial}.merge(opts)
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
5
app/jobs/heartbeat_job.rb
Normal file
5
app/jobs/heartbeat_job.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class HeartbeatJob
|
||||
def perform
|
||||
Spree::Config.last_job_queue_heartbeat_at = Time.now
|
||||
end
|
||||
end
|
||||
24
app/jobs/products_cache_integrity_checker_job.rb
Normal file
24
app/jobs/products_cache_integrity_checker_job.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
require 'open_food_network/products_cache_integrity_checker'
|
||||
|
||||
ProductsCacheIntegrityCheckerJob = Struct.new(:distributor_id, :order_cycle_id) do
|
||||
def perform
|
||||
unless checker.ok?
|
||||
Bugsnag.notify RuntimeError.new("Products JSON differs from cached version for distributor: #{distributor_id}, order cycle: #{order_cycle_id}"), diff: checker.diff.to_s(:text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def checker
|
||||
OpenFoodNetwork::ProductsCacheIntegrityChecker.new(distributor, order_cycle)
|
||||
end
|
||||
|
||||
def distributor
|
||||
Enterprise.find distributor_id
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
OrderCycle.find order_cycle_id
|
||||
end
|
||||
end
|
||||
16
app/jobs/refresh_products_cache_job.rb
Normal file
16
app/jobs/refresh_products_cache_job.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require 'open_food_network/products_renderer'
|
||||
|
||||
RefreshProductsCacheJob = Struct.new(:distributor_id, :order_cycle_id) do
|
||||
def perform
|
||||
Rails.cache.write "products-json-#{distributor_id}-#{order_cycle_id}", products_json
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def products_json
|
||||
distributor = Enterprise.find distributor_id
|
||||
order_cycle = OrderCycle.find order_cycle_id
|
||||
OpenFoodNetwork::ProductsRenderer.new(distributor, order_cycle).products_json
|
||||
end
|
||||
end
|
||||
@@ -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: ""
|
||||
|
||||
15
app/models/coordinator_fee.rb
Normal file
15
app/models/coordinator_fee.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
class CoordinatorFee < ActiveRecord::Base
|
||||
belongs_to :order_cycle
|
||||
belongs_to :enterprise_fee
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
order_cycle.refresh_products_cache
|
||||
end
|
||||
|
||||
end
|
||||
@@ -2,10 +2,12 @@ class Customer < ActiveRecord::Base
|
||||
acts_as_taggable
|
||||
|
||||
belongs_to :enterprise
|
||||
belongs_to :user, :class_name => Spree.user_class
|
||||
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: "is associated with an existing customer" }
|
||||
validates :email, presence: true, uniqueness: { scope: :enterprise_id, message: I18n.t('validation_msg_is_associated_with_an_exising_customer') }
|
||||
validates :enterprise_id, presence: true
|
||||
|
||||
scope :of, ->(enterprise) { where(enterprise_id: enterprise) }
|
||||
@@ -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
|
||||
|
||||
@@ -41,11 +41,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>" },
|
||||
@@ -175,17 +177,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)
|
||||
@@ -353,6 +344,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
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
class EnterpriseFee < ActiveRecord::Base
|
||||
belongs_to :enterprise
|
||||
belongs_to :tax_category, class_name: 'Spree::TaxCategory', foreign_key: 'tax_category_id'
|
||||
has_and_belongs_to_many :order_cycles, join_table: 'coordinator_fees'
|
||||
|
||||
has_many :coordinator_fees, dependent: :destroy
|
||||
has_many :order_cycles, through: :coordinator_fees
|
||||
|
||||
has_many :exchange_fees, dependent: :destroy
|
||||
has_many :exchanges, through: :exchange_fees
|
||||
|
||||
before_destroy { order_cycles.clear }
|
||||
|
||||
after_save :refresh_products_cache
|
||||
# After destroy, the products cache is refreshed via the after_destroy hook for
|
||||
# coordinator_fees and exchange_fees
|
||||
|
||||
|
||||
calculated_adjustments
|
||||
|
||||
@@ -59,6 +66,7 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
:locked => true}, :without_protection => true)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def ensure_valid_tax_category_settings
|
||||
@@ -72,4 +80,8 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.enterprise_fee_changed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
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."
|
||||
validates_uniqueness_of :child_id, scope: :parent_id, message: I18n.t('validation_msg_relationship_already_established')
|
||||
|
||||
after_save :apply_variant_override_permissions
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ class Exchange < ActiveRecord::Base
|
||||
validates_presence_of :order_cycle, :sender, :receiver
|
||||
validates_uniqueness_of :sender_id, :scope => [:order_cycle_id, :receiver_id, :incoming]
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
accepts_nested_attributes_for :variants
|
||||
|
||||
scope :in_order_cycle, lambda { |order_cycle| where(order_cycle_id: order_cycle) }
|
||||
@@ -31,6 +34,12 @@ class Exchange < ActiveRecord::Base
|
||||
joins('INNER JOIN enterprises AS receiver ON (receiver.id = exchanges.receiver_id)').
|
||||
order("CASE WHEN exchanges.incoming='t' THEN sender.name ELSE receiver.name END")
|
||||
|
||||
# Exchanges on order cycles that are dated and are upcoming or open are cached
|
||||
scope :cachable, outgoing.
|
||||
joins(:order_cycle).
|
||||
merge(OrderCycle.dated).
|
||||
merge(OrderCycle.not_closed)
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
scoped
|
||||
@@ -75,4 +84,11 @@ class Exchange < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.exchange_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.exchange_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
class ExchangeFee < ActiveRecord::Base
|
||||
belongs_to :exchange
|
||||
belongs_to :enterprise_fee
|
||||
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
exchange.refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
class InventoryItem < ActiveRecord::Base
|
||||
attr_accessible :enterprise, :enterprise_id, :variant, :variant_id, :visible
|
||||
|
||||
@@ -11,4 +13,13 @@ class InventoryItem < ActiveRecord::Base
|
||||
|
||||
scope :visible, where(visible: true)
|
||||
scope :hidden, where(visible: false)
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.inventory_item_changed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class OrderCycle < ActiveRecord::Base
|
||||
belongs_to :coordinator, :class_name => 'Enterprise'
|
||||
has_and_belongs_to_many :coordinator_fees, :class_name => 'EnterpriseFee', :join_table => 'coordinator_fees'
|
||||
|
||||
has_many :coordinator_fee_refs, class_name: 'CoordinatorFee'
|
||||
has_many :coordinator_fees, through: :coordinator_fee_refs, source: :enterprise_fee
|
||||
|
||||
has_many :exchanges, :dependent => :destroy
|
||||
|
||||
@@ -11,14 +13,18 @@ class OrderCycle < ActiveRecord::Base
|
||||
|
||||
validates_presence_of :name, :coordinator_id
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
|
||||
|
||||
scope :active, lambda { where('order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?', Time.zone.now, Time.zone.now) }
|
||||
scope :active_or_complete, lambda { where('order_cycles.orders_open_at <= ?', Time.zone.now) }
|
||||
scope :inactive, lambda { where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?', Time.zone.now, Time.zone.now) }
|
||||
scope :upcoming, lambda { where('order_cycles.orders_open_at > ?', Time.zone.now) }
|
||||
scope :not_closed, lambda { where('order_cycles.orders_close_at > ? OR order_cycles.orders_close_at IS NULL', Time.zone.now) }
|
||||
scope :closed, lambda { where('order_cycles.orders_close_at < ?', Time.zone.now).order("order_cycles.orders_close_at DESC") }
|
||||
scope :undated, where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL')
|
||||
scope :dated, where('orders_open_at IS NOT NULL AND orders_close_at IS NOT NULL')
|
||||
|
||||
scope :soonest_closing, lambda { active.order('order_cycles.orders_close_at ASC') }
|
||||
# TODO This method returns all the closed orders. So maybe we can replace it with :recently_closed.
|
||||
@@ -187,6 +193,10 @@ class OrderCycle < ActiveRecord::Base
|
||||
self.variants.include? variant
|
||||
end
|
||||
|
||||
def dated?
|
||||
!undated?
|
||||
end
|
||||
|
||||
def undated?
|
||||
self.orders_open_at.nil? || self.orders_close_at.nil?
|
||||
end
|
||||
@@ -236,6 +246,10 @@ class OrderCycle < ActiveRecord::Base
|
||||
coordinator.users.include? user
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.order_cycle_changed self
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
class ProducerProperty < ActiveRecord::Base
|
||||
belongs_to :producer, class_name: 'Enterprise'
|
||||
belongs_to :property, class_name: 'Spree::Property'
|
||||
|
||||
default_scope order("#{self.table_name}.position")
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
|
||||
def property_name
|
||||
property.name if property
|
||||
@@ -14,4 +18,16 @@ class ProducerProperty < ActiveRecord::Base
|
||||
Spree::Property.create(name: name, presentation: name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.producer_property_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.producer_property_destroyed self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -22,4 +22,8 @@ Spree::AppConfiguration.class_eval do
|
||||
preference :account_invoices_tax_rate, :decimal, default: 0
|
||||
preference :shop_trial_length_days, :integer, default: 30
|
||||
preference :minimum_billable_turnover, :integer, default: -1
|
||||
|
||||
# Monitoring
|
||||
preference :last_job_queue_heartbeat_at, :string, default: nil
|
||||
|
||||
end
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
Spree::Classification.class_eval do
|
||||
belongs_to :product, :class_name => "Spree::Product", touch: true
|
||||
after_save :refresh_products_cache
|
||||
before_destroy :dont_destroy_if_primary_taxon
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product.refresh_products_cache
|
||||
end
|
||||
|
||||
def dont_destroy_if_primary_taxon
|
||||
if product.primary_taxon == taxon
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
Spree::Image.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
# Spree stores attachent definitions in JSON. This converts the style name and format to
|
||||
# strings. However, when paperclip encounters these, it doesn't recognise the format.
|
||||
# Here we solve that problem by converting format and style name to symbols.
|
||||
@@ -20,4 +23,11 @@ Spree::Image.class_eval do
|
||||
end
|
||||
|
||||
reformat_styles
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
viewable.try :refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
13
app/models/spree/option_type_decorator.rb
Normal file
13
app/models/spree/option_type_decorator.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Spree
|
||||
OptionType.class_eval do
|
||||
has_many :products, through: :product_option_types
|
||||
after_save :refresh_products_cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each &:refresh_products_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/models/spree/option_value_decorator.rb
Normal file
20
app/models/spree/option_value_decorator.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module Spree
|
||||
OptionValue.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
around_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
variants(:reload).each &:refresh_products_cache
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
vs = variants(:reload).to_a
|
||||
yield
|
||||
vs.each &:refresh_products_cache
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
31
app/models/spree/preference_decorator.rb
Normal file
31
app/models/spree/preference_decorator.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
module Spree
|
||||
Preference.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# When the setting preferred_product_selection_from_inventory_only has changed, we want to
|
||||
# refresh all active exchanges for this enterprise.
|
||||
def refresh_products_cache
|
||||
if product_selection_from_inventory_only_changed?
|
||||
OpenFoodNetwork::ProductsCache.distributor_changed(enterprise)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def product_selection_from_inventory_only_changed?
|
||||
key =~ product_selection_from_inventory_only_regex
|
||||
end
|
||||
|
||||
def enterprise
|
||||
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
|
||||
enterprise = Enterprise.find enterprise_id
|
||||
end
|
||||
|
||||
def product_selection_from_inventory_only_regex
|
||||
/^enterprise\/product_selection_from_inventory_only\/(\d+)$/
|
||||
end
|
||||
end
|
||||
end
|
||||
12
app/models/spree/price_decorator.rb
Normal file
12
app/models/spree/price_decorator.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Spree
|
||||
Price.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
variant.andand.refresh_products_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,12 +22,10 @@ Spree::Product.class_eval do
|
||||
attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value
|
||||
attr_accessible :inherits_properties, :sku
|
||||
|
||||
before_validation :sanitize_permalink
|
||||
|
||||
# validates_presence_of :variants, unless: :new_record?, message: "Product must have at least one variant"
|
||||
validates_presence_of :supplier
|
||||
validates :primary_taxon, presence: { message: "^Product Category can't be blank" }
|
||||
validates :tax_category_id, presence: { message: "^Tax Category can't be blank" }, if: "Spree::Config.products_require_tax_category"
|
||||
validates :primary_taxon, presence: { message: I18n.t("validation_msg_product_category_cant_be_blank") }
|
||||
validates :tax_category_id, presence: { message: I18n.t("validation_msg_tax") }, if: "Spree::Config.products_require_tax_category"
|
||||
|
||||
validates_presence_of :variant_unit
|
||||
validates_presence_of :variant_unit_scale,
|
||||
@@ -35,11 +33,13 @@ Spree::Product.class_eval do
|
||||
validates_presence_of :variant_unit_name,
|
||||
if: -> p { p.variant_unit == 'items' }
|
||||
|
||||
after_save :ensure_standard_variant
|
||||
after_initialize :set_available_on_to_now, :if => :new_record?
|
||||
after_save :update_units
|
||||
after_touch :touch_distributors
|
||||
before_validation :sanitize_permalink
|
||||
before_save :add_primary_taxon_to_taxons
|
||||
after_touch :touch_distributors
|
||||
after_save :ensure_standard_variant
|
||||
after_save :update_units
|
||||
after_save :refresh_products_cache
|
||||
|
||||
|
||||
# -- Joins
|
||||
@@ -198,6 +198,11 @@ Spree::Product.class_eval do
|
||||
alias_method_chain :delete, :delete_from_order_cycles
|
||||
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.product_changed self
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def set_available_on_to_now
|
||||
|
||||
10
app/models/spree/product_property_decorator.rb
Normal file
10
app/models/spree/product_property_decorator.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module Spree
|
||||
ProductProperty.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
def refresh_products_cache
|
||||
product.refresh_products_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
15
app/models/spree/property_decorator.rb
Normal file
15
app/models/spree/property_decorator.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Spree
|
||||
Property.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# When a Property is destroyed, dependent-destroy will destroy all ProductProperties,
|
||||
# which will take care of refreshing the products cache
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product_properties(:reload).each &:refresh_products_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
Spree::Taxon.class_eval do
|
||||
has_many :classifications, :dependent => :destroy
|
||||
|
||||
|
||||
self.attachment_definitions[:icon][:path] = 'public/images/spree/taxons/:id/:style/:basename.:extension'
|
||||
self.attachment_definitions[:icon][:url] = '/images/spree/taxons/:id/:style/:basename.:extension'
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
|
||||
# Indicate which filters should be used for this taxon
|
||||
def applicable_filters
|
||||
@@ -45,4 +50,11 @@ Spree::Taxon.class_eval do
|
||||
|
||||
taxons
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each &:refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -49,6 +54,27 @@ Spree.user_class.class_eval do
|
||||
owned_enterprises(:reload).size < enterprise_limit
|
||||
end
|
||||
|
||||
# Returns Enterprise IDs for distributors that the user has shopped at
|
||||
def enterprises_ordered_from
|
||||
orders.where(state: :complete).map(&:distributor_id).uniq
|
||||
end
|
||||
|
||||
# Returns orders and their associated payments for all distributors that have been ordered from
|
||||
def compelete_orders_by_distributor
|
||||
Enterprise
|
||||
.includes(distributed_orders: { payments: :payment_method })
|
||||
.where(enterprises: { id: enterprises_ordered_from },
|
||||
spree_orders: { state: 'complete', user_id: id })
|
||||
.order('spree_orders.completed_at DESC')
|
||||
end
|
||||
|
||||
def orders_by_distributor
|
||||
# Remove uncompleted payments as these will not be reflected in order balance
|
||||
data_array = compelete_orders_by_distributor.to_a
|
||||
remove_uncompleted_payments(data_array)
|
||||
data_array.sort! { |a, b| b.distributed_orders.length <=> a.distributed_orders.length }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def limit_owned_enterprises
|
||||
@@ -56,4 +82,12 @@ Spree.user_class.class_eval do
|
||||
errors.add(:owned_enterprises, "^#{email} is not permitted to own any more enterprises (limit is #{enterprise_limit}).")
|
||||
end
|
||||
end
|
||||
|
||||
def remove_uncompleted_payments(enterprises)
|
||||
enterprises.each do |enterprise|
|
||||
enterprise.distributed_orders.each do |order|
|
||||
order.payments.keep_if { |payment| payment.state == "completed" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user