mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Compare commits
225 Commits
v1.8.8-rc1
...
v1.8.12-tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c3c3394a5 | ||
|
|
5b3816fa9e | ||
|
|
3507bae002 | ||
|
|
b7f99c3185 | ||
|
|
ce85027b79 | ||
|
|
94b6a61665 | ||
|
|
f84acf44f3 | ||
|
|
386d651678 | ||
|
|
23a6e2dd8f | ||
|
|
6c69eebcbf | ||
|
|
d8a158c56d | ||
|
|
dfda30f4cb | ||
|
|
d26970d04c | ||
|
|
63e453e2c7 | ||
|
|
c7ed27286a | ||
|
|
e5340cb53a | ||
|
|
7079952f6b | ||
|
|
f79dcaba15 | ||
|
|
33a1d97d5e | ||
|
|
28ea23965b | ||
|
|
fbdbbb980f | ||
|
|
fc5b339e2a | ||
|
|
4acb3f1962 | ||
|
|
7bb58342fc | ||
|
|
b277ff03ea | ||
|
|
5061d0c4cd | ||
|
|
931a5be162 | ||
|
|
e437cba155 | ||
|
|
ec53d9df7a | ||
|
|
3330e9b219 | ||
|
|
884743ce97 | ||
|
|
b482d1e57c | ||
|
|
78f5002be5 | ||
|
|
a42799dff4 | ||
|
|
1196cd7df4 | ||
|
|
3c1eae1f47 | ||
|
|
ba27d63f9c | ||
|
|
0083733c4c | ||
|
|
a1ffc869f3 | ||
|
|
7a07e8fa16 | ||
|
|
699da16049 | ||
|
|
18c7b03f3d | ||
|
|
8b560e6cee | ||
|
|
7974ac45f2 | ||
|
|
41b5cf10dd | ||
|
|
b6955cb98c | ||
|
|
6c36c269c8 | ||
|
|
b879ea5a96 | ||
|
|
a6ed4a2c6a | ||
|
|
6972822c49 | ||
|
|
db63f30a76 | ||
|
|
848193434b | ||
|
|
570f0344da | ||
|
|
38c25c412f | ||
|
|
3b4ffe1f70 | ||
|
|
4699c654fc | ||
|
|
c1c5d00f45 | ||
|
|
220693f4e3 | ||
|
|
fcce858ea4 | ||
|
|
1f9698f7a2 | ||
|
|
1766da9d60 | ||
|
|
15ee62aaa8 | ||
|
|
6fc4a297a0 | ||
|
|
74d8dc48b4 | ||
|
|
b6f4ce373e | ||
|
|
c9f186f48f | ||
|
|
daab0dfd7a | ||
|
|
22b5dafad2 | ||
|
|
110f74eee8 | ||
|
|
61cb78fc93 | ||
|
|
38d3b446cc | ||
|
|
e47e10d267 | ||
|
|
77c8c85775 | ||
|
|
fab6d70832 | ||
|
|
9586666248 | ||
|
|
f5ab9a3445 | ||
|
|
1ac8c85113 | ||
|
|
56c2350d36 | ||
|
|
6eafed00f5 | ||
|
|
74661c0b77 | ||
|
|
c4fbcb19d0 | ||
|
|
08e391856c | ||
|
|
8c8b40c5a8 | ||
|
|
c3eda435eb | ||
|
|
5eadb33db9 | ||
|
|
f977a05b08 | ||
|
|
217eda8362 | ||
|
|
579f3bf90a | ||
|
|
348ab81c42 | ||
|
|
3df629bc6e | ||
|
|
07b2f0a7c2 | ||
|
|
c0445d46f3 | ||
|
|
6c90b4e6d0 | ||
|
|
e79a23a554 | ||
|
|
314ccc2f27 | ||
|
|
68c8759af1 | ||
|
|
314e9a4f15 | ||
|
|
0029a1b6cf | ||
|
|
bfcde72855 | ||
|
|
3d0ada803f | ||
|
|
840c936a6f | ||
|
|
7ea74ccf4a | ||
|
|
b55036e165 | ||
|
|
47011e11ff | ||
|
|
ae28d7a96b | ||
|
|
0dd8959bf7 | ||
|
|
316b0915e4 | ||
|
|
e21bfd95f4 | ||
|
|
893331c7bb | ||
|
|
b94bcd697f | ||
|
|
b0ff7ca767 | ||
|
|
493a537f2c | ||
|
|
cda43f075b | ||
|
|
5d9f92eaa7 | ||
|
|
c6afa1849c | ||
|
|
eec3a21c89 | ||
|
|
768240a5ba | ||
|
|
5af8668560 | ||
|
|
630b8a2577 | ||
|
|
12a6f652ad | ||
|
|
db4a528ba3 | ||
|
|
479c7ba24b | ||
|
|
1f08729df3 | ||
|
|
811671661e | ||
|
|
936df71d0d | ||
|
|
f3f6714472 | ||
|
|
3bce2eb7b5 | ||
|
|
bf05866f92 | ||
|
|
219ad4a3a7 | ||
|
|
a7d8028d5a | ||
|
|
d49469a3e6 | ||
|
|
4835ef067f | ||
|
|
4112c3cc75 | ||
|
|
0fe4030d71 | ||
|
|
e8d2d4d96f | ||
|
|
88c3f414fb | ||
|
|
c0d6b68233 | ||
|
|
65f62c42b9 | ||
|
|
fe7bd5e2cd | ||
|
|
e1b40142b8 | ||
|
|
963b4617a9 | ||
|
|
95ddfc4e40 | ||
|
|
01746ed470 | ||
|
|
f25e3bc6f7 | ||
|
|
e63f1c2991 | ||
|
|
29e2886b05 | ||
|
|
a97bcf74de | ||
|
|
1e6f4aa73d | ||
|
|
7c7933f8bb | ||
|
|
dc69c6e825 | ||
|
|
41e91765ca | ||
|
|
19569f9316 | ||
|
|
d91c3d1241 | ||
|
|
b302deb7a3 | ||
|
|
2fae467e9a | ||
|
|
6a5e4bb592 | ||
|
|
c72d17dc83 | ||
|
|
78ffdec693 | ||
|
|
49c19a1d6a | ||
|
|
e854eb0426 | ||
|
|
4a9c17cb28 | ||
|
|
0d1547f439 | ||
|
|
fa5ed529cb | ||
|
|
accb3076e9 | ||
|
|
a4e4e1ec68 | ||
|
|
507e12adba | ||
|
|
4809237ecc | ||
|
|
81877fedb6 | ||
|
|
1f2c6f2a85 | ||
|
|
4fe5e60967 | ||
|
|
f4eb9cb790 | ||
|
|
775f9b3ada | ||
|
|
188b33921c | ||
|
|
20c033317f | ||
|
|
e7a5d063ac | ||
|
|
1fda781d7e | ||
|
|
45fc801a08 | ||
|
|
5fa45c0716 | ||
|
|
21337a5b50 | ||
|
|
ec36a843cf | ||
|
|
e99dbaf4d8 | ||
|
|
c83ad2ecc4 | ||
|
|
80d8d18eb2 | ||
|
|
903d1afb53 | ||
|
|
cd55d2e2ff | ||
|
|
05cf8c4351 | ||
|
|
b04d815408 | ||
|
|
7b370a2eb6 | ||
|
|
5808b601b8 | ||
|
|
fdcd3dc3e3 | ||
|
|
c4bd085393 | ||
|
|
0e91d01412 | ||
|
|
fcb9e9fa56 | ||
|
|
3591354cb1 | ||
|
|
b38eab11eb | ||
|
|
c43dea60b7 | ||
|
|
268bea25d0 | ||
|
|
e94ae20b31 | ||
|
|
a94961c0a7 | ||
|
|
0d5fde919b | ||
|
|
429ef4e2ba | ||
|
|
e8999d23e1 | ||
|
|
209c9242d9 | ||
|
|
6defb09d2e | ||
|
|
2774c09d7a | ||
|
|
c62a044598 | ||
|
|
f73fbe7f23 | ||
|
|
5e1e4c1d19 | ||
|
|
cc5a335fb7 | ||
|
|
91fc3f33a0 | ||
|
|
648753b412 | ||
|
|
24fcc3dd34 | ||
|
|
14fb40a996 | ||
|
|
f4511fc74d | ||
|
|
3d0f192490 | ||
|
|
6b7cdf3a37 | ||
|
|
052d6c6b02 | ||
|
|
6e5c878491 | ||
|
|
c0c6cd1a60 | ||
|
|
2ad433590d | ||
|
|
2cb3da56ab | ||
|
|
8582e6d6b4 | ||
|
|
170101cbfe | ||
|
|
8107f49373 | ||
|
|
f235099859 |
@@ -1,11 +1,8 @@
|
||||
engines:
|
||||
rubocop:
|
||||
enabled: true
|
||||
exclude_fingerprints:
|
||||
- ac41db8d4ec4cbf508c353d9b65a024f
|
||||
- 8e3b6322aef5be9f38700b3fd0cd347e
|
||||
scss-lint:
|
||||
enabled: true
|
||||
enabled: false
|
||||
ratings:
|
||||
paths:
|
||||
- app/**
|
||||
|
||||
160
.rubocop.yml
160
.rubocop.yml
@@ -1,4 +1,9 @@
|
||||
inherit_from:
|
||||
- .rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.1
|
||||
TargetRailsVersion: 3.2
|
||||
Include:
|
||||
- '**/Rakefile'
|
||||
- '**/config.ru'
|
||||
@@ -6,24 +11,163 @@ AllCops:
|
||||
- 'db/**/*'
|
||||
- 'config/**/*'
|
||||
- 'script/**/*'
|
||||
- 'spec/**/*'
|
||||
- !ruby/regexp /old_and_unused\.rb$/
|
||||
|
||||
Documentation:
|
||||
# OFN SETTINGS
|
||||
# Cop settings that have been agreed upon by the OFN community
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/EmptyLinesAroundClassBody:
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
|
||||
Style/BracesAroundHashParameters:
|
||||
# TEMPORARY/CONTESTED SETTINGS
|
||||
# These are still to be decided upon, but recommended for inclusion by
|
||||
# oeoeaio after scrutinising offenses the codebase
|
||||
|
||||
# Don't think this is a big issue, mostly picking up RPSEC scope definitions
|
||||
# with lamdas and RSpec '.to change{}' blocks
|
||||
Lint/AmbiguousBlockAssociation:
|
||||
Enabled: false
|
||||
|
||||
|
||||
# Heaps of offences (> 100) in specs, mostly in situations where two or more
|
||||
# instances of a model are required, but only one is referenced. Difficult to
|
||||
# fix without making the spec look messy or rewriting it.
|
||||
# Should definitely fix at some point.
|
||||
Lint/UselessAssignment:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
|
||||
# AFAIK, there is no good alternative to dynamic matchers until we upgrade
|
||||
# to Rails 4 and can use #find_by. If there is a better approach, let's do it.
|
||||
Rails/DynamicFindBy:
|
||||
Enabled: false
|
||||
|
||||
# This should be the programmer's discretion, perhaps we should review all of
|
||||
# the uses of it an make specific exceptions though.
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
# Relaxed.Ruby.Style SETTINGS
|
||||
# These styles are a starting point for the conversation around conventions
|
||||
# They should be removed or tweaked and moved above as decisions are made
|
||||
# NOTE: Cops which did not fail at the time of writing were removed
|
||||
|
||||
Layout/DotPosition:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styledotposition
|
||||
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylespacebeforeblockbraces
|
||||
|
||||
Layout/SpaceInsideParens:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylespaceinsideparens
|
||||
|
||||
Style/Alias:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylealias
|
||||
|
||||
Style/BlockDelimiters:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styleblockdelimiters
|
||||
|
||||
Style/CommentAnnotation:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylecommentannotation
|
||||
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styledoublenegation
|
||||
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styleformatstring
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styleifunlessmodifier
|
||||
|
||||
Style/Lambda:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylelambda
|
||||
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylemultilineblockchain
|
||||
|
||||
Style/NegatedIf:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylenegatedif
|
||||
|
||||
Style/NegatedWhile:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylenegatedwhile
|
||||
|
||||
Style/ParallelAssignment:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styleparallelassignment
|
||||
|
||||
Style/PercentLiteralDelimiters:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylepercentliteraldelimiters
|
||||
|
||||
Style/Semicolon:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylesemicolon
|
||||
|
||||
Style/SingleLineMethods:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylesinglelinemethods
|
||||
|
||||
Style/TrailingCommaInArguments:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainarguments
|
||||
|
||||
Style/TrailingCommaInLiteral:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainliteral
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#stylewordarray
|
||||
|
||||
Lint/AmbiguousRegexpLiteral:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#lintambiguousregexpliteral
|
||||
|
||||
Lint/AssignmentInCondition:
|
||||
Enabled: false
|
||||
StyleGuide: http://relaxed.ruby.style/#lintassignmentincondition
|
||||
|
||||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
|
||||
Metrics/LineLength:
|
||||
Enabled: false
|
||||
Max: 120
|
||||
|
||||
MethodLength:
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
StringLiterals:
|
||||
Metrics/ParameterLists:
|
||||
Enabled: false
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Enabled: false
|
||||
|
||||
2187
.rubocop_todo.yml
Normal file
2187
.rubocop_todo.yml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,6 @@
|
||||
See this here post on raising a github issue:
|
||||
https://community.openfoodnetwork.org/t/how-to-raise-a-github-issue/912
|
||||
|
||||
# Contributing
|
||||
|
||||
We love pull requests from everyone. Here are some instructions for
|
||||
|
||||
3
Gemfile
3
Gemfile
@@ -63,6 +63,7 @@ gem 'wkhtmltopdf-binary'
|
||||
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
gem 'roo', '~> 2.7.0'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
@@ -114,7 +115,6 @@ end
|
||||
|
||||
group :test do
|
||||
gem 'webmock'
|
||||
|
||||
# See spec/spec_helper.rb for instructions
|
||||
#gem 'perftools.rb'
|
||||
end
|
||||
@@ -129,4 +129,5 @@ group :development do
|
||||
gem 'guard-zeus'
|
||||
gem 'guard-rspec'
|
||||
gem 'parallel_tests'
|
||||
gem 'rubocop', '>= 0.49.1'
|
||||
end
|
||||
|
||||
35
Gemfile.lock
35
Gemfile.lock
@@ -165,6 +165,7 @@ GEM
|
||||
angularjs-rails (1.5.5)
|
||||
ansi (1.4.2)
|
||||
arel (3.0.3)
|
||||
ast (2.3.0)
|
||||
atomic (1.1.99)
|
||||
awesome_nested_set (2.1.5)
|
||||
activerecord (>= 3.0.0)
|
||||
@@ -477,9 +478,11 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
cocaine (~> 0.5.3)
|
||||
mime-types
|
||||
parallel (1.4.1)
|
||||
parallel_tests (1.3.7)
|
||||
parallel (1.11.2)
|
||||
parallel_tests (2.14.1)
|
||||
parallel
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
xml-simple
|
||||
@@ -494,6 +497,7 @@ GEM
|
||||
polyamorous (0.5.0)
|
||||
activerecord (~> 3.0)
|
||||
polyglot (0.3.5)
|
||||
powerpack (0.1.1)
|
||||
pry (0.9.12.2)
|
||||
coderay (~> 1.0.5)
|
||||
method_source (~> 0.8)
|
||||
@@ -531,8 +535,10 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
raindrops (0.13.0)
|
||||
rake (11.1.2)
|
||||
rake (11.3.0)
|
||||
ransack (0.7.2)
|
||||
actionpack (~> 3.0)
|
||||
activerecord (~> 3.0)
|
||||
@@ -558,6 +564,9 @@ GEM
|
||||
roadie-rails (1.0.3)
|
||||
rails (>= 3.0, < 4.2)
|
||||
roadie (~> 3.0)
|
||||
roo (2.7.1)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (~> 1.1, < 2.0.0)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
@@ -575,7 +584,15 @@ GEM
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-retry (0.4.2)
|
||||
rspec-core
|
||||
ruby-progressbar (1.7.1)
|
||||
rubocop (0.49.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-progressbar (1.8.1)
|
||||
rubyzip (1.2.0)
|
||||
safe_yaml (0.9.5)
|
||||
sass (3.3.14)
|
||||
sass-rails (3.2.6)
|
||||
@@ -616,6 +633,7 @@ GEM
|
||||
uglifier (2.7.1)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
unicode-display_width (1.3.0)
|
||||
unicorn (4.9.0)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
@@ -639,9 +657,8 @@ GEM
|
||||
whenever (0.9.2)
|
||||
activesupport (>= 2.3.4)
|
||||
chronic (>= 0.6.3)
|
||||
wicked_pdf (0.11.0)
|
||||
rails
|
||||
wkhtmltopdf-binary (0.9.9.3)
|
||||
wicked_pdf (1.1.0)
|
||||
wkhtmltopdf-binary (0.12.3.1)
|
||||
xml-simple (1.1.4)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
@@ -717,8 +734,10 @@ DEPENDENCIES
|
||||
redcarpet
|
||||
representative_view
|
||||
roadie-rails (~> 1.0.3)
|
||||
roo (~> 2.7.0)
|
||||
rspec-rails
|
||||
rspec-retry
|
||||
rubocop (>= 0.49.1)
|
||||
sass (~> 3.3)
|
||||
sass-rails (~> 3.2.3)
|
||||
shoulda-matchers
|
||||
@@ -745,4 +764,4 @@ RUBY VERSION
|
||||
ruby 2.1.5p273
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.3
|
||||
1.15.1
|
||||
|
||||
@@ -47,4 +47,3 @@ end
|
||||
#watch(%r{^spec/acceptance/(.+)\.feature$})
|
||||
#watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
||||
#end
|
||||
|
||||
|
||||
@@ -98,6 +98,14 @@ The site is configured to use
|
||||
startup time while Rails loads. See the Zeus github page for
|
||||
usage instructions.
|
||||
|
||||
Once [npm dependencies are
|
||||
installed](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Karma), AngularJS tests can be run with:
|
||||
|
||||
./script/karma run
|
||||
|
||||
If you want karma to automatically rerun the tests on file modification, use:
|
||||
|
||||
./script/karma start
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ angular.module('admin.orderCycles')
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
|
||||
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
|
||||
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
|
||||
|
||||
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
|
||||
StatusMessage.setValidation(isValid)
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
|
||||
|
||||
@@ -13,7 +13,10 @@ angular.module('admin.orderCycles')
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
|
||||
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
|
||||
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
|
||||
|
||||
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
|
||||
StatusMessage.setValidation(isValid)
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
|
||||
|
||||
@@ -8,7 +8,10 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
|
||||
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
|
||||
|
||||
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
|
||||
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
|
||||
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
|
||||
|
||||
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
|
||||
StatusMessage.setValidation(isValid)
|
||||
|
||||
$scope.init = (enterprises) ->
|
||||
enterprise = enterprises[Object.keys(enterprises)[0]]
|
||||
|
||||
@@ -10,7 +10,10 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
|
||||
$scope.init()
|
||||
|
||||
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
|
||||
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
|
||||
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
|
||||
|
||||
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
|
||||
StatusMessage.setValidation(isValid)
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("ofn.admin").controller "DropdownPanelsCtrl", ($scope) ->
|
||||
$scope.active = false
|
||||
|
||||
$scope.togglePanel = ->
|
||||
$scope.active = !$scope.active
|
||||
@@ -0,0 +1,15 @@
|
||||
angular.module("ofn.admin").controller "ImportOptionsFormCtrl", ($scope, $rootScope, ProductImportService) ->
|
||||
|
||||
$scope.toggleResetAbsent = () ->
|
||||
confirmed = confirm 'This will set stock level to zero on all products for this \n' +
|
||||
'enterprise that are not present in the uploaded file.' if $scope.resetAbsent
|
||||
|
||||
if confirmed or !$scope.resetAbsent
|
||||
ProductImportService.updateResetAbsent($scope.supplierId, $scope.resetCount, $scope.resetAbsent)
|
||||
else
|
||||
$scope.resetAbsent = false
|
||||
|
||||
$scope.resetTotal = ProductImportService.resetTotal
|
||||
|
||||
$rootScope.$watch 'resetTotal', (newValue) ->
|
||||
$scope.resetTotal = newValue if newValue || newValue == 0
|
||||
@@ -0,0 +1,15 @@
|
||||
angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
|
||||
new class ProductImportService
|
||||
suppliers: {}
|
||||
resetTotal: 0
|
||||
|
||||
updateResetAbsent: (supplierId, resetCount, resetAbsent) ->
|
||||
if resetAbsent
|
||||
@suppliers[supplierId] = resetCount
|
||||
@resetTotal += resetCount
|
||||
else
|
||||
@suppliers[supplierId] = null
|
||||
@resetTotal -= resetCount
|
||||
|
||||
$rootScope.resetTotal = @resetTotal
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
angular.module("admin.products").controller "editUnitsCtrl", ($scope, VariantUnitManager) ->
|
||||
|
||||
$scope.product =
|
||||
variant_unit: angular.element('#variant_unit').val()
|
||||
variant_unit_scale: angular.element('#variant_unit_scale').val()
|
||||
|
||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||
|
||||
if $scope.product.variant_unit == 'items'
|
||||
$scope.variant_unit_with_scale = 'items'
|
||||
else
|
||||
$scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale
|
||||
|
||||
$scope.setFields = ->
|
||||
if $scope.variant_unit_with_scale == 'items'
|
||||
variant_unit = 'items'
|
||||
variant_unit_scale = null
|
||||
else
|
||||
options = $scope.variant_unit_with_scale.split('_')
|
||||
variant_unit = options[0]
|
||||
variant_unit_scale = options[1]
|
||||
|
||||
$scope.product.variant_unit = variant_unit
|
||||
$scope.product.variant_unit_scale = variant_unit_scale
|
||||
@@ -0,0 +1,14 @@
|
||||
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout) ->
|
||||
|
||||
$scope.unitName = (scale, type) ->
|
||||
VariantUnitManager.getUnitName(scale, type)
|
||||
|
||||
$scope.scale = angular.element('#product_variant_unit_scale').val()
|
||||
|
||||
$scope.updateValue = ->
|
||||
unit_value_human = angular.element('#unit_value_human').val()
|
||||
$scope.unit_value = unit_value_human * $scope.scale
|
||||
|
||||
variant_unit_value = angular.element('#variant_unit_value').val()
|
||||
$scope.unit_value_human = variant_unit_value / $scope.scale
|
||||
$timeout -> $scope.updateValue()
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.products", ["admin.utils"])
|
||||
angular.module("admin.products", ["textAngular", "admin.utils"])
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("admin.utils").directive "textangularStrip", () ->
|
||||
restrict: 'CA'
|
||||
link: (scope, element, attrs) ->
|
||||
scope.stripFormatting = ($html) ->
|
||||
return String($html).replace(/<[^>]+>/gm, '')
|
||||
@@ -11,6 +11,14 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
|
||||
text: ""
|
||||
style: {}
|
||||
|
||||
invalidMessage: ""
|
||||
|
||||
setValidation: (isValid) ->
|
||||
if isValid
|
||||
StatusMessage.invalidMessage = ''
|
||||
else
|
||||
StatusMessage.invalidMessage = t("admin.form_invalid")
|
||||
|
||||
active: ->
|
||||
@statusMessage.text != ''
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
$ ->
|
||||
if ($ 'form#update-cart').is('*')
|
||||
($ 'form#update-cart a.delete').show().one 'click', ->
|
||||
($ this).parents('.line-item').first().find('input.line_item_quantity').val 0
|
||||
($ this).parents('form').first().submit()
|
||||
if $('form#update-cart').is('*') || $('form#update-order').is('*')
|
||||
$('form#update-cart a.delete, form#update-order a.delete').show().one 'click', ->
|
||||
$(this).parents('.line-item').first().find('input.line_item_quantity').val 0
|
||||
$(this).parents('form').first().submit()
|
||||
false
|
||||
|
||||
($ 'form#update-cart').submit ->
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
Darkswarm.controller "EditBoughtOrderController", ($scope, $resource, Cart) ->
|
||||
$scope.showBought = false
|
||||
|
||||
$scope.deleteLineItem = (id) ->
|
||||
params = {id: id}
|
||||
success = (response) ->
|
||||
$(".line-item-" + id).remove()
|
||||
Cart.removeFinalisedLineItem(id)
|
||||
fail = (error) ->
|
||||
console.log error
|
||||
|
||||
$resource("/line_items/:id").delete(params, success, fail)
|
||||
@@ -10,7 +10,7 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
|
||||
$("#order_cycle_id").trigger("openTrigger")
|
||||
|
||||
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart) ->
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
|
||||
# Track previous order cycle id for use with revertOrderCycle()
|
||||
$scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
$scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)->
|
||||
@@ -30,3 +30,5 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod
|
||||
Variants.clear()
|
||||
Cart.clear()
|
||||
Products.update()
|
||||
Cart.reloadFinalisedLineItems()
|
||||
ChangeableOrdersAlert.reload()
|
||||
|
||||
@@ -5,7 +5,20 @@ Darkswarm.controller "RegistrationCtrl", ($scope, RegistrationService, Enterpris
|
||||
|
||||
$scope.steps = ['details', 'contact', 'type', 'about', 'images', 'social']
|
||||
|
||||
$scope.countries = availableCountries
|
||||
# Filter countries without states since the form requires a state to be selected.
|
||||
# Consider changing the form to require a state only if a country requires them (Spree option).
|
||||
# Invalid countries still need to be filtered (better server-side).
|
||||
$scope.countries = availableCountries.filter (country) ->
|
||||
country.states.length > 0
|
||||
|
||||
$scope.countriesById = $scope.countries.reduce (obj, country) ->
|
||||
obj[country.id] = country
|
||||
obj
|
||||
, {}
|
||||
|
||||
$scope.setDefaultCountry = (id) ->
|
||||
country = $scope.countriesById[id]
|
||||
$scope.enterprise.country = country if country
|
||||
|
||||
$scope.countryHasStates = ->
|
||||
$scope.enterprise.country.states.length > 0
|
||||
|
||||
@@ -11,8 +11,7 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
|
||||
'angularFileUpload',
|
||||
'angularSlideables'
|
||||
]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
|
||||
$httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
|
||||
$httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
|
||||
$httpProvider.defaults.headers['common']['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
|
||||
$httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'
|
||||
$httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*"
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Darkswarm.directive "changeableOrdersAlert", (ChangeableOrdersAlert) ->
|
||||
restrict: "C"
|
||||
scope: true
|
||||
link: (scope, element, attrs) ->
|
||||
scope.alert = ChangeableOrdersAlert
|
||||
@@ -0,0 +1,9 @@
|
||||
Darkswarm.directive "confirmLinkClick", ($window) ->
|
||||
restrict: 'A'
|
||||
scope:
|
||||
confirmMsg: '@confirmLinkClick'
|
||||
link: (scope, elem, attr) ->
|
||||
elem.bind 'click', (event) ->
|
||||
unless confirm(scope.confirmMsg)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
@@ -6,7 +6,11 @@ Darkswarm.directive "ofnOnHand", ->
|
||||
# In cases where this field gets its value from the HTML element rather than the model,
|
||||
# initialise the model with the HTML value.
|
||||
if scope.$eval(attr.ngModel) == undefined
|
||||
ngModel.$setViewValue elem.val()
|
||||
# Don't dirty the model when we do this
|
||||
setDirty = ngModel.$setDirty
|
||||
ngModel.$setDirty = angular.noop
|
||||
ngModel.$setViewValue(elem.val())
|
||||
ngModel.$setDirty = setDirty
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
on_hand = parseInt(attr.ofnOnHand)
|
||||
|
||||
@@ -5,4 +5,4 @@ Darkswarm.filter 'products', (Matcher) ->
|
||||
return products if text == ""
|
||||
products.filter (product) =>
|
||||
propertiesToMatch = [product.name, product.variant_names, product.supplier.name, product.primary_taxon.name]
|
||||
Matcher.match propertiesToMatch, text
|
||||
Matcher.matchBeginning propertiesToMatch, text
|
||||
|
||||
@@ -25,9 +25,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
|
||||
isActive: Navigation.isActive
|
||||
|
||||
close: ->
|
||||
if location.pathname in ["/", "/checkout"]
|
||||
Navigation.navigate "/"
|
||||
else
|
||||
if location.pathname in ["/register", "/register/auth"]
|
||||
Loading.message = t 'going_back_to_home_page'
|
||||
location.hash = ""
|
||||
location.pathname = "/"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, localStorageService)->
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService) ->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
@@ -6,11 +6,15 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
update_enqueued: false
|
||||
order: CurrentOrder.order
|
||||
line_items: CurrentOrder.order?.line_items || []
|
||||
line_items_finalised: CurrentOrder.order?.finalised_line_items || []
|
||||
|
||||
constructor: ->
|
||||
for line_item in @line_items
|
||||
line_item.variant.line_item = line_item
|
||||
Variants.register line_item.variant
|
||||
for line_item in @line_items_finalised
|
||||
line_item.variant.line_item = line_item
|
||||
Variants.extend line_item.variant
|
||||
|
||||
adjust: (line_item) =>
|
||||
line_item.total_price = line_item.variant.price_with_fees * line_item.quantity
|
||||
@@ -115,3 +119,15 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
clear: ->
|
||||
@line_items = []
|
||||
localStorageService.clearAll() # One day this will have to be moar GRANULAR
|
||||
|
||||
removeFinalisedLineItem: (id) =>
|
||||
@line_items_finalised = @line_items_finalised.filter (item) ->
|
||||
item.id != id
|
||||
|
||||
reloadFinalisedLineItems: =>
|
||||
@line_items_finalised = []
|
||||
$resource("/line_items/bought").query (items) =>
|
||||
for line_item in items
|
||||
line_item.variant.line_item = line_item
|
||||
Variants.extend line_item.variant
|
||||
@line_items_finalised = items
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
Darkswarm.factory 'ChangeableOrdersAlert', ($http) ->
|
||||
new class ChangeableOrdersAlert
|
||||
html: ''
|
||||
visible: true
|
||||
|
||||
constructor: ->
|
||||
@reload()
|
||||
|
||||
reload: ->
|
||||
$http.get('/shop/changeable_orders_alert').then (response) =>
|
||||
@html = response.data.trim()
|
||||
|
||||
close: =>
|
||||
@visible = false
|
||||
@@ -1,7 +1,14 @@
|
||||
Darkswarm.factory "Matcher", ->
|
||||
# Match text fragment in an array of strings.
|
||||
new class Matcher
|
||||
|
||||
# Match text fragment in an array of strings.
|
||||
match: (properties, text)->
|
||||
properties.some (prop)->
|
||||
prop ||= ""
|
||||
prop.toLowerCase().indexOf(text.toLowerCase()) != -1
|
||||
|
||||
# Return true if text occurs at the beginning of any word present in an array of strings
|
||||
matchBeginning: (properties, text) ->
|
||||
text = text.trim()
|
||||
regexp = new RegExp("(?:^|[\\s-])#{text}", "i")
|
||||
properties.some (prop) -> (prop || "").search(regexp) >= 0
|
||||
|
||||
@@ -3,10 +3,12 @@ Darkswarm.factory 'Orders', (orders_by_distributor, currencyConfig, CurrentHub,
|
||||
constructor: ->
|
||||
# Populate Orders.orders from json in page.
|
||||
@orders_by_distributor = orders_by_distributor
|
||||
@changeable_orders = []
|
||||
@currency_symbol = currencyConfig.symbol
|
||||
|
||||
for distributor in @orders_by_distributor
|
||||
@updateRunningBalance(distributor.distributed_orders)
|
||||
@findChangeableOrders(distributor.distributed_orders)
|
||||
@updateRunningBalance(distributor.distributed_orders)
|
||||
|
||||
|
||||
updateRunningBalance: (orders) ->
|
||||
@@ -14,3 +16,7 @@ Darkswarm.factory 'Orders', (orders_by_distributor, currencyConfig, CurrentHub,
|
||||
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)
|
||||
|
||||
findChangeableOrders: (orders) ->
|
||||
for order in orders when order.changes_allowed
|
||||
@changeable_orders.push(order)
|
||||
|
||||
@@ -42,9 +42,10 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
|
||||
registerVariants: ->
|
||||
for product in @products
|
||||
if product.variants
|
||||
product.variant_names = ""
|
||||
product.variants = for variant in product.variants
|
||||
variant = Variants.register variant
|
||||
if product.name != variant.name_to_display
|
||||
product.variant_names += variant.name_to_display
|
||||
product.variant_names += variant.name_to_display + " "
|
||||
variant.product = product
|
||||
variant
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
|
||||
.container
|
||||
.eight.columns.alpha
|
||||
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
|
||||
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage == ''", style: 'StatusMessage.statusMessage.style' } }
|
||||
{{ StatusMessage.statusMessage.text || " " }}
|
||||
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage !== ''" }, style: 'color: #da5354' }
|
||||
{{ StatusMessage.invalidMessage || " " }}
|
||||
.eight.columns.omega.text-right{ ng: { transclude: true } }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%div{ ng: { show: "data.length > limit" } }
|
||||
%input{ type: 'button', value: t(:show_more), ng: { click: 'limit = limit + increment' } }
|
||||
or
|
||||
%input{ type: 'button', value: t(:show_more_with_more, num: '{{ data.length - limit }}'), ng: { click: 'limit = data.length' } }
|
||||
%input{ type: 'button', value: t(:show_all_with_more, num: '{{ data.length - limit }}'), ng: { click: 'limit = data.length' } }
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
%p.modal-header {{'contact' | t}}
|
||||
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
|
||||
|
||||
%p.word-wrap{"ng-if" => "::enterprise.email_address"}
|
||||
%p{"ng-if" => "::enterprise.email_address"}
|
||||
%a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
|
||||
%span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
|
||||
|
||||
%p.word-wrap{"ng-if" => "enterprise.website"}
|
||||
%p{"ng-if" => "enterprise.website"}
|
||||
%a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
.filter-shopfront.property-selectors.inline-block
|
||||
%filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
|
||||
|
||||
%div{"ng-if" => "product.description"}
|
||||
%div{"ng-if" => "product.description_html"}
|
||||
%hr
|
||||
%p.text-small{"ng-bind" => "::product.description"}
|
||||
%p.text-small{"ng-bind-html" => "::product.description_html"}
|
||||
%hr
|
||||
|
||||
.columns.small-12.large-6
|
||||
|
||||
3
app/assets/stylesheets/admin/enterprises.css.scss
Normal file
3
app/assets/stylesheets/admin/enterprises.css.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
form[name="enterprise_form"] div.row.warning {
|
||||
color: #DA7F52;
|
||||
}
|
||||
@@ -74,6 +74,9 @@ form.order_cycle {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
.icon-question-sign {
|
||||
font-size: 18px;
|
||||
}
|
||||
table.exchanges {
|
||||
tr td.active {
|
||||
width: 20px;
|
||||
@@ -113,6 +116,14 @@ form.order_cycle {
|
||||
margin-right: 2em;
|
||||
}
|
||||
}
|
||||
.collection-details {
|
||||
input {
|
||||
width: 90%
|
||||
}
|
||||
span {
|
||||
font-size: 1rem
|
||||
}
|
||||
}
|
||||
}
|
||||
.coordinator-fees {
|
||||
margin-top: 1em;
|
||||
@@ -200,6 +211,13 @@ table#listing_enterprise_groups {
|
||||
|
||||
// textAngular wysiwyg
|
||||
text-angular {
|
||||
.ta-toolbar {
|
||||
border: 1px solid #cdd9e4;
|
||||
padding: 0.4em;
|
||||
margin-bottom: -1px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 0.25em 0.25em 0 0;
|
||||
}
|
||||
.ta-scroll-window > .ta-bind {
|
||||
max-height: 400px;
|
||||
min-height: 100px;
|
||||
@@ -210,12 +228,18 @@ text-angular {
|
||||
}
|
||||
.ta-scroll-window.form-control {
|
||||
min-height: 100px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.btn-group {
|
||||
display: inline;
|
||||
margin-right: 8px;
|
||||
button {
|
||||
padding: 5px 10px;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
button.active:not(:hover) {
|
||||
box-shadow: 0 0 0.7em rgba(0,0,0,0.3) inset;
|
||||
background-color: #4583bf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
225
app/assets/stylesheets/admin/product_import.css.scss
Normal file
225
app/assets/stylesheets/admin/product_import.css.scss
Normal file
@@ -0,0 +1,225 @@
|
||||
div.panel-section {
|
||||
|
||||
.neutral {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.warning {
|
||||
color: #da5354;
|
||||
}
|
||||
.success {
|
||||
color: #86d83a;
|
||||
}
|
||||
.info {
|
||||
color: #68b7c0;
|
||||
}
|
||||
|
||||
div.panel-header {
|
||||
width: 100%;
|
||||
//font-size: 1.5em;
|
||||
clear: both;
|
||||
//border: 1px solid #ccc;
|
||||
float: left;
|
||||
padding: 0.5em;
|
||||
|
||||
div {
|
||||
font-size: 1.25em;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.header-caret {
|
||||
width: 2em;
|
||||
text-align: center;
|
||||
min-height: 0.1em; //Empty div fix
|
||||
}
|
||||
|
||||
div.header-icon {
|
||||
width: 2.5em;
|
||||
text-align: center;
|
||||
padding-top: 0.18em;
|
||||
|
||||
i {
|
||||
font-size: 1.5em;
|
||||
line-height: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
div.header-count {
|
||||
min-width: 2em;
|
||||
text-align: right;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
div.header-description {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
div.panel-header:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
div.panel-header.active {
|
||||
background-color: #efefef;
|
||||
text-shadow: 1px 1px 0px rgba(255,255,255,0.75);
|
||||
}
|
||||
|
||||
div.panel-content {
|
||||
width: 100%;
|
||||
clear: both;
|
||||
//border: 1px solid #ccc;
|
||||
margin-bottom: 0.5em;
|
||||
background-color: #f9f9f9;
|
||||
padding: 1.5em;
|
||||
|
||||
div.table-wrap {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-right: 1px solid #ceede3;
|
||||
max-height: 23em;
|
||||
}
|
||||
|
||||
table {
|
||||
background-color: white;
|
||||
margin-bottom: 0;
|
||||
td, th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
tr.error {
|
||||
//background-color: #ffe6e4;
|
||||
//color: #ee4728;
|
||||
color: #c84C4c;
|
||||
}
|
||||
tr:hover td.invalid {
|
||||
background-color: #ed5135;
|
||||
}
|
||||
tr i {
|
||||
display: block;
|
||||
margin-bottom: -0.2em;
|
||||
font-size: 1.4em !important;
|
||||
}
|
||||
td.invalid {
|
||||
background-color: #f05c51;
|
||||
box-shadow: inset 0px 0px 1px red;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
div.import-errors {
|
||||
margin-bottom: 0.85em;
|
||||
|
||||
p.line {
|
||||
font-size: 1.15em;
|
||||
margin-bottom: 0.2em;
|
||||
color: #577084;
|
||||
}
|
||||
p.error {
|
||||
color: #cb1b1b;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
br.panels.clearfix {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
table.import-settings {
|
||||
background-color: transparent !important;
|
||||
width: auto;
|
||||
|
||||
//select {
|
||||
// width: 100%;
|
||||
//}
|
||||
tr {
|
||||
|
||||
}
|
||||
tbody tr:hover td {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
td {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: left;
|
||||
|
||||
input {
|
||||
width: 15em;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
}
|
||||
td.description {
|
||||
font-weight: bold;
|
||||
padding-right: 2.5em;
|
||||
}
|
||||
tr:first-child td {
|
||||
//border-top: 1px solid #eee;
|
||||
border-top: 0;
|
||||
}
|
||||
tr:last-child td {
|
||||
//border-top: 1px solid #eee;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.panel-section.import-settings {
|
||||
|
||||
.header-description {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
span.header-error {
|
||||
font-size: 0.85em;
|
||||
color: #da5354;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-results {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.post-save-results {
|
||||
p {
|
||||
font-size: 1.25em;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
strong {
|
||||
margin-right: 0.2em;
|
||||
min-width: 1.8em;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.4em;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
i.fa-check-circle {
|
||||
color: #86d83a;
|
||||
}
|
||||
i.fa-info-circle {
|
||||
color: #68b7c0;
|
||||
}
|
||||
}
|
||||
|
||||
p.save-error {
|
||||
color: #ee4728;
|
||||
font-size: 1.05em;
|
||||
margin-top: 0.4em;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
height: 100px
|
||||
width: 100px
|
||||
margin-right: 12px
|
||||
|
||||
|
||||
location
|
||||
@include headingFont
|
||||
@media all and (max-width: 768px)
|
||||
@@ -30,7 +30,7 @@
|
||||
margin-top: 0
|
||||
@media all and (max-width: 768px)
|
||||
margin-bottom: 8px
|
||||
|
||||
|
||||
|
||||
ordercycle
|
||||
text-align: right
|
||||
@@ -47,24 +47,26 @@
|
||||
p
|
||||
max-width: 100%
|
||||
float: right
|
||||
form.custom
|
||||
form.custom
|
||||
text-align: right
|
||||
& > strong
|
||||
line-height: 2.5
|
||||
font-size: 1.29em
|
||||
padding-right: 14px
|
||||
@media all and (max-width: 768px)
|
||||
select
|
||||
select
|
||||
width: inherit
|
||||
display: inline-block
|
||||
border-width: 1px
|
||||
border-color: #999
|
||||
color: #666
|
||||
font-size: 1em
|
||||
font-size: 1em
|
||||
margin-bottom: 0
|
||||
padding: 8px 20px 8px 12px
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 0.875em
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0)
|
||||
font-size: 16px
|
||||
closing
|
||||
@include headingFont
|
||||
@media all and (max-width: 768px)
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
@import "branding";
|
||||
@import "mixins";
|
||||
|
||||
.account-summary {
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
|
||||
.orders {
|
||||
@include sidepaddingSm;
|
||||
|
||||
@include panepadding;
|
||||
|
||||
padding-top: 10px;
|
||||
|
||||
h3 {
|
||||
padding-top: 2em;
|
||||
}
|
||||
margin-top: 50px;
|
||||
margin-bottom: 100px;
|
||||
|
||||
a {
|
||||
color: $clr-brick;
|
||||
@@ -26,6 +24,12 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.active_table_row {
|
||||
h3 {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
i.ofn-i_059-producer, i.ofn-i_060-producer-reversed {
|
||||
font-size: 3rem;
|
||||
display: inline-block;
|
||||
@@ -61,6 +65,7 @@
|
||||
.transaction-group {}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
|
||||
tr:nth-of-type(even) {
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
|
||||
@-webkit-keyframes slideOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
-ms-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@@ -46,10 +48,18 @@
|
||||
|
||||
@keyframes slideOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
-ms-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-20px);
|
||||
-ms-transform: translateY(-20px);
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
|
||||
@@ -6,23 +6,25 @@
|
||||
background-color: #e1f0f5
|
||||
padding: 1em
|
||||
width: 100%
|
||||
border: none
|
||||
color: inherit
|
||||
|
||||
checkout
|
||||
display: block
|
||||
|
||||
@media all and (max-width: 640px)
|
||||
&.row .row
|
||||
@media all and (max-width: 640px)
|
||||
&.row .row
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
|
||||
|
||||
orderdetails
|
||||
.button, table
|
||||
width: 100%
|
||||
@media all and (max-width: 640px)
|
||||
@media all and (max-width: 640px)
|
||||
form.edit_order
|
||||
border: 1px solid $disabled-bright
|
||||
margin-bottom: 2rem
|
||||
|
||||
|
||||
#details, #billing, #shipping, #payment
|
||||
border: 0
|
||||
margin: 1em 0
|
||||
@@ -34,20 +36,20 @@ checkout
|
||||
margin: 0
|
||||
padding: 0.65em
|
||||
background: #f7f7f7
|
||||
|
||||
.label
|
||||
|
||||
.label
|
||||
font-size: 1em
|
||||
padding: 0.3rem 0.35rem 0.275rem
|
||||
|
||||
// Logic to turn on & off the alerts for success against each fieldset
|
||||
|
||||
label, label.alert, label.success, &.valid label.alert, &.dirty label.success
|
||||
label, label.alert, label.success, &.valid label.alert, &.dirty label.success
|
||||
display: none
|
||||
|
||||
&.dirty label.alert
|
||||
&.dirty label.alert
|
||||
display: inline
|
||||
&.dirty.valid label.alert
|
||||
display: none
|
||||
&.dirty.valid label.alert
|
||||
display: none
|
||||
&.valid label.success
|
||||
display: inline
|
||||
|
||||
@@ -60,7 +62,7 @@ checkout
|
||||
text-align: left
|
||||
|
||||
// Logic to swap out up / down accordion icons
|
||||
//Foundation overrides
|
||||
//Foundation overrides
|
||||
dd > a
|
||||
@include csstrans
|
||||
background: $disabled-light !important
|
||||
@@ -68,7 +70,7 @@ checkout
|
||||
dd > a:hover
|
||||
background: $disabled-v-dark !important
|
||||
color: white
|
||||
|
||||
|
||||
dd
|
||||
span.accordion-up
|
||||
display: none
|
||||
@@ -79,4 +81,3 @@ checkout
|
||||
display: inline
|
||||
span.accordion-down
|
||||
display: none
|
||||
|
||||
|
||||
@@ -117,6 +117,13 @@ nav {
|
||||
padding: 9px 0 0 9px;
|
||||
}
|
||||
|
||||
.top-bar .ofn-logo img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-height: 51px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.left-off-canvas-menu {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
color: $clr-turquoise;
|
||||
}
|
||||
|
||||
.vertical-align-middle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a {
|
||||
&:hover, &:active, &:focus {
|
||||
color: $clr-turquoise-bright;
|
||||
|
||||
@@ -110,6 +110,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.alert-box.changeable-orders-alert {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.alert-box.shopfront-message {
|
||||
border: 2px solid $clr-turquoise;
|
||||
border-radius: 5px;
|
||||
|
||||
@@ -72,11 +72,35 @@
|
||||
|
||||
// Shopping cart
|
||||
#cart-detail {
|
||||
.cart-item-delete {
|
||||
a.delete {
|
||||
.cart-item-delete, .bought-item-delete {
|
||||
a {
|
||||
font-size: 1.125em;
|
||||
}
|
||||
}
|
||||
|
||||
.out-of-stock {
|
||||
color: $clr-brick;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toggle-bought {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr.bought td {
|
||||
color: $med-grey;
|
||||
|
||||
h5 {
|
||||
color: $med-grey;
|
||||
}
|
||||
|
||||
.already-confirmed {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-thumb-image {
|
||||
@@ -90,9 +114,3 @@
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
#edit-cart {
|
||||
button, .button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ module Admin
|
||||
@owner_email = @enterprise_group.andand.owner.andand.email || ""
|
||||
end
|
||||
|
||||
|
||||
def collection
|
||||
EnterpriseGroup.by_position
|
||||
end
|
||||
|
||||
@@ -115,7 +115,7 @@ module Admin
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index]
|
||||
[:index, :bulk_update]
|
||||
end
|
||||
|
||||
private
|
||||
@@ -179,9 +179,5 @@ module Admin
|
||||
def ams_prefix_whitelist
|
||||
[:basic]
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :bulk_update]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
62
app/controllers/admin/product_import_controller.rb
Normal file
62
app/controllers/admin/product_import_controller.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
require 'roo'
|
||||
|
||||
class Admin::ProductImportController < Spree::Admin::BaseController
|
||||
|
||||
before_filter :validate_upload_presence, except: :index
|
||||
|
||||
def import
|
||||
# Save uploaded file to tmp directory
|
||||
@filepath = save_uploaded_file(params[:file])
|
||||
@importer = ProductImporter.new(File.new(@filepath), editable_enterprises)
|
||||
|
||||
check_file_errors @importer
|
||||
check_spreadsheet_has_data @importer
|
||||
|
||||
@tax_categories = Spree::TaxCategory.order('is_default DESC, name ASC')
|
||||
@shipping_categories = Spree::ShippingCategory.order('name ASC')
|
||||
end
|
||||
|
||||
def save
|
||||
@importer = ProductImporter.new(File.new(params[:filepath]), editable_enterprises, params[:settings])
|
||||
@importer.save_all if @importer.has_valid_entries?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_upload_presence
|
||||
unless params[:file] || (params[:filepath] && File.exist?(params[:filepath]))
|
||||
redirect_to '/admin/product_import', notice: 'File not found or could not be opened'
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def check_file_errors(importer)
|
||||
if importer.errors.present?
|
||||
redirect_to '/admin/product_import', notice: @importer.errors.full_messages.to_sentence
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def check_spreadsheet_has_data(importer)
|
||||
unless importer.item_count
|
||||
redirect_to '/admin/product_import', notice: 'No data found in spreadsheet'
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def save_uploaded_file(upload)
|
||||
filename = 'import' + Time.now.strftime('%d-%m-%Y-%H-%M-%S')
|
||||
extension = '.' + upload.original_filename.split('.').last
|
||||
directory = 'tmp/product_import'
|
||||
Dir.mkdir(directory) unless File.exists?(directory)
|
||||
File.open(Rails.root.join(directory, filename+extension), 'wb') do |f|
|
||||
f.write(upload.read)
|
||||
f.path
|
||||
end
|
||||
end
|
||||
|
||||
# Define custom model class for Cancan permissions
|
||||
def model_class
|
||||
ProductImporter
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,6 @@ module Admin
|
||||
"#{controller_name.classify}".constantize
|
||||
end
|
||||
|
||||
|
||||
# URL helpers
|
||||
def new_object_url(options = {})
|
||||
if parent_data.present?
|
||||
|
||||
@@ -12,7 +12,6 @@ module Admin
|
||||
def index
|
||||
end
|
||||
|
||||
|
||||
def bulk_update
|
||||
# Ensure we're authorised to update all variant overrides
|
||||
@vo_set.collection.each { |vo| authorize! :update, vo }
|
||||
|
||||
@@ -24,7 +24,6 @@ class ApplicationController < ActionController::Base
|
||||
params[:action].to_sym
|
||||
end
|
||||
|
||||
|
||||
def require_distributor_chosen
|
||||
unless @distributor = current_distributor
|
||||
redirect_to spree.root_path
|
||||
@@ -62,7 +61,6 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# All render calls within the block will be performed with the specified format
|
||||
# Useful for rendering html within a JSON response, particularly if the specified
|
||||
# template or partial then goes on to render further partials without specifying
|
||||
|
||||
@@ -23,6 +23,11 @@ class BaseController < ApplicationController
|
||||
private
|
||||
|
||||
def set_order_cycles
|
||||
unless @distributor.ready_for_checkout?
|
||||
@order_cycles = OrderCycle.where('false')
|
||||
return
|
||||
end
|
||||
|
||||
@order_cycles = OrderCycle.with_distributor(@distributor).active
|
||||
.order(@distributor.preferred_shopfront_order_cycle_order)
|
||||
|
||||
|
||||
@@ -123,7 +123,6 @@ class CheckoutController < Spree::CheckoutController
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
def update_failed
|
||||
clear_ship_address
|
||||
respond_to do |format|
|
||||
@@ -168,8 +167,8 @@ class CheckoutController < Spree::CheckoutController
|
||||
|
||||
customer_preferred_bill_address, customer_preferred_ship_address = @order.customer.bill_address, @order.customer.ship_address if @order.customer
|
||||
|
||||
@order.bill_address ||= customer_preferred_bill_address ||= preferred_bill_address || last_used_bill_address || Spree::Address.default
|
||||
@order.ship_address ||= customer_preferred_ship_address ||= preferred_ship_address || last_used_ship_address || Spree::Address.default
|
||||
@order.bill_address ||= customer_preferred_bill_address || preferred_bill_address || last_used_bill_address || Spree::Address.default
|
||||
@order.ship_address ||= customer_preferred_ship_address || preferred_ship_address || last_used_ship_address || Spree::Address.default
|
||||
end
|
||||
|
||||
def after_payment
|
||||
|
||||
@@ -30,7 +30,6 @@ class DiscourseSsoController < ApplicationController
|
||||
|
||||
def sso_url
|
||||
secret = discourse_sso_secret!
|
||||
discourse_url = discourse_url!
|
||||
sso = Discourse::SingleSignOn.parse(request.query_string, secret)
|
||||
sso.email = spree_current_user.email
|
||||
sso.username = spree_current_user.login
|
||||
|
||||
48
app/controllers/line_items_controller.rb
Normal file
48
app/controllers/line_items_controller.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
class LineItemsController < BaseController
|
||||
respond_to :json
|
||||
|
||||
before_filter :load_line_item, only: :destroy
|
||||
|
||||
def bought
|
||||
respond_with bought_items, each_serializer: Api::LineItemSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize! :destroy, @line_item
|
||||
destroy_with_lock @line_item
|
||||
respond_with(@line_item)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_line_item
|
||||
@line_item = Spree::LineItem.find_by_id(params[:id])
|
||||
not_found unless @line_item
|
||||
end
|
||||
|
||||
# List all items the user already ordered in the current order cycle
|
||||
def bought_items
|
||||
return [] unless current_order_cycle && spree_current_user && current_distributor
|
||||
current_order_cycle.items_bought_by_user(spree_current_user, current_distributor)
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
status = spree_current_user ? 403 : 401
|
||||
render nothing: true, status: status and return
|
||||
end
|
||||
|
||||
def not_found
|
||||
render nothing: true, status: 404 and return
|
||||
end
|
||||
|
||||
def destroy_with_lock(item)
|
||||
order = item.order
|
||||
order.with_lock do
|
||||
item.destroy
|
||||
order.update_shipping_fees!
|
||||
order.update_payment_fees!
|
||||
order.update_distribution_charge!
|
||||
order.create_tax_charge!
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -37,6 +37,10 @@ class ShopController < BaseController
|
||||
end
|
||||
end
|
||||
|
||||
def changeable_orders_alert
|
||||
render layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_json(products_json)
|
||||
|
||||
@@ -28,7 +28,6 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def set_included_tax
|
||||
if params[:tax_rate_id].present?
|
||||
tax_rate = TaxRate.find params[:tax_rate_id]
|
||||
|
||||
@@ -12,6 +12,9 @@ Spree::Admin::OrdersController.class_eval do
|
||||
|
||||
before_filter :load_distribution_choices, only: [:new, :edit, :update]
|
||||
|
||||
# Ensure that the distributor is set for an order when
|
||||
before_filter :ensure_distribution, only: :new
|
||||
|
||||
# After updating an order, the fees should be updated as well
|
||||
# Currently, adding or deleting line items does not trigger updating the
|
||||
# fees! This is a quick fix for that.
|
||||
@@ -131,4 +134,16 @@ Spree::Admin::OrdersController.class_eval do
|
||||
ocs.closed +
|
||||
ocs.undated
|
||||
end
|
||||
|
||||
def ensure_distribution
|
||||
unless @order
|
||||
@order = Spree::Order.new
|
||||
@order.generate_order_number
|
||||
@order.save!
|
||||
end
|
||||
unless @order.distribution_set?
|
||||
render 'set_distribution', locals: { order: @order }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -101,7 +101,6 @@ Spree::Admin::ReportsController.class_eval do
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrderCycleManagementReport.new spree_current_user, params
|
||||
@table = @report.table_items
|
||||
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], "order_cycle_management_#{timestamp}.csv")
|
||||
end
|
||||
@@ -126,7 +125,6 @@ Spree::Admin::ReportsController.class_eval do
|
||||
@report = OpenFoodNetwork::PackingReport.new spree_current_user, params
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
@table = order_grouper.table(@report.table_items)
|
||||
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], "packing_#{timestamp}.csv")
|
||||
end
|
||||
@@ -231,7 +229,7 @@ Spree::Admin::ReportsController.class_eval do
|
||||
@report_types = REPORT_TYPES[:orders_and_fulfillment]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
@include_blank = 'All'
|
||||
@include_blank = I18n.t(:all)
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user, params
|
||||
@@ -273,7 +271,6 @@ Spree::Admin::ReportsController.class_eval do
|
||||
render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
|
||||
def render_report(header, table, create_csv, csv_file_name)
|
||||
unless create_csv
|
||||
render :html => table
|
||||
|
||||
@@ -8,10 +8,12 @@ Spree::OrdersController.class_eval do
|
||||
prepend_before_filter :require_order_cycle, only: :edit
|
||||
prepend_before_filter :require_distributor_chosen, only: :edit
|
||||
before_filter :check_hub_ready_for_checkout, only: :edit
|
||||
before_filter :check_at_least_one_line_item, only: :update
|
||||
|
||||
include OrderCyclesHelper
|
||||
layout 'darkswarm'
|
||||
|
||||
respond_to :json
|
||||
|
||||
# Patching to redirect to shop if order is empty
|
||||
def edit
|
||||
@@ -29,10 +31,9 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
@insufficient_stock_lines = []
|
||||
@order = current_order
|
||||
@order = order_to_update
|
||||
unless @order
|
||||
flash[:error] = t(:order_not_found)
|
||||
redirect_to root_path and return
|
||||
@@ -43,12 +44,19 @@ Spree::OrdersController.class_eval do
|
||||
|
||||
render :edit and return unless apply_coupon_code
|
||||
|
||||
fire_event('spree.order.contents_changed')
|
||||
if @order == current_order
|
||||
fire_event('spree.order.contents_changed')
|
||||
else
|
||||
@order.update_distribution_charge!
|
||||
end
|
||||
|
||||
respond_with(@order) do |format|
|
||||
format.html do
|
||||
if params.has_key?(:checkout)
|
||||
@order.next_transition.run_callbacks if @order.cart?
|
||||
redirect_to checkout_state_path(@order.checkout_steps.first)
|
||||
elsif @order.complete?
|
||||
redirect_to order_path(@order)
|
||||
else
|
||||
redirect_to cart_path
|
||||
end
|
||||
@@ -62,7 +70,6 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def populate
|
||||
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
|
||||
# during cart population, for both taxation and enterprise fees. This operation triggers a
|
||||
@@ -90,7 +97,6 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Report the stock levels in the order for all variant ids requested
|
||||
def stock_levels(order, variant_ids)
|
||||
stock_levels = li_stock_levels(order)
|
||||
@@ -162,6 +168,18 @@ Spree::OrdersController.class_eval do
|
||||
@order_cycle = OrderCycle.find session[:expired_order_cycle_id]
|
||||
end
|
||||
|
||||
def cancel
|
||||
@order = Spree::Order.find_by_number!(params[:id])
|
||||
authorize! :cancel, @order
|
||||
|
||||
if @order.cancel
|
||||
flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
|
||||
else
|
||||
flash[:error] = I18n.t(:orders_could_not_cancel)
|
||||
end
|
||||
redirect_to request.referer || order_path(@order)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
@@ -202,4 +220,30 @@ Spree::OrdersController.class_eval do
|
||||
def wrap_json_infinity(n)
|
||||
n == Float::INFINITY ? 2147483647 : n
|
||||
end
|
||||
|
||||
def order_to_update
|
||||
return @order_to_update if defined? @order_to_update
|
||||
return @order_to_update = current_order unless params[:id]
|
||||
@order_to_update = changeable_order_from_number
|
||||
end
|
||||
|
||||
# If a specific order is requested, return it if it is COMPLETE and
|
||||
# changes are allowed and the user has access. Return nil if not.
|
||||
def changeable_order_from_number
|
||||
order = Spree::Order.complete.find_by_number(params[:id])
|
||||
return nil unless order.andand.changes_allowed? && can?(:update, order)
|
||||
order
|
||||
end
|
||||
|
||||
def check_at_least_one_line_item
|
||||
return unless order_to_update.andand.complete?
|
||||
|
||||
items = params[:order][:line_items_attributes]
|
||||
.andand.select{ |k,attrs| attrs["quantity"].to_i > 0 }
|
||||
|
||||
if items.empty?
|
||||
flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
|
||||
redirect_to order_path(order_to_update)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,12 +5,12 @@ module Admin
|
||||
end
|
||||
|
||||
def admin_image_settings_geometry_from_style(style)
|
||||
geometry, format = admin_image_settings_split_style style
|
||||
geometry, _format = admin_image_settings_split_style style
|
||||
geometry
|
||||
end
|
||||
|
||||
def admin_image_settings_format_from_style(style)
|
||||
geometry, format = admin_image_settings_split_style style
|
||||
_geometry, format = admin_image_settings_split_style style
|
||||
format
|
||||
end
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ module ApplicationHelper
|
||||
form_for(name, *(args << options.merge(:builder => AngularFormBuilder)), &block)
|
||||
end
|
||||
|
||||
|
||||
# Pass URL helper calls on to spree where applicable so that we don't need to use
|
||||
# spree.foo_path in any view rendered from non-spree-namespaced controllers.
|
||||
def method_missing(method, *args, &block)
|
||||
|
||||
@@ -16,7 +16,7 @@ module CheckoutHelper
|
||||
enterprise_fee_adjustments = adjustments.select { |a| a.originator_type == 'EnterpriseFee' && a.source_type != 'Spree::LineItem' }
|
||||
adjustments.reject! { |a| a.originator_type == 'EnterpriseFee' && a.source_type != 'Spree::LineItem' }
|
||||
unless exclude.include? :admin_and_handling
|
||||
adjustments << Spree::Adjustment.new(label: 'Admin & Handling', amount: enterprise_fee_adjustments.sum(&:amount))
|
||||
adjustments << Spree::Adjustment.new(label: I18n.t(:orders_form_admin), amount: enterprise_fee_adjustments.sum(&:amount))
|
||||
end
|
||||
|
||||
adjustments
|
||||
@@ -54,12 +54,8 @@ module CheckoutHelper
|
||||
end
|
||||
|
||||
def display_adjustment_tax_rates(adjustment)
|
||||
tax_rate = (adjustment.included_tax / (adjustment.amount - adjustment.included_tax)).round(2)
|
||||
if tax_rate == 0 || tax_rate.infinite?
|
||||
""
|
||||
else
|
||||
number_to_percentage(tax_rate * 100, :precision => 1)
|
||||
end
|
||||
tax_rates = adjustment.tax_rates
|
||||
tax_rates.map { |tr| number_to_percentage(tr.amount * 100, :precision => 1) }.join(", ")
|
||||
end
|
||||
|
||||
def display_adjustment_amount(adjustment)
|
||||
@@ -84,7 +80,6 @@ module CheckoutHelper
|
||||
available_countries.map { |c| [c.name, c.id] }
|
||||
end
|
||||
|
||||
|
||||
def validated_input(name, path, args = {})
|
||||
attributes = {
|
||||
required: true,
|
||||
|
||||
@@ -88,4 +88,12 @@ module EnterprisesHelper
|
||||
def remaining_trial_days(enterprise)
|
||||
distance_of_time_in_words(Time.zone.now, enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days)
|
||||
end
|
||||
|
||||
def order_changes_allowed?
|
||||
current_order.andand.distributor.andand.allow_order_changes?
|
||||
end
|
||||
|
||||
def show_bought_items?
|
||||
order_changes_allowed? && current_order.finalised_line_items.present?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,7 +43,6 @@ module OrderCyclesHelper
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def order_cycle_options
|
||||
@order_cycles.
|
||||
with_distributor(current_distributor).
|
||||
|
||||
@@ -16,5 +16,31 @@ module Spree
|
||||
def cart_count
|
||||
current_order.andand.line_items.andand.count || 0
|
||||
end
|
||||
|
||||
def changeable_orders
|
||||
# Only returns open order for the current user + shop + oc combo
|
||||
return @changeable_orders unless @changeable_orders.nil?
|
||||
return @changeable_orders = [] unless spree_current_user && current_distributor && current_order_cycle
|
||||
return @changeable_orders = [] unless current_distributor.allow_order_changes?
|
||||
@changeable_orders = Spree::Order.complete.where(
|
||||
state: 'complete',
|
||||
user_id: spree_current_user.id,
|
||||
distributor_id: current_distributor.id,
|
||||
order_cycle_id: current_order_cycle.id)
|
||||
end
|
||||
|
||||
def changeable_orders_link_path
|
||||
changeable_orders.one? ? spree.order_path(changeable_orders.first) : spree.account_path
|
||||
end
|
||||
|
||||
def shop_changeable_orders_alert_html
|
||||
return "" unless changeable_orders.any?
|
||||
t(:shop_changeable_orders_alert_html,
|
||||
count: changeable_orders.count,
|
||||
path: changeable_orders_link_path,
|
||||
order: changeable_orders.first.number,
|
||||
shop: current_distributor.name,
|
||||
oc_close: l(current_order_cycle.orders_close_at, format: "%A, %b %d, %Y @ %H:%M"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,17 +6,14 @@ module Spree
|
||||
"(#{Spree::Money.new(variant.price).to_s})"
|
||||
end
|
||||
|
||||
|
||||
def product_has_variant_unit_option_type?(product)
|
||||
product.option_types.any? { |option_type| variant_unit_option_type? option_type }
|
||||
end
|
||||
|
||||
|
||||
def variant_unit_option_type?(option_type)
|
||||
Spree::Product.all_variant_unit_option_types.include? option_type
|
||||
end
|
||||
|
||||
|
||||
def product_variant_unit_options
|
||||
[['Weight', 'weight'],
|
||||
['Volume', 'volume'],
|
||||
|
||||
@@ -37,7 +37,7 @@ class UpdateAccountInvoices
|
||||
if billable_periods.any?
|
||||
oldest_enterprise = billable_periods.first.enterprise
|
||||
address = oldest_enterprise.address.dup
|
||||
first, space, last = (oldest_enterprise.contact || "").partition(' ')
|
||||
first, _space, last = (oldest_enterprise.contact || "").partition(' ')
|
||||
address.update_attributes(phone: oldest_enterprise.phone) if oldest_enterprise.phone.present?
|
||||
address.update_attributes(firstname: first, lastname: last) if first.present? && last.present?
|
||||
account_invoice.order.update_attributes(bill_address: address, ship_address: address)
|
||||
|
||||
@@ -12,7 +12,6 @@ Spree::BaseMailer.class_eval do
|
||||
'test@example.com'
|
||||
end
|
||||
|
||||
|
||||
def roadie_options
|
||||
# This lets us specify assets using relative paths in email templates
|
||||
super.merge(url_options: {host: URI(spree.root_url).host })
|
||||
|
||||
@@ -3,8 +3,6 @@ class Cart < ActiveRecord::Base
|
||||
belongs_to :user, :class_name => Spree.user_class
|
||||
|
||||
def add_variant variant_id, quantity, distributor, order_cycle, currency
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
|
||||
order = create_or_find_order_for_distributor distributor, order_cycle, currency
|
||||
|
||||
@populator = Spree::OrderPopulator.new(order, currency)
|
||||
|
||||
@@ -61,7 +61,6 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
relatives
|
||||
end
|
||||
|
||||
|
||||
def permissions_list=(perms)
|
||||
if perms.nil?
|
||||
permissions.destroy_all
|
||||
|
||||
@@ -36,8 +36,8 @@ class ModelSet
|
||||
end
|
||||
|
||||
def save
|
||||
collection_to_delete.each &:destroy
|
||||
collection_to_keep.all? &:save
|
||||
collection_to_delete.each(&:destroy)
|
||||
collection_to_keep.all?(&:save)
|
||||
end
|
||||
|
||||
def collection_to_delete
|
||||
|
||||
@@ -4,7 +4,7 @@ module OpenFoodNetwork
|
||||
attr_accessible :preferred_per_kg
|
||||
|
||||
def self.description
|
||||
"Weight (per kg)"
|
||||
I18n.t('spree.weight')
|
||||
end
|
||||
|
||||
def compute(object)
|
||||
|
||||
@@ -115,7 +115,6 @@ class OrderCycle < ActiveRecord::Base
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def clone!
|
||||
oc = self.dup
|
||||
oc.name = "COPY OF #{oc.name}"
|
||||
@@ -250,6 +249,13 @@ class OrderCycle < ActiveRecord::Base
|
||||
OpenFoodNetwork::ProductsCache.order_cycle_changed self
|
||||
end
|
||||
|
||||
def items_bought_by_user(user, distributor)
|
||||
# The Spree::Order.complete scope only checks for completed_at date, does not ensure state is "complete"
|
||||
orders = Spree::Order.complete.where(state: "complete", user_id: user, distributor_id: distributor, order_cycle_id: self)
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
items = Spree::LineItem.joins(:order).merge(orders)
|
||||
items.each { |li| scoper.scope(li.variant) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
464
app/models/product_importer.rb
Normal file
464
app/models/product_importer.rb
Normal file
@@ -0,0 +1,464 @@
|
||||
require 'roo'
|
||||
|
||||
class ProductImporter
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :total_supplier_products
|
||||
|
||||
def initialize(file, editable_enterprises, import_settings={})
|
||||
if file.is_a?(File)
|
||||
@file = file
|
||||
@sheet = open_spreadsheet
|
||||
@entries = []
|
||||
@valid_entries = {}
|
||||
@invalid_entries = {}
|
||||
|
||||
@products_to_create = {}
|
||||
@variants_to_create = {}
|
||||
@variants_to_update = {}
|
||||
|
||||
@products_created = 0
|
||||
@variants_created = 0
|
||||
@variants_updated = 0
|
||||
|
||||
@import_settings = import_settings
|
||||
@editable_enterprises = {}
|
||||
editable_enterprises.map { |e| @editable_enterprises[e.name] = e.id }
|
||||
|
||||
@total_supplier_products = 0
|
||||
@products_to_reset = {}
|
||||
@updated_ids = []
|
||||
|
||||
init_product_importer if @sheet
|
||||
else
|
||||
self.errors.add(:importer, 'error: no file uploaded')
|
||||
end
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false #ActiveModel, not ActiveRecord
|
||||
end
|
||||
|
||||
def has_valid_entries?
|
||||
valid_count and valid_count > 0
|
||||
end
|
||||
|
||||
def item_count
|
||||
@sheet ? @sheet.last_row - 1 : 0
|
||||
end
|
||||
|
||||
def products_to_reset
|
||||
# Return indexed data about existing product count, reset count, and updates count per supplier
|
||||
@products_to_reset.each do |supplier_id, values|
|
||||
values[:updates_count] = 0 if values[:updates_count].blank?
|
||||
|
||||
if values[:updates_count] and values[:existing_products]
|
||||
@products_to_reset[supplier_id][:reset_count] = values[:existing_products] - values[:updates_count]
|
||||
end
|
||||
end
|
||||
@products_to_reset
|
||||
end
|
||||
|
||||
def valid_count
|
||||
@valid_entries.count
|
||||
end
|
||||
|
||||
def invalid_count
|
||||
@invalid_entries.count
|
||||
end
|
||||
|
||||
def products_create_count
|
||||
@products_to_create.count + @variants_to_create.count
|
||||
end
|
||||
|
||||
def products_update_count
|
||||
@variants_to_update.count
|
||||
end
|
||||
|
||||
def suppliers_index
|
||||
index = @suppliers_index || build_suppliers_index
|
||||
index.sort_by{ |k,v| v.to_i }.reverse.to_h
|
||||
end
|
||||
|
||||
def all_entries
|
||||
invalid_entries.merge(products_to_create).merge(products_to_update).sort.to_h
|
||||
end
|
||||
|
||||
def invalid_entries
|
||||
@invalid_entries
|
||||
end
|
||||
|
||||
def products_to_create
|
||||
@products_to_create.merge(@variants_to_create)
|
||||
end
|
||||
|
||||
def products_to_update
|
||||
@variants_to_update
|
||||
end
|
||||
|
||||
def products_created_count
|
||||
@products_created + @variants_created
|
||||
end
|
||||
|
||||
def products_updated_count
|
||||
@variants_updated
|
||||
end
|
||||
|
||||
def products_reset_count
|
||||
@products_reset_count || 0
|
||||
end
|
||||
|
||||
def total_saved_count
|
||||
@products_created + @variants_created + @variants_updated
|
||||
end
|
||||
|
||||
def save_all
|
||||
save_all_valid
|
||||
delete_uploaded_file
|
||||
end
|
||||
|
||||
def permission_by_name?(supplier_name)
|
||||
@editable_enterprises.has_key?(supplier_name)
|
||||
end
|
||||
|
||||
def permission_by_id?(supplier_id)
|
||||
@editable_enterprises.has_value?(Integer(supplier_id))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_product_importer
|
||||
build_entries
|
||||
build_categories_index
|
||||
build_suppliers_index
|
||||
validate_all
|
||||
end
|
||||
|
||||
def open_spreadsheet
|
||||
if accepted_mimetype
|
||||
Roo::Spreadsheet.open(@file, extension: accepted_mimetype)
|
||||
else
|
||||
self.errors.add(:importer, 'could not process file: invalid filetype')
|
||||
delete_uploaded_file
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def accepted_mimetype
|
||||
File.extname(@file.path).in?('.csv', '.xls', '.xlsx', '.ods') ? @file.path.split('.').last.to_sym : false
|
||||
end
|
||||
|
||||
def headers
|
||||
@sheet.row(1)
|
||||
end
|
||||
|
||||
def rows
|
||||
return [] unless @sheet and @sheet.last_row
|
||||
(2..@sheet.last_row).map do |i|
|
||||
@sheet.row(i)
|
||||
end
|
||||
end
|
||||
|
||||
def build_entries
|
||||
rows.each_with_index do |row, i|
|
||||
row_data = Hash[[headers, row].transpose]
|
||||
entry = SpreadsheetEntry.new(row_data)
|
||||
entry.line_number = i+2
|
||||
@entries.push entry
|
||||
end
|
||||
@entries
|
||||
end
|
||||
|
||||
def validate_all
|
||||
@entries.each do |entry|
|
||||
supplier_validation(entry)
|
||||
category_validation(entry)
|
||||
set_update_status(entry)
|
||||
|
||||
mark_as_valid(entry) unless entry_invalid?(entry.line_number)
|
||||
end
|
||||
|
||||
count_existing_products
|
||||
delete_uploaded_file if item_count.zero? or valid_count.zero?
|
||||
end
|
||||
|
||||
def count_existing_products
|
||||
@suppliers_index.each do |supplier_name, supplier_id|
|
||||
if supplier_id and permission_by_id?(supplier_id)
|
||||
products_count = Spree::Variant.joins(:product).
|
||||
where('spree_products.supplier_id IN (?)
|
||||
AND spree_variants.is_master = false
|
||||
AND spree_variants.deleted_at IS NULL', supplier_id).
|
||||
count
|
||||
|
||||
if @products_to_reset[supplier_id]
|
||||
@products_to_reset[supplier_id][:existing_products] = products_count
|
||||
else
|
||||
@products_to_reset[supplier_id] = {existing_products: products_count}
|
||||
end
|
||||
|
||||
@total_supplier_products += products_count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def entry_invalid?(line_number)
|
||||
!!@invalid_entries[line_number]
|
||||
end
|
||||
|
||||
def supplier_validation(entry)
|
||||
supplier_name = entry.supplier
|
||||
|
||||
if supplier_name.blank?
|
||||
mark_as_invalid(entry, attribute: "supplier", error: "can't be blank")
|
||||
return
|
||||
end
|
||||
|
||||
unless supplier_exists?(supplier_name)
|
||||
mark_as_invalid(entry, attribute: "supplier", error: "\"#{supplier_name}\" not found in database")
|
||||
return
|
||||
end
|
||||
|
||||
unless permission_by_name?(supplier_name)
|
||||
mark_as_invalid(entry, attribute: "supplier", error: "\"#{supplier_name}\": you do not have permission to manage products for this enterprise")
|
||||
return
|
||||
end
|
||||
|
||||
entry.supplier_id = @suppliers_index[supplier_name]
|
||||
end
|
||||
|
||||
def supplier_exists?(supplier_name)
|
||||
@suppliers_index[supplier_name]
|
||||
end
|
||||
|
||||
def category_validation(entry)
|
||||
category_name = entry.category
|
||||
|
||||
if category_name.blank?
|
||||
mark_as_invalid(entry, attribute: "category", error: "can't be blank")
|
||||
return
|
||||
end
|
||||
|
||||
if category_exists?(category_name)
|
||||
entry.primary_taxon_id = @categories_index[category_name]
|
||||
else
|
||||
mark_as_invalid(entry, attribute: "category", error: "\"#{category_name}\" not found in database")
|
||||
end
|
||||
end
|
||||
|
||||
def category_exists?(category_name)
|
||||
@categories_index[category_name]
|
||||
end
|
||||
|
||||
def mark_as_valid(entry)
|
||||
@valid_entries[entry.line_number] = entry
|
||||
end
|
||||
|
||||
def mark_as_invalid(entry, options={})
|
||||
entry.errors.add(options[:attribute], options[:error]) if options[:attribute] and options[:error]
|
||||
entry.product_validations = options[:product_validations] if options[:product_validations]
|
||||
|
||||
@invalid_entries[entry.line_number] = entry
|
||||
end
|
||||
|
||||
# Minimise db queries by getting a list of suppliers to look
|
||||
# up, instead of doing a query for each entry in the spreadsheet
|
||||
def build_suppliers_index
|
||||
@suppliers_index = {}
|
||||
@entries.each do |entry|
|
||||
supplier_name = entry.supplier
|
||||
supplier_id = @suppliers_index[supplier_name] ||
|
||||
Enterprise.find_by_name(supplier_name, :select => 'id, name').try(:id)
|
||||
@suppliers_index[supplier_name] = supplier_id
|
||||
end
|
||||
@suppliers_index
|
||||
end
|
||||
|
||||
def build_categories_index
|
||||
@categories_index = {}
|
||||
@entries.each do |entry|
|
||||
category_name = entry.category
|
||||
category_id = @categories_index[category_name] ||
|
||||
Spree::Taxon.find_by_name(category_name, :select => 'id, name').try(:id)
|
||||
@categories_index[category_name] = category_id
|
||||
end
|
||||
@categories_index
|
||||
end
|
||||
|
||||
def save_all_valid
|
||||
already_created = {}
|
||||
@products_to_create.each do |line_number, entry|
|
||||
# If we've already added a new product with these attributes
|
||||
# from this spreadsheet, mark this entry as a new variant with
|
||||
# the new product id, as this is a now variant of that product...
|
||||
if already_created[entry.supplier_id] and already_created[entry.supplier_id][entry.name]
|
||||
product_id = already_created[entry.supplier_id][entry.name]
|
||||
mark_as_new_variant(entry, product_id)
|
||||
next
|
||||
end
|
||||
|
||||
product = Spree::Product.new()
|
||||
product.assign_attributes(entry.attributes.except('id'))
|
||||
assign_defaults(product, entry.attributes)
|
||||
if product.save
|
||||
ensure_variant_updated(product, entry)
|
||||
@products_created += 1
|
||||
@updated_ids.push product.variants.first.id
|
||||
else
|
||||
self.errors.add("Line #{line_number}:", product.errors.full_messages) #TODO: change
|
||||
end
|
||||
|
||||
already_created[entry.supplier_id] = {entry.name => product.id}
|
||||
end
|
||||
|
||||
@variants_to_update.each do |line_number, entry|
|
||||
variant = entry.product_object
|
||||
assign_defaults(variant, entry.attributes)
|
||||
if variant.valid? and variant.save
|
||||
@variants_updated += 1
|
||||
@updated_ids.push variant.id
|
||||
else
|
||||
self.errors.add("Line #{line_number}:", variant.errors.full_messages) #TODO: change
|
||||
end
|
||||
end
|
||||
|
||||
@variants_to_create.each do |line_number, entry|
|
||||
new_variant = entry.product_object
|
||||
assign_defaults(new_variant, entry.attributes)
|
||||
if new_variant.valid? and new_variant.save
|
||||
@variants_created += 1
|
||||
@updated_ids.push new_variant.id
|
||||
else
|
||||
self.errors.add("Line #{line_number}:", new_variant.errors.full_messages)
|
||||
end
|
||||
end
|
||||
|
||||
self.errors.add(:importer, "did not save any products successfully") if total_saved_count.zero?
|
||||
|
||||
reset_absent_products
|
||||
total_saved_count
|
||||
end
|
||||
|
||||
def reset_absent_products
|
||||
return if total_saved_count.zero?
|
||||
|
||||
enterprises_to_reset = []
|
||||
@import_settings.each do |enterprise_id, settings|
|
||||
enterprises_to_reset.push enterprise_id if settings['reset_all_absent'] and permission_by_id?(enterprise_id)
|
||||
end
|
||||
|
||||
unless enterprises_to_reset.empty? or @updated_ids.empty?
|
||||
# For selected enterprises; set stock to zero for all products
|
||||
# that were not present in the uploaded spreadsheet
|
||||
@products_reset_count = Spree::Variant.joins(:product).
|
||||
where('spree_products.supplier_id IN (?)
|
||||
AND spree_variants.id NOT IN (?)
|
||||
AND spree_variants.is_master = false
|
||||
AND spree_variants.deleted_at IS NULL', enterprises_to_reset, @updated_ids).
|
||||
update_all(count_on_hand: 0)
|
||||
end
|
||||
end
|
||||
|
||||
def assign_defaults(object, entry)
|
||||
@import_settings[entry['supplier_id'].to_s]['defaults'].each do |attribute, setting|
|
||||
case setting['mode']
|
||||
when 'overwrite_all'
|
||||
object.assign_attributes(attribute => setting['value'])
|
||||
when 'overwrite_empty'
|
||||
if object.send(attribute).blank? or (attribute == 'on_hand' and entry['on_hand_nil'])
|
||||
object.assign_attributes(attribute => setting['value'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_variant_updated(product, entry)
|
||||
# Ensure display_name and on_demand are copied to new product's variant
|
||||
if entry.display_name || entry.on_demand
|
||||
variant = product.variants.first
|
||||
variant.display_name = entry.display_name if entry.display_name
|
||||
variant.on_demand = entry.on_demand if entry.on_demand
|
||||
variant.save
|
||||
end
|
||||
end
|
||||
|
||||
def set_update_status(entry)
|
||||
# Find product with matching supplier and name
|
||||
match = Spree::Product.where(supplier_id: entry.supplier_id, name: entry.name, deleted_at: nil).first
|
||||
|
||||
# If no matching product was found, create a new product
|
||||
if match.nil?
|
||||
mark_as_new_product(entry)
|
||||
return
|
||||
end
|
||||
|
||||
# Otherwise, if a variant exists with matching display_name and unit_value, update it
|
||||
match.variants.each do |existing_variant|
|
||||
if existing_variant.display_name == entry.display_name && existing_variant.unit_value == Float(entry.unit_value)
|
||||
mark_as_existing_variant(entry, existing_variant)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Otherwise, a variant with sufficiently matching attributes doesn't exist; create a new one
|
||||
mark_as_new_variant(entry, match.id)
|
||||
end
|
||||
|
||||
def mark_as_new_product(entry)
|
||||
new_product = Spree::Product.new()
|
||||
new_product.assign_attributes(entry.attributes.except('id'))
|
||||
if new_product.valid?
|
||||
@products_to_create[entry.line_number] = entry unless entry_invalid?(entry.line_number)
|
||||
else
|
||||
mark_as_invalid(entry, product_validations: new_product.errors)
|
||||
end
|
||||
end
|
||||
|
||||
def mark_as_existing_variant(entry, existing_variant)
|
||||
existing_variant.assign_attributes(entry.attributes.except('id', 'product_id'))
|
||||
check_on_hand_nil(entry, existing_variant)
|
||||
if existing_variant.valid?
|
||||
entry.product_object = existing_variant
|
||||
@variants_to_update[entry.line_number] = entry unless entry_invalid?(entry.line_number)
|
||||
updates_count_per_supplier(entry.supplier_id) unless entry_invalid?(entry.line_number)
|
||||
else
|
||||
mark_as_invalid(entry, product_validations: existing_variant.errors)
|
||||
end
|
||||
end
|
||||
|
||||
def mark_as_new_variant(entry, product_id)
|
||||
new_variant = Spree::Variant.new(entry.attributes.except('id', 'product_id'))
|
||||
new_variant.product_id = product_id
|
||||
check_on_hand_nil(entry, new_variant)
|
||||
if new_variant.valid?
|
||||
entry.product_object = new_variant
|
||||
@variants_to_create[entry.line_number] = entry unless entry_invalid?(entry.line_number)
|
||||
else
|
||||
mark_as_invalid(entry, product_validations: new_variant.errors)
|
||||
end
|
||||
end
|
||||
|
||||
def updates_count_per_supplier(supplier_id)
|
||||
if @products_to_reset[supplier_id] and @products_to_reset[supplier_id][:updates_count]
|
||||
@products_to_reset[supplier_id][:updates_count] += 1
|
||||
else
|
||||
@products_to_reset[supplier_id] = {updates_count: 1}
|
||||
end
|
||||
end
|
||||
|
||||
def check_on_hand_nil(entry, variant)
|
||||
if entry.on_hand.blank?
|
||||
variant.on_hand = 0
|
||||
entry.on_hand_nil = true
|
||||
end
|
||||
end
|
||||
|
||||
def delete_uploaded_file
|
||||
# Only delete if file is in '/tmp/product_import' directory
|
||||
if @file.path == Rails.root.join('tmp', 'product_import').to_s
|
||||
File.delete(@file)
|
||||
end
|
||||
end
|
||||
end
|
||||
68
app/models/spreadsheet_entry.rb
Normal file
68
app/models/spreadsheet_entry.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# Class for defining spreadsheet entry objects for use in ProductImporter
|
||||
class SpreadsheetEntry
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_accessor :line_number, :valid, :product_object, :product_validations, :save_type, :on_hand_nil
|
||||
|
||||
attr_accessor :id, :product_id, :supplier, :supplier_id, :name, :display_name, :sku,
|
||||
:unit_value, :unit_description, :variant_unit, :variant_unit_scale, :variant_unit_name,
|
||||
:display_as, :category, :primary_taxon_id, :price, :on_hand, :on_demand,
|
||||
:tax_category_id, :shipping_category_id, :description
|
||||
|
||||
def initialize(attrs)
|
||||
@product_validations = {}
|
||||
|
||||
attrs.each do |k, v|
|
||||
if self.respond_to?("#{k}=")
|
||||
send("#{k}=", v) unless non_product_attributes.include?(k)
|
||||
else
|
||||
# Trying to assign unknown attribute. Record this and give feedback or just ignore silently?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false #ActiveModel
|
||||
end
|
||||
|
||||
def has_errors?
|
||||
self.errors.count > 0 or @product_validations.count > 0
|
||||
end
|
||||
|
||||
def attributes
|
||||
attrs = {}
|
||||
self.instance_variables.each do |var|
|
||||
attrs[var.to_s.delete("@")] = self.instance_variable_get(var)
|
||||
end
|
||||
attrs.except(*non_product_attributes)
|
||||
end
|
||||
|
||||
def displayable_attributes
|
||||
# Modified attributes list for displaying in user feedback
|
||||
attrs = {}
|
||||
self.instance_variables.each do |var|
|
||||
attrs[var.to_s.delete("@")] = self.instance_variable_get(var)
|
||||
end
|
||||
attrs.except(*non_product_attributes, *non_display_attributes)
|
||||
end
|
||||
|
||||
def invalid_attributes
|
||||
invalid_attrs = {}
|
||||
@product_validations.messages.merge(self.errors.messages).each do |attr, message|
|
||||
invalid_attrs[attr.to_s] = "#{attr.to_s.capitalize} #{message.first}"
|
||||
end
|
||||
invalid_attrs.except(*non_product_attributes, *non_display_attributes)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def non_display_attributes
|
||||
['id', 'product_id', 'variant_id', 'supplier_id', 'primary_taxon', 'primary_taxon_id', 'category_id', 'shipping_category_id', 'tax_category_id']
|
||||
end
|
||||
|
||||
def non_product_attributes
|
||||
['line_number', 'valid', 'errors', 'product_object', 'product_validations', 'save_type', 'on_hand_nil']
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,7 @@ class AbilityDecorator
|
||||
# All abilites are allocated from this initialiser, currently in 5 chunks.
|
||||
# Spree also defines other abilities.
|
||||
def initialize(user)
|
||||
add_shopping_abilities user
|
||||
add_base_abilities user if is_new_user? user
|
||||
add_enterprise_management_abilities user if can_manage_enterprises? user
|
||||
add_group_management_abilities user if can_manage_groups? user
|
||||
@@ -50,6 +51,16 @@ class AbilityDecorator
|
||||
can_manage_enterprises? user
|
||||
end
|
||||
|
||||
def add_shopping_abilities(user)
|
||||
can [:destroy], Spree::LineItem do |item|
|
||||
user == item.order.user &&
|
||||
item.order.changes_allowed?
|
||||
end
|
||||
can [:cancel], Spree::Order do |order|
|
||||
order.user == user
|
||||
end
|
||||
end
|
||||
|
||||
# New users can create an enterprise, and gain other permissions from doing this.
|
||||
def add_base_abilities(user)
|
||||
can [:create], Enterprise
|
||||
@@ -155,6 +166,8 @@ class AbilityDecorator
|
||||
can [:admin, :index, :read, :search], Spree::Taxon
|
||||
can [:admin, :index, :read, :create, :edit], Spree::Classification
|
||||
|
||||
can [:admin, :index, :import, :save], ProductImporter
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
|
||||
end
|
||||
@@ -173,7 +186,7 @@ class AbilityDecorator
|
||||
def add_order_management_abilities(user)
|
||||
# Enterprise User can only access orders that they are a distributor for
|
||||
can [:index, :create], Spree::Order
|
||||
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
|
||||
can [:read, :update, :fire, :resend, :invoice, :print, :print_ticket], Spree::Order do |order|
|
||||
# We allow editing orders with a nil distributor as this state occurs
|
||||
# during the order creation process from the admin backend
|
||||
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
|
||||
@@ -182,6 +195,7 @@ class AbilityDecorator
|
||||
can [:admin , :for_line_items], Enterprise
|
||||
can [:admin, :index, :create], Spree::LineItem
|
||||
can [:destroy, :update], Spree::LineItem do |item|
|
||||
order = item.order
|
||||
user.admin? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
|
||||
end
|
||||
|
||||
@@ -226,7 +240,6 @@ class AbilityDecorator
|
||||
can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
|
||||
end
|
||||
|
||||
|
||||
def add_relationship_management_abilities(user)
|
||||
can [:admin, :index, :create], EnterpriseRelationship
|
||||
can [:destroy], EnterpriseRelationship do |enterprise_relationship|
|
||||
|
||||
@@ -34,8 +34,31 @@ module Spree
|
||||
included_tax > 0
|
||||
end
|
||||
|
||||
def display_included_tax
|
||||
Spree::Money.new(included_tax, { :currency => currency })
|
||||
# @return [Array<Spree::TaxRate>]
|
||||
def tax_rates
|
||||
case originator
|
||||
when Spree::TaxRate
|
||||
[originator]
|
||||
when EnterpriseFee
|
||||
case source
|
||||
when Spree::LineItem
|
||||
tax_category = originator.inherits_tax_category? ? source.product.tax_category : originator.tax_category
|
||||
return tax_category ? tax_category.tax_rates.match(source.order) : []
|
||||
when Spree::Order
|
||||
return originator.tax_category ? originator.tax_category.tax_rates.match(source) : []
|
||||
end
|
||||
else
|
||||
find_closest_tax_rates_from_included_tax
|
||||
end
|
||||
end
|
||||
|
||||
# shipping fees and adjustments created from the admin panel have
|
||||
# taxes set at creation in the included_tax field without relation
|
||||
# to the corresponding TaxRate, so we look for the closest one
|
||||
def find_closest_tax_rates_from_included_tax
|
||||
approximation = (included_tax / (amount - included_tax))
|
||||
return [] if approximation.infinite? or approximation.zero?
|
||||
[Spree::TaxRate.order("ABS(amount - #{approximation})").first]
|
||||
end
|
||||
|
||||
def self.without_callbacks
|
||||
|
||||
@@ -6,7 +6,6 @@ Spree::AppConfiguration.class_eval do
|
||||
|
||||
# Terms of Service Preferences
|
||||
preference :enterprises_require_tos, :boolean, default: false
|
||||
preference :enterprise_tos_link, :string, default: "/Terms-of-service.pdf"
|
||||
|
||||
# Tax Preferences
|
||||
preference :products_require_tax_category, :boolean, default: false
|
||||
|
||||
@@ -47,7 +47,6 @@ Spree::LineItem.class_eval do
|
||||
update_attributes!(quantity: variant.on_hand) if quantity > variant.on_hand
|
||||
end
|
||||
|
||||
|
||||
def has_tax?
|
||||
adjustments.included_tax.any?
|
||||
end
|
||||
@@ -94,8 +93,41 @@ Spree::LineItem.class_eval do
|
||||
(final_weight_volume || 0) / quantity
|
||||
end
|
||||
|
||||
# MONKEYPATCH of Spree method
|
||||
# Enables scoping of variant to hub/shop, stock drawn down from inventory
|
||||
def update_inventory
|
||||
return true unless order.completed?
|
||||
|
||||
scoper.scope(variant) # this line added
|
||||
|
||||
if new_record?
|
||||
Spree::InventoryUnit.increase(order, variant, quantity)
|
||||
elsif old_quantity = self.changed_attributes['quantity']
|
||||
if old_quantity < quantity
|
||||
Spree::InventoryUnit.increase(order, variant, (quantity - old_quantity))
|
||||
elsif old_quantity > quantity
|
||||
Spree::InventoryUnit.decrease(order, variant, (old_quantity - quantity))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# MONKEYPATCH of Spree method
|
||||
# Enables scoping of variant to hub/shop, stock replaced to inventory
|
||||
def remove_inventory
|
||||
return true unless order.completed?
|
||||
|
||||
scoper.scope(variant) # this line added
|
||||
|
||||
Spree::InventoryUnit.decrease(order, variant, quantity)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scoper
|
||||
return @scoper unless @scoper.nil?
|
||||
@scoper = OpenFoodNetwork::ScopeVariantToHub.new(order.distributor)
|
||||
end
|
||||
|
||||
def calculate_final_weight_volume
|
||||
if final_weight_volume.present? && quantity_was > 0
|
||||
self.final_weight_volume = final_weight_volume * quantity / quantity_was
|
||||
|
||||
@@ -7,7 +7,7 @@ module Spree
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each &:refresh_products_cache
|
||||
products(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,13 +7,13 @@ module Spree
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
variants(:reload).each &: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
|
||||
vs.each(&:refresh_products_cache)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ end
|
||||
|
||||
Spree::Order.class_eval do
|
||||
belongs_to :order_cycle
|
||||
belongs_to :distributor, :class_name => 'Enterprise'
|
||||
belongs_to :distributor, class_name: 'Enterprise'
|
||||
belongs_to :cart
|
||||
belongs_to :customer
|
||||
|
||||
@@ -21,6 +21,9 @@ Spree::Order.class_eval do
|
||||
before_validation :associate_customer, unless: :customer_id?
|
||||
before_validation :ensure_customer, unless: :customer_is_valid?
|
||||
|
||||
before_save :update_shipping_fees!, if: :complete?
|
||||
before_save :update_payment_fees!, if: :complete?
|
||||
|
||||
checkout_flow do
|
||||
go_to_state :address
|
||||
go_to_state :delivery
|
||||
@@ -39,7 +42,6 @@ Spree::Order.class_eval do
|
||||
remove_transition :from => :delivery, :to => :confirm
|
||||
end
|
||||
|
||||
|
||||
# -- Scopes
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
@@ -88,7 +90,7 @@ Spree::Order.class_eval do
|
||||
unless self.order_cycle == order_cycle
|
||||
self.order_cycle = order_cycle
|
||||
self.distributor = nil unless order_cycle.nil? || order_cycle.has_distributor?(distributor)
|
||||
self.empty!
|
||||
empty!
|
||||
save!
|
||||
end
|
||||
end
|
||||
@@ -99,7 +101,6 @@ Spree::Order.class_eval do
|
||||
current_item.andand.destroy
|
||||
end
|
||||
|
||||
|
||||
# Overridden to support max_quantity
|
||||
def add_variant(variant, quantity = 1, max_quantity = nil, currency = nil)
|
||||
line_items(:reload)
|
||||
@@ -126,7 +127,7 @@ Spree::Order.class_eval do
|
||||
current_item.currency = currency unless currency.nil?
|
||||
current_item.save
|
||||
else
|
||||
current_item = Spree::LineItem.new(:quantity => quantity, max_quantity: max_quantity)
|
||||
current_item = Spree::LineItem.new(quantity: quantity, max_quantity: max_quantity)
|
||||
current_item.variant = variant
|
||||
if currency
|
||||
current_item.currency = currency unless currency.nil?
|
||||
@@ -134,17 +135,34 @@ Spree::Order.class_eval do
|
||||
else
|
||||
current_item.price = variant.price
|
||||
end
|
||||
self.line_items << current_item
|
||||
line_items << current_item
|
||||
end
|
||||
|
||||
self.reload
|
||||
reload
|
||||
current_item
|
||||
end
|
||||
|
||||
def cap_quantity_at_stock!
|
||||
line_items.each &:cap_quantity_at_stock!
|
||||
# After changing line items of a completed order
|
||||
def update_shipping_fees!
|
||||
shipments.each do |shipment|
|
||||
next if shipment.shipped?
|
||||
update_adjustment! shipment.adjustment
|
||||
shipment.save # updates included tax
|
||||
end
|
||||
end
|
||||
|
||||
# After changing line items of a completed order
|
||||
def update_payment_fees!
|
||||
payments.each do |payment|
|
||||
next if payment.completed?
|
||||
update_adjustment! payment.adjustment
|
||||
payment.save
|
||||
end
|
||||
end
|
||||
|
||||
def cap_quantity_at_stock!
|
||||
line_items.each(&:cap_quantity_at_stock!)
|
||||
end
|
||||
|
||||
def set_distributor!(distributor)
|
||||
self.distributor = distributor
|
||||
@@ -158,6 +176,10 @@ Spree::Order.class_eval do
|
||||
save!
|
||||
end
|
||||
|
||||
def distribution_set?
|
||||
distributor && order_cycle
|
||||
end
|
||||
|
||||
def update_distribution_charge!
|
||||
with_lock do
|
||||
EnterpriseFee.clear_all_adjustments_on_order self
|
||||
@@ -195,6 +217,12 @@ Spree::Order.class_eval do
|
||||
line_items.map(&:variant)
|
||||
end
|
||||
|
||||
# Show already bought line items of this order cycle
|
||||
def finalised_line_items
|
||||
return [] unless order_cycle && user && distributor
|
||||
order_cycle.items_bought_by_user(user, distributor)
|
||||
end
|
||||
|
||||
def admin_and_handling_total
|
||||
adjustments.eligible.where("originator_type = ? AND source_type != ?", 'EnterpriseFee', 'Spree::LineItem').sum(&:amount)
|
||||
end
|
||||
@@ -205,26 +233,26 @@ Spree::Order.class_eval do
|
||||
|
||||
# Does this order have shipments that can be shipped?
|
||||
def ready_to_ship?
|
||||
self.shipments.any?{|s| s.can_ship?}
|
||||
shipments.any?(&:can_ship?)
|
||||
end
|
||||
|
||||
# Ship all pending orders
|
||||
def ship
|
||||
self.shipments.each do |s|
|
||||
shipments.each do |s|
|
||||
s.ship if s.can_ship?
|
||||
end
|
||||
end
|
||||
|
||||
def shipping_tax
|
||||
adjustments(:reload).shipping.sum &:included_tax
|
||||
adjustments(:reload).shipping.sum(&:included_tax)
|
||||
end
|
||||
|
||||
def enterprise_fee_tax
|
||||
adjustments(:reload).enterprise_fee.sum &:included_tax
|
||||
adjustments(:reload).enterprise_fee.sum(&:included_tax)
|
||||
end
|
||||
|
||||
def total_tax
|
||||
(adjustments + price_adjustments).sum &:included_tax
|
||||
(adjustments + price_adjustments).sum(&:included_tax)
|
||||
end
|
||||
|
||||
def tax_adjustments
|
||||
@@ -234,12 +262,12 @@ Spree::Order.class_eval do
|
||||
|
||||
def tax_adjustment_totals
|
||||
tax_adjustments.each_with_object(Hash.new) do |adjustment, hash|
|
||||
if adjustment.originator_type == "Spree::TaxRate"
|
||||
tax_rate = adjustment.originator.amount
|
||||
else
|
||||
tax_rate = (adjustment.included_tax / (adjustment.amount - adjustment.included_tax)).round(2)
|
||||
end
|
||||
hash.update({tax_rate => adjustment.included_tax}) { |_tax_rate, amount1, amount2| amount1 + amount2 }
|
||||
tax_rates = adjustment.tax_rates
|
||||
tax_rates_hash = Hash[tax_rates.collect do |tax_rate|
|
||||
tax_amount = tax_rates.one? ? adjustment.included_tax : tax_rate.compute_tax(adjustment.amount)
|
||||
[tax_rate.amount, tax_amount]
|
||||
end]
|
||||
hash.update(tax_rates_hash) { |_tax_rate, amount1, amount2| amount1 + amount2 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -254,11 +282,12 @@ Spree::Order.class_eval do
|
||||
# Overrride of Spree method, that allows us to send separate confirmation emails to user and shop owners
|
||||
# And separately, to skip sending confirmation email completely for user invoice orders
|
||||
def deliver_order_confirmation_email
|
||||
unless account_invoice?
|
||||
Delayed::Job.enqueue ConfirmOrderJob.new(id)
|
||||
end
|
||||
Delayed::Job.enqueue ConfirmOrderJob.new(id) unless account_invoice?
|
||||
end
|
||||
|
||||
def changes_allowed?
|
||||
complete? && distributor.andand.allow_order_changes? && order_cycle.andand.open?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -272,9 +301,9 @@ Spree::Order.class_eval do
|
||||
self.ship_address = distributor.address.clone
|
||||
|
||||
if bill_address
|
||||
self.ship_address.firstname = bill_address.firstname
|
||||
self.ship_address.lastname = bill_address.lastname
|
||||
self.ship_address.phone = bill_address.phone
|
||||
ship_address.firstname = bill_address.firstname
|
||||
ship_address.lastname = bill_address.lastname
|
||||
ship_address.phone = bill_address.phone
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -286,11 +315,11 @@ Spree::Order.class_eval do
|
||||
end
|
||||
|
||||
def product_distribution_for(line_item)
|
||||
line_item.variant.product.product_distribution_for self.distributor
|
||||
line_item.variant.product.product_distribution_for distributor
|
||||
end
|
||||
|
||||
def require_customer?
|
||||
return true unless new_record? or state == 'cart'
|
||||
return true unless new_record? || state == 'cart'
|
||||
end
|
||||
|
||||
def customer_is_valid?
|
||||
@@ -313,4 +342,11 @@ Spree::Order.class_eval do
|
||||
self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user, name: customer_name, bill_address: bill_address.andand.clone, ship_address: ship_address.andand.clone)
|
||||
end
|
||||
end
|
||||
|
||||
def update_adjustment!(adjustment)
|
||||
locked = adjustment.locked
|
||||
adjustment.locked = false
|
||||
adjustment.update!(self)
|
||||
adjustment.locked = locked
|
||||
end
|
||||
end
|
||||
|
||||
@@ -82,7 +82,6 @@ Spree::OrderPopulator.class_eval do
|
||||
[quantity_to_add, max_quantity_to_add]
|
||||
end
|
||||
|
||||
|
||||
def cart_remove(variant_id)
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
@order.remove_variant(variant)
|
||||
|
||||
@@ -16,7 +16,7 @@ module Spree
|
||||
adjustment.save
|
||||
else
|
||||
payment_method.create_adjustment(adjustment_label, order, self, true)
|
||||
reload
|
||||
association(:adjustment).reload
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ module Spree
|
||||
|
||||
def enterprise
|
||||
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
|
||||
enterprise = Enterprise.find enterprise_id
|
||||
Enterprise.find(enterprise_id)
|
||||
end
|
||||
|
||||
def product_selection_from_inventory_only_regex
|
||||
|
||||
@@ -13,7 +13,6 @@ module Spree::Preferences
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def get_preference(key)
|
||||
if !has_preference?(key) && has_attachment?(key)
|
||||
send key
|
||||
@@ -32,7 +31,6 @@ module Spree::Preferences
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Spree's Configuration responds to preference methods via method_missing, but doesn't
|
||||
# override respond_to?, which consequently reports those methods as unavailable. Paperclip
|
||||
# errors if respond_to? isn't correct, so we override it here.
|
||||
@@ -41,7 +39,6 @@ module Spree::Preferences
|
||||
super(self.class.preference_getter_method(name), include_all) || super(method, include_all)
|
||||
end
|
||||
|
||||
|
||||
def has_attachment?(name)
|
||||
self.class.respond_to?(:attachment_definitions) &&
|
||||
self.class.attachment_definitions.keys.include?(name.to_sym)
|
||||
|
||||
@@ -216,7 +216,7 @@ Spree::Product.class_eval do
|
||||
if variant_unit_changed?
|
||||
option_types.delete self.class.all_variant_unit_option_types
|
||||
option_types << variant_unit_option_type if variant_unit.present?
|
||||
variants_including_master.each &:update_units
|
||||
variants_including_master.each(&:update_units)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Spree::ProductOptionType.class_eval do
|
||||
def remove_option_values
|
||||
self.product.variants_including_master.each do |variant|
|
||||
option_values = variant.option_values.where(option_type_id: self.option_type)
|
||||
variant.option_values.destroy *option_values
|
||||
variant.option_values.destroy(*option_values)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ module Spree
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product_properties(:reload).each &:refresh_products_cache
|
||||
product_properties(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,6 @@ Spree::ShippingMethod.class_eval do
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def available_to_order_with_distributor_check?(order, display_on=nil)
|
||||
available_to_order_without_distributor_check?(order, display_on) &&
|
||||
self.distributors.include?(order.distributor)
|
||||
|
||||
@@ -12,7 +12,8 @@ module Spree
|
||||
def adjust_with_included_tax(order)
|
||||
adjust_without_included_tax(order)
|
||||
|
||||
order.reload
|
||||
order.adjustments(:reload)
|
||||
order.line_items(:reload)
|
||||
(order.adjustments.tax + order.price_adjustments).each do |a|
|
||||
a.set_absolute_included_tax! a.amount
|
||||
end
|
||||
|
||||
@@ -37,13 +37,14 @@ Spree::Taxon.class_eval do
|
||||
#
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.distributed_taxons(which_taxons=:all)
|
||||
# TODO: Why can't we merge(Spree::Product.with_order_cycles_inner) here?
|
||||
taxons = Spree::Taxon.
|
||||
joins(products: {variants_including_master: {exchanges: :order_cycle}}).
|
||||
merge(Exchange.outgoing).
|
||||
select('spree_taxons.*, exchanges.receiver_id AS enterprise_id')
|
||||
ents_and_vars = ExchangeVariant.joins(exchange: :order_cycle).merge(Exchange.outgoing)
|
||||
.select("DISTINCT variant_id, receiver_id AS enterprise_id")
|
||||
|
||||
taxons = taxons.merge(OrderCycle.active) if which_taxons == :current
|
||||
ents_and_vars = ents_and_vars.merge(OrderCycle.active) if which_taxons == :current
|
||||
|
||||
taxons = Spree::Taxon
|
||||
.select("DISTINCT spree_taxons.id, ents_and_vars.enterprise_id").joins(products: :variants_including_master)
|
||||
.joins("INNER JOIN (#{ents_and_vars.to_sql}) AS ents_and_vars ON spree_variants.id = ents_and_vars.variant_id")
|
||||
|
||||
taxons.inject({}) do |ts, t|
|
||||
ts[t.enterprise_id.to_i] ||= Set.new
|
||||
@@ -56,6 +57,6 @@ Spree::Taxon.class_eval do
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each &:refresh_products_cache
|
||||
products(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,7 +70,6 @@ Spree::Variant.class_eval do
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def price_with_fees(distributor, order_cycle)
|
||||
price + fees_for(distributor, order_cycle)
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ class VariantOverride < ActiveRecord::Base
|
||||
|
||||
def self.indexed(hub)
|
||||
Hash[
|
||||
for_hubs(hub).map { |vo| [vo.variant, vo] }
|
||||
for_hubs(hub).preload(:variant).map { |vo| [vo.variant, vo] }
|
||||
]
|
||||
end
|
||||
|
||||
@@ -45,7 +45,6 @@ class VariantOverride < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def stock_overridden?
|
||||
count_on_hand.present?
|
||||
end
|
||||
@@ -58,6 +57,14 @@ class VariantOverride < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def increment_stock!(quantity)
|
||||
if stock_overridden?
|
||||
increment! :count_on_hand, quantity
|
||||
else
|
||||
Bugsnag.notify RuntimeError.new "Attempting to decrement stock level on a VariantOverride without a count_on_hand specified."
|
||||
end
|
||||
end
|
||||
|
||||
def default_stock?
|
||||
default_stock.present?
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user