mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-14 18:56:49 +00:00
Compare commits
288 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12fde5b9fc | ||
|
|
0da6275d41 | ||
|
|
52d2d1d23c | ||
|
|
4dfc020eaa | ||
|
|
8ef52f97e5 | ||
|
|
68a7f547b1 | ||
|
|
7a0f2d47a3 | ||
|
|
4affd01b7b | ||
|
|
0432c73f19 | ||
|
|
103902c006 | ||
|
|
d2933e35f1 | ||
|
|
0b5e341ae7 | ||
|
|
7d36c3b5aa | ||
|
|
d511763733 | ||
|
|
a059c11d0f | ||
|
|
cecc19ae1d | ||
|
|
d4311a848e | ||
|
|
d64573f7fd | ||
|
|
a9fe6ec1b5 | ||
|
|
f14bbc5ed9 | ||
|
|
5ba8efec2c | ||
|
|
3bf38b7c08 | ||
|
|
a591e0736f | ||
|
|
3080eb9dfd | ||
|
|
ed98a16eec | ||
|
|
7a924bd9ca | ||
|
|
7639e19184 | ||
|
|
71aff7e1d2 | ||
|
|
2506667bca | ||
|
|
f87a553230 | ||
|
|
3171b60d6f | ||
|
|
516398fbd6 | ||
|
|
4a1b74c136 | ||
|
|
b9edea7c0e | ||
|
|
b9053f9fd2 | ||
|
|
f297cff8c7 | ||
|
|
7563d38b4b | ||
|
|
83523a676f | ||
|
|
abcfb5ce8d | ||
|
|
8ee6d1c320 | ||
|
|
1bc19ad6a4 | ||
|
|
191b8064ed | ||
|
|
e28274db14 | ||
|
|
02dec1d6cd | ||
|
|
df04c837a5 | ||
|
|
7a6c085b63 | ||
|
|
a4317b70f4 | ||
|
|
cb90fb052d | ||
|
|
18e5f9ba64 | ||
|
|
e9a5b889de | ||
|
|
cc57b0c200 | ||
|
|
a8040e986d | ||
|
|
2269f824c0 | ||
|
|
c45a3c2303 | ||
|
|
1327d80446 | ||
|
|
43a3ac0a7b | ||
|
|
b948312de7 | ||
|
|
7063de4734 | ||
|
|
56c1a9cca2 | ||
|
|
9555bfcc93 | ||
|
|
e455a47135 | ||
|
|
e15e71a3a7 | ||
|
|
91d959b7d6 | ||
|
|
108f57a705 | ||
|
|
af42159e09 | ||
|
|
8e55c39ca0 | ||
|
|
6cfb060184 | ||
|
|
0cfb7269c8 | ||
|
|
cbec495620 | ||
|
|
55eea21bb0 | ||
|
|
f63c7cf54f | ||
|
|
13633e8bea | ||
|
|
093edb66d3 | ||
|
|
fb25ddd219 | ||
|
|
d54850f097 | ||
|
|
f8451a2511 | ||
|
|
1a88549954 | ||
|
|
f79182253a | ||
|
|
8cfd7c610b | ||
|
|
486b5e9edc | ||
|
|
8fe3abfd45 | ||
|
|
0e7dafea46 | ||
|
|
4c9cc7460a | ||
|
|
c9e3f58aed | ||
|
|
4136306abf | ||
|
|
9f1eaf0b66 | ||
|
|
4771612adb | ||
|
|
7ce3dfe365 | ||
|
|
6f13707b9d | ||
|
|
64cb104434 | ||
|
|
1606f9900f | ||
|
|
9f0e8b0b2e | ||
|
|
d796e96470 | ||
|
|
ba7f78ef60 | ||
|
|
988e146240 | ||
|
|
e6d9ec7bd7 | ||
|
|
3acc53a389 | ||
|
|
9c9fc999de | ||
|
|
d824c84ce6 | ||
|
|
1a301f3dbb | ||
|
|
fad4f3b22a | ||
|
|
6080c99850 | ||
|
|
2944acff8a | ||
|
|
2068a59b72 | ||
|
|
3063439ed1 | ||
|
|
6bed05c721 | ||
|
|
a3ee6674ea | ||
|
|
e59077e63e | ||
|
|
f9f8d85841 | ||
|
|
0042690e18 | ||
|
|
5f1111b52b | ||
|
|
25ded0d23c | ||
|
|
a5458150ca | ||
|
|
96eebbabf3 | ||
|
|
c58e6fa964 | ||
|
|
fbe8f5195c | ||
|
|
f587bbb7d5 | ||
|
|
2793693a7c | ||
|
|
07e2317369 | ||
|
|
042162eda8 | ||
|
|
795f13d73a | ||
|
|
f1814f1b67 | ||
|
|
4ab7b78cb8 | ||
|
|
11631c3a33 | ||
|
|
0212381362 | ||
|
|
e6ca6bacac | ||
|
|
59df45b8cf | ||
|
|
5892e85869 | ||
|
|
c59326743b | ||
|
|
cb3397fd1a | ||
|
|
2bd4de3e29 | ||
|
|
b2c5be775e | ||
|
|
d22212ccfa | ||
|
|
dd600cd163 | ||
|
|
f189ca8004 | ||
|
|
db7146014c | ||
|
|
c6af55d9ae | ||
|
|
0f588dbe0b | ||
|
|
9c14d8ff36 | ||
|
|
a104bf8efd | ||
|
|
a039ef13f4 | ||
|
|
7ad8951375 | ||
|
|
99e59595b4 | ||
|
|
3264355f12 | ||
|
|
7e3b6e2b5d | ||
|
|
a9598c5d97 | ||
|
|
3e5b7ebbf1 | ||
|
|
1a9c3007b0 | ||
|
|
2f7fd1482a | ||
|
|
0416521772 | ||
|
|
cfe3f72d0e | ||
|
|
00478cc57c | ||
|
|
d5e42ee1e5 | ||
|
|
9451f1b66d | ||
|
|
ff584f9be9 | ||
|
|
a589ba38da | ||
|
|
a4a2f98b6e | ||
|
|
f5ddbfbac3 | ||
|
|
a66a4c3edb | ||
|
|
8179252924 | ||
|
|
0bec492208 | ||
|
|
5cf50f0adf | ||
|
|
197fb36524 | ||
|
|
62e6f09d94 | ||
|
|
79b2460664 | ||
|
|
66f3656bb5 | ||
|
|
6b087adab8 | ||
|
|
3653b88da6 | ||
|
|
3223bf930d | ||
|
|
2b3bc6d1ff | ||
|
|
24d7672abb | ||
|
|
89628c27f3 | ||
|
|
9adbdc377d | ||
|
|
883cd81058 | ||
|
|
0d7d029255 | ||
|
|
ce31a059bf | ||
|
|
17bac20c65 | ||
|
|
066243057f | ||
|
|
ea40547fd7 | ||
|
|
0ebc6d4b1e | ||
|
|
62c2e4709a | ||
|
|
c5229dd763 | ||
|
|
99d4190814 | ||
|
|
f086c02e13 | ||
|
|
b726f961fc | ||
|
|
4c17cf0087 | ||
|
|
9fe143cf94 | ||
|
|
e2d783c385 | ||
|
|
81cb162884 | ||
|
|
c7b6dd2677 | ||
|
|
d1fd73fd2b | ||
|
|
b0221d264e | ||
|
|
0f64badc74 | ||
|
|
7ccfdc8d21 | ||
|
|
4799293996 | ||
|
|
58a93c27ae | ||
|
|
0202b59634 | ||
|
|
9f351607d1 | ||
|
|
c45e3c9cca | ||
|
|
71bf3f5f71 | ||
|
|
ef142de5f2 | ||
|
|
f64e8bf50e | ||
|
|
e8d68e3b89 | ||
|
|
1b29d474d0 | ||
|
|
baae58ecb6 | ||
|
|
6411871ecb | ||
|
|
22833ae79b | ||
|
|
ac20b0e7fb | ||
|
|
e9e6aa77d8 | ||
|
|
29e30c388e | ||
|
|
54a40fe79c | ||
|
|
7840118dea | ||
|
|
5e27bd6d6d | ||
|
|
d4512904ea | ||
|
|
52dc288470 | ||
|
|
302de04e73 | ||
|
|
41767936d6 | ||
|
|
0ccf30202e | ||
|
|
00f36e4686 | ||
|
|
4d77f30bc0 | ||
|
|
f38b1b95f0 | ||
|
|
97ef93b840 | ||
|
|
6db15a0a20 | ||
|
|
c8395a487a | ||
|
|
852adfd436 | ||
|
|
1e948735fb | ||
|
|
0ef4247914 | ||
|
|
2673a6efee | ||
|
|
6ffe7f1a99 | ||
|
|
6d1fb63a21 | ||
|
|
9bcd303f4f | ||
|
|
38c327dae0 | ||
|
|
51177b833e | ||
|
|
cc3368704a | ||
|
|
2d53fbbe8c | ||
|
|
3959f16d65 | ||
|
|
fb28826d92 | ||
|
|
beaa8ffa27 | ||
|
|
da6d035a1d | ||
|
|
5cb77c443b | ||
|
|
8d16f496f4 | ||
|
|
82b274e522 | ||
|
|
484cdd1e07 | ||
|
|
bb2e6324bd | ||
|
|
89056e13ed | ||
|
|
df0458743b | ||
|
|
ba1ad0a6dd | ||
|
|
4e7b397c5a | ||
|
|
842e191c5f | ||
|
|
1476859c83 | ||
|
|
c6fb7dafec | ||
|
|
80069731ed | ||
|
|
feaa928674 | ||
|
|
dfa3d40665 | ||
|
|
00c2b95a0e | ||
|
|
4a82a26830 | ||
|
|
f1831fc6bb | ||
|
|
4c91a5571a | ||
|
|
ccb7a305bc | ||
|
|
0c87afefce | ||
|
|
d546817f0a | ||
|
|
e0e833b2f3 | ||
|
|
c8d359a0da | ||
|
|
210757641c | ||
|
|
68bf599a1a | ||
|
|
a10966b66b | ||
|
|
08003f2003 | ||
|
|
7cc034c2bc | ||
|
|
15bcde36cb | ||
|
|
6e69960ee9 | ||
|
|
a7a03b04a9 | ||
|
|
5759dcee48 | ||
|
|
a9672011a5 | ||
|
|
808aa188ab | ||
|
|
ad7fc61228 | ||
|
|
3e32e5c16e | ||
|
|
f67a2120f4 | ||
|
|
a4ee562387 | ||
|
|
6df7ec9dbd | ||
|
|
0f7357166d | ||
|
|
50093c325a | ||
|
|
4288428c70 | ||
|
|
dc122a9450 | ||
|
|
ec1b5a7a92 | ||
|
|
df2306cf82 | ||
|
|
172a79acc7 | ||
|
|
6e51be095b | ||
|
|
1c7237869a |
10
.github/ISSUE_TEMPLATE/release-template.md
vendored
10
.github/ISSUE_TEMPLATE/release-template.md
vendored
@@ -1,10 +0,0 @@
|
||||
Draft: x
|
||||
|
||||
Target commit: x
|
||||
|
||||
Build: x
|
||||
|
||||
- [ ] Draft
|
||||
- [ ] Test
|
||||
- [ ] Publish
|
||||
- [ ] Deploy
|
||||
19
.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Release task
|
||||
about: Track the process of a new release
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Steps:
|
||||
|
||||
- [ ] Include translations
|
||||
- [ ] Draft: https://github.com/openfoodfoundation/openfoodnetwork/releases/new <!-- replace the URL -->
|
||||
- [ ] Test: https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2/branches/master <!-- replace the URL -->
|
||||
- [ ] Publish and notify #global-community
|
||||
- [ ] Deploy and notify #instance-managers
|
||||
- [ ] Nudge next release manager
|
||||
|
||||
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
|
||||
@@ -4,6 +4,7 @@
|
||||
#
|
||||
# The configuration is split into three files. Look into those files for more details.
|
||||
#
|
||||
require: rubocop-rails
|
||||
inherit_from:
|
||||
|
||||
# The automatically generated todo list to ignore all current violations.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#
|
||||
# This process probably doesn't need repeating. Otherwise there is plenty
|
||||
# of room for improvements and automation.
|
||||
Metrics/LineLength:
|
||||
Layout/LineLength:
|
||||
Max: 100
|
||||
Exclude:
|
||||
- Gemfile
|
||||
@@ -42,10 +42,8 @@ Metrics/LineLength:
|
||||
- app/controllers/application_controller.rb
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/spree/admin/adjustments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/base_controller_decorator.rb
|
||||
- app/controllers/spree/admin/orders_controller_decorator.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/reports_controller_decorator.rb
|
||||
- app/controllers/spree/credit_cards_controller.rb
|
||||
- app/controllers/spree/paypal_controller_decorator.rb
|
||||
- app/controllers/stripe/callbacks_controller.rb
|
||||
@@ -116,12 +114,10 @@ Metrics/LineLength:
|
||||
- lib/open_food_network/enterprise_issue_validator.rb
|
||||
- lib/open_food_network/group_buy_report.rb
|
||||
- lib/open_food_network/lettuce_share_report.rb
|
||||
- lib/open_food_network/order_and_distributor_report.rb
|
||||
- lib/open_food_network/order_cycle_form_applicator.rb
|
||||
- lib/open_food_network/order_cycle_management_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
|
||||
- lib/open_food_network/reports/line_items.rb
|
||||
- lib/open_food_network/sales_tax_report.rb
|
||||
- lib/open_food_network/variant_and_line_item_naming.rb
|
||||
- lib/open_food_network/xero_invoices_report.rb
|
||||
@@ -146,6 +142,7 @@ Metrics/LineLength:
|
||||
- spec/controllers/admin/subscriptions_controller_spec.rb
|
||||
- spec/controllers/admin/variant_overrides_controller_spec.rb
|
||||
- spec/controllers/api/base_controller_spec.rb
|
||||
- spec/controllers/api/exchange_products_controller_spec.rb
|
||||
- spec/controllers/api/logos_controller_spec.rb
|
||||
- spec/controllers/api/order_cycles_controller_spec.rb
|
||||
- spec/controllers/api/orders_controller_spec.rb
|
||||
@@ -307,6 +304,7 @@ Metrics/LineLength:
|
||||
- spec/serializers/api/admin/exchange_serializer_spec.rb
|
||||
- spec/serializers/api/admin/for_order_cycle/enterprise_serializer_spec.rb
|
||||
- spec/serializers/api/admin/for_order_cycle/supplied_product_serializer_spec.rb
|
||||
- spec/serializers/api/admin/order_cycle_serializer_spec.rb
|
||||
- spec/serializers/api/admin/subscription_customer_serializer_spec.rb
|
||||
- spec/serializers/api/admin/variant_override_serializer_spec.rb
|
||||
- spec/serializers/api/current_order_serializer_spec.rb
|
||||
@@ -314,11 +312,13 @@ Metrics/LineLength:
|
||||
- spec/serializers/api/order_serializer_spec.rb
|
||||
- spec/services/cart_service_spec.rb
|
||||
- spec/services/embedded_page_service_spec.rb
|
||||
- spec/services/exchange_products_renderer_spec.rb
|
||||
- spec/services/order_cycle_distributed_products_spec.rb
|
||||
- spec/services/order_cycle_distributed_variants_spec.rb
|
||||
- spec/services/order_cycle_form_spec.rb
|
||||
- spec/services/order_factory_spec.rb
|
||||
- spec/services/order_syncer_spec.rb
|
||||
- spec/services/permissions/order_spec.rb
|
||||
- spec/services/product_tag_rules_filterer_spec.rb
|
||||
- spec/services/products_renderer_spec.rb
|
||||
- spec/services/subscription_estimator_spec.rb
|
||||
@@ -364,11 +364,12 @@ Metrics/AbcSize:
|
||||
- app/controllers/spree/admin/image_settings_controller.rb
|
||||
- app/controllers/spree/admin/orders/customer_details_controller_decorator.rb
|
||||
- app/controllers/spree/admin/orders_controller_decorator.rb
|
||||
- app/controllers/spree/admin/overview_controller_decorator.rb
|
||||
- app/controllers/spree/admin/overview_controller.rb
|
||||
- app/controllers/spree/admin/payment_methods_controller.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/products_controller_decorator.rb
|
||||
- app/controllers/spree/admin/reports_controller_decorator.rb
|
||||
- app/controllers/spree/admin/reports_controller.rb
|
||||
- app/controllers/spree/admin/resource_controller.rb
|
||||
- app/controllers/spree/admin/search_controller_decorator.rb
|
||||
- app/controllers/spree/admin/taxons_controller.rb
|
||||
- app/controllers/spree/admin/users_controller.rb
|
||||
@@ -567,7 +568,8 @@ Metrics/MethodLength:
|
||||
- app/controllers/spree/admin/payment_methods_controller.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/products_controller_decorator.rb
|
||||
- app/controllers/spree/admin/reports_controller_decorator.rb
|
||||
- app/controllers/spree/admin/reports_controller.rb
|
||||
- app/controllers/spree/admin/resource_controller.rb
|
||||
- app/controllers/spree/admin/search_controller_decorator.rb
|
||||
- app/controllers/spree/admin/tax_categories_controller.rb
|
||||
- app/controllers/spree/admin/taxons_controller.rb
|
||||
@@ -646,7 +648,10 @@ Metrics/ClassLength:
|
||||
- app/controllers/admin/subscriptions_controller.rb
|
||||
- app/controllers/api/products_controller.rb
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/spree/admin/base_controller.rb
|
||||
- app/controllers/spree/admin/payment_methods_controller.rb
|
||||
- app/controllers/spree/admin/reports_controller.rb
|
||||
- app/controllers/spree/admin/resource_controller.rb
|
||||
- app/controllers/spree/admin/users_controller.rb
|
||||
- app/controllers/spree/orders_controller.rb
|
||||
- app/models/enterprise.rb
|
||||
@@ -702,6 +707,7 @@ Metrics/ModuleLength:
|
||||
- spec/models/spree/payment_spec.rb
|
||||
- spec/models/spree/product_spec.rb
|
||||
- spec/models/spree/variant_spec.rb
|
||||
- spec/services/permissions/order_spec.rb
|
||||
- spec/support/request/web_helper.rb
|
||||
|
||||
Metrics/ParameterLists:
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
# rubocop locally, the default configuration file `.rubocop.yml` loads
|
||||
# our "todo lists" to ignore all current violations.
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.2
|
||||
TargetRailsVersion: 3.2
|
||||
Exclude:
|
||||
- 'bin/**/*'
|
||||
@@ -41,7 +40,7 @@ Layout/MultilineMethodCallIndentation:
|
||||
Enabled: true
|
||||
EnforcedStyle: indented
|
||||
|
||||
Metrics/LineLength:
|
||||
Layout/LineLength:
|
||||
Max: 100
|
||||
|
||||
## TEMPORARY/CONTESTED SETTINGS
|
||||
|
||||
1201
.rubocop_todo.yml
1201
.rubocop_todo.yml
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
2.2.10
|
||||
2.3.7
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -6,16 +6,19 @@ RUN apt-get update && apt-get install -y curl git build-essential software-prope
|
||||
# Setup ENV variables
|
||||
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:$PATH
|
||||
ENV RBENV_ROOT /usr/local/src/rbenv
|
||||
ENV RUBY_VERSION 2.1.9
|
||||
ENV CONFIGURE_OPTS --disable-install-doc
|
||||
ENV BUNDLE_PATH /bundles
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY .ruby-version .
|
||||
|
||||
# Rbenv & Ruby part
|
||||
RUN git clone https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
||||
git clone https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
|
||||
${RBENV_ROOT}/plugins/ruby-build/install.sh && \
|
||||
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
|
||||
rbenv install $RUBY_VERSION && \
|
||||
rbenv global $RUBY_VERSION && \
|
||||
rbenv install $(cat .ruby-version) && \
|
||||
rbenv global $(cat .ruby-version) && \
|
||||
gem install bundler --version=1.17.2
|
||||
|
||||
# Postgres
|
||||
@@ -24,8 +27,4 @@ RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main'
|
||||
apt-get update && \
|
||||
apt-get install -yqq --no-install-recommends postgresql-client-9.5 libpq-dev
|
||||
|
||||
ENV BUNDLE_PATH /bundles
|
||||
|
||||
COPY . /usr/src/app/
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
18
Gemfile
18
Gemfile
@@ -1,9 +1,9 @@
|
||||
source 'https://rubygems.org'
|
||||
ruby "2.2.10"
|
||||
ruby "2.3.7"
|
||||
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
||||
|
||||
gem 'i18n', '~> 0.6.11'
|
||||
gem 'i18n-js', '~> 3.5.0'
|
||||
gem 'i18n-js', '~> 3.5.1'
|
||||
gem 'rails', '~> 3.2.22'
|
||||
gem 'rails-i18n', '~> 3.0.0'
|
||||
gem 'rails_safe_tasks', '~> 1.0'
|
||||
@@ -93,7 +93,7 @@ gem 'wkhtmltopdf-binary'
|
||||
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
gem 'roo', '~> 2.7.0'
|
||||
gem 'roo', '~> 2.8.2'
|
||||
gem 'roo-xls', '~> 1.1.0'
|
||||
|
||||
gem 'whenever', require: false
|
||||
@@ -106,10 +106,7 @@ group :assets do
|
||||
gem 'coffee-rails', '~> 3.2.1'
|
||||
gem 'compass-rails'
|
||||
|
||||
gem 'mini_racer', '0.1.15'
|
||||
# Previously we found that libv8 6.7.288.46.1 breakis the compilation of mini_racer.
|
||||
# Now we see that we need to set the version explicitly. Nothing else depends on libv8.
|
||||
gem 'libv8', '6.3.292.48.1'
|
||||
gem 'mini_racer', '0.2.4'
|
||||
|
||||
gem 'uglifier', '>= 1.0.3'
|
||||
|
||||
@@ -135,7 +132,7 @@ group :test, :development do
|
||||
# Pretty printed test output
|
||||
gem 'atomic'
|
||||
gem 'awesome_print'
|
||||
gem 'capybara', '>= 2.15.4'
|
||||
gem 'capybara', '>= 2.18.0' # 3.0 requires nokogiri 1.8
|
||||
gem 'database_cleaner', '0.7.1', require: false
|
||||
gem "factory_bot_rails", require: false
|
||||
gem 'fuubar', '~> 2.5.0'
|
||||
@@ -148,7 +145,7 @@ group :test, :development do
|
||||
gem 'shoulda-matchers'
|
||||
gem 'timecop'
|
||||
gem 'unicorn-rails'
|
||||
gem 'webdrivers', '3.8.1'
|
||||
gem 'webdrivers'
|
||||
end
|
||||
|
||||
group :test do
|
||||
@@ -163,7 +160,8 @@ group :development do
|
||||
gem 'debugger-linecache'
|
||||
gem "newrelic_rpm", "~> 3.0"
|
||||
gem 'pry-byebug', '>= 3.4.3'
|
||||
gem 'rubocop', '>= 0.49.1'
|
||||
gem 'rubocop'
|
||||
gem 'rubocop-rails'
|
||||
gem 'spring', '1.7.2'
|
||||
gem 'spring-commands-rspec'
|
||||
|
||||
|
||||
87
Gemfile.lock
87
Gemfile.lock
@@ -129,7 +129,7 @@ GEM
|
||||
activesupport (= 3.2.22.5)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activerecord-import (1.0.3)
|
||||
activerecord-import (1.0.4)
|
||||
activerecord (>= 3.2)
|
||||
activerecord-postgresql-adapter (0.0.1)
|
||||
pg
|
||||
@@ -178,8 +178,7 @@ GEM
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (>= 2.0, < 4.0)
|
||||
childprocess (0.9.0)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
childprocess (3.0.0)
|
||||
chronic (0.10.2)
|
||||
chunky_png (1.3.10)
|
||||
climate_control (0.2.0)
|
||||
@@ -213,7 +212,6 @@ GEM
|
||||
sass-rails (< 5.1)
|
||||
sprockets (< 4.0)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
css_parser (1.7.0)
|
||||
@@ -225,7 +223,7 @@ GEM
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
fog (~> 1.0)
|
||||
rails (>= 3.2.0, < 5.0)
|
||||
ddtrace (0.28.0)
|
||||
ddtrace (0.31.0)
|
||||
msgpack
|
||||
debugger-linecache (1.2.0)
|
||||
deface (1.0.2)
|
||||
@@ -256,17 +254,17 @@ GEM
|
||||
dry-inflector (0.1.2)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.2.7)
|
||||
excon (0.62.0)
|
||||
excon (0.71.1)
|
||||
execjs (2.7.0)
|
||||
factory_bot (4.10.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.10.0)
|
||||
factory_bot (~> 4.10.0)
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.15.4)
|
||||
faraday (0.17.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffaker (1.22.1)
|
||||
ffi (1.10.0)
|
||||
ffi (1.11.3)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
fission (0.5.0)
|
||||
@@ -439,7 +437,7 @@ GEM
|
||||
httparty (0.16.2)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.6.11)
|
||||
i18n-js (3.5.0)
|
||||
i18n-js (3.5.1)
|
||||
i18n (>= 0.6.6)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
@@ -467,7 +465,7 @@ GEM
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
libv8 (6.3.292.48.1)
|
||||
libv8 (7.3.492.27.1)
|
||||
mail (2.5.5)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
@@ -475,8 +473,8 @@ GEM
|
||||
mime-types (1.25.1)
|
||||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.1.0)
|
||||
mini_racer (0.1.15)
|
||||
libv8 (~> 6.3)
|
||||
mini_racer (0.2.4)
|
||||
libv8 (>= 6.3)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
money (5.1.1)
|
||||
@@ -485,8 +483,6 @@ GEM
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
net-http-persistent (3.1.0)
|
||||
connection_pool (~> 2.2)
|
||||
newrelic_rpm (3.18.1.330)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
@@ -496,7 +492,7 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.7.12)
|
||||
oj (3.10.0)
|
||||
orm_adapter (0.5.0)
|
||||
paper_trail (5.2.3)
|
||||
activerecord (>= 3.0, < 6.0)
|
||||
@@ -507,10 +503,10 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
cocaine (~> 0.5.0)
|
||||
mime-types
|
||||
parallel (1.18.0)
|
||||
parallel (1.19.1)
|
||||
paranoia (1.3.4)
|
||||
activerecord (~> 3.1)
|
||||
parser (2.6.5.0)
|
||||
parser (2.7.0.2)
|
||||
ast (~> 2.4.0)
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
@@ -534,7 +530,7 @@ GEM
|
||||
rack (1.4.7)
|
||||
rack-cache (1.9.0)
|
||||
rack (>= 0.4)
|
||||
rack-mini-profiler (1.0.0)
|
||||
rack-mini-profiler (1.1.4)
|
||||
rack (>= 1.2.0)
|
||||
rack-protection (1.5.5)
|
||||
rack
|
||||
@@ -588,9 +584,9 @@ GEM
|
||||
roadie-rails (1.3.0)
|
||||
railties (>= 3.0, < 5.3)
|
||||
roadie (~> 3.1)
|
||||
roo (2.7.1)
|
||||
roo (2.8.2)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (~> 1.1, < 2.0.0)
|
||||
rubyzip (>= 1.2.1, < 2.0.0)
|
||||
roo-xls (1.1.0)
|
||||
nokogiri
|
||||
roo (>= 2.0.0beta1, < 3)
|
||||
@@ -615,16 +611,19 @@ GEM
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-retry (0.6.1)
|
||||
rspec-retry (0.6.2)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.9.0)
|
||||
rubocop (0.68.1)
|
||||
rubocop (0.79.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.6)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-rails (2.4.1)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-ole (1.2.12.1)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-rc4 (0.1.5)
|
||||
@@ -638,9 +637,9 @@ GEM
|
||||
select2-rails (3.4.9)
|
||||
sass-rails
|
||||
thor (~> 0.14)
|
||||
selenium-webdriver (3.141.0)
|
||||
childprocess (~> 0.5)
|
||||
rubyzip (~> 1.2, >= 1.2.2)
|
||||
selenium-webdriver (3.142.7)
|
||||
childprocess (>= 0.5, < 4.0)
|
||||
rubyzip (>= 1.2.2)
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.17.1)
|
||||
@@ -666,10 +665,8 @@ GEM
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
state_machine (1.2.0)
|
||||
stringex (1.5.1)
|
||||
stripe (4.24.0)
|
||||
faraday (~> 0.13)
|
||||
net-http-persistent (~> 3.0)
|
||||
test-unit (3.3.4)
|
||||
stripe (5.11.0)
|
||||
test-unit (3.3.5)
|
||||
power_assert
|
||||
thor (0.20.3)
|
||||
tilt (1.4.1)
|
||||
@@ -682,11 +679,11 @@ GEM
|
||||
turbo-sprockets-rails3 (0.3.14)
|
||||
railties (> 3.2.8, < 4.0.0)
|
||||
sprockets (>= 2.2.0)
|
||||
tzinfo (0.3.55)
|
||||
tzinfo (0.3.56)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.5.0)
|
||||
unicorn (5.5.1)
|
||||
unicode-display_width (1.6.0)
|
||||
unicorn (5.5.2)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
unicorn-rails (2.2.1)
|
||||
@@ -699,10 +696,10 @@ GEM
|
||||
railties (>= 3.0)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
webdrivers (3.8.1)
|
||||
webdrivers (4.2.0)
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (~> 1.0)
|
||||
selenium-webdriver (~> 3.0)
|
||||
rubyzip (>= 1.3.0)
|
||||
selenium-webdriver (>= 3.0, < 4.0)
|
||||
webmock (3.7.6)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
@@ -734,7 +731,7 @@ DEPENDENCIES
|
||||
blockenspiel
|
||||
bugsnag
|
||||
byebug (~> 9.0.0)
|
||||
capybara (>= 2.15.4)
|
||||
capybara (>= 2.18.0)
|
||||
coffee-rails (~> 3.2.1)
|
||||
combine_pdf
|
||||
compass-rails
|
||||
@@ -763,7 +760,7 @@ DEPENDENCIES
|
||||
gmaps4rails
|
||||
haml
|
||||
i18n (~> 0.6.11)
|
||||
i18n-js (~> 3.5.0)
|
||||
i18n-js (~> 3.5.1)
|
||||
immigrant
|
||||
jquery-migrate-rails
|
||||
jquery-rails (= 3.0.4)
|
||||
@@ -772,8 +769,7 @@ DEPENDENCIES
|
||||
kaminari (~> 0.14.1)
|
||||
knapsack
|
||||
letter_opener (>= 1.4.1)
|
||||
libv8 (= 6.3.292.48.1)
|
||||
mini_racer (= 0.1.15)
|
||||
mini_racer (= 0.2.4)
|
||||
momentjs-rails
|
||||
newrelic_rpm (~> 3.0)
|
||||
nokogiri (>= 1.6.7.1)
|
||||
@@ -794,11 +790,12 @@ DEPENDENCIES
|
||||
rails_safe_tasks (~> 1.0)
|
||||
redcarpet
|
||||
roadie-rails (~> 1.3.0)
|
||||
roo (~> 2.7.0)
|
||||
roo (~> 2.8.2)
|
||||
roo-xls (~> 1.1.0)
|
||||
rspec-rails (>= 3.5.2)
|
||||
rspec-retry
|
||||
rubocop (>= 0.49.1)
|
||||
rubocop
|
||||
rubocop-rails
|
||||
sass (~> 3.3)
|
||||
sass-rails (~> 3.2.3)
|
||||
selenium-webdriver
|
||||
@@ -821,14 +818,14 @@ DEPENDENCIES
|
||||
unicorn
|
||||
unicorn-rails
|
||||
web!
|
||||
webdrivers (= 3.8.1)
|
||||
webdrivers
|
||||
webmock
|
||||
whenever
|
||||
wicked_pdf
|
||||
wkhtmltopdf-binary
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.2.10p489
|
||||
ruby 2.3.7p456
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.2
|
||||
|
||||
@@ -142,7 +142,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
if confirm("Are you sure?")
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.id + "/soft_delete"
|
||||
url: "/api/products/" + product.id
|
||||
).success (data) ->
|
||||
$scope.products.splice $scope.products.indexOf(product), 1
|
||||
DirtyProducts.deleteProduct product.id
|
||||
@@ -157,7 +157,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
if confirm(t("are_you_sure"))
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
|
||||
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id
|
||||
).success (data) ->
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||
|
||||
$scope.enterpriseTotalVariants = (enterprise) ->
|
||||
Enterprise.totalVariants(enterprise)
|
||||
$scope.view = 'incoming'
|
||||
|
||||
$scope.exchangeTotalVariants = (exchange) ->
|
||||
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?
|
||||
|
||||
enterprise = $scope.enterprises[exchange.enterprise_id]
|
||||
return enterprise.numVariants if enterprise.numVariants?
|
||||
|
||||
enterprise.numVariants = 0
|
||||
params = { exchange_id: exchange.id, enterprise_id: exchange.enterprise_id, order_cycle_id: $scope.order_cycle.id, incoming: true}
|
||||
ExchangeProduct.countVariants params, (variants_count) ->
|
||||
enterprise.numVariants = variants_count
|
||||
$scope.setSelectAllVariantsCheckboxValue(exchange, enterprise.numVariants)
|
||||
|
||||
return enterprise.numVariants
|
||||
|
||||
$scope.addSupplier = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addSupplier $scope.new_supplier_id
|
||||
|
||||
# To select all variants we first need to load them all from the server
|
||||
#
|
||||
# This is only needed in Incoming exchanges as here we use supplied_products,
|
||||
# in Outgoing Exchanges the variants are loaded as part of the Exchange payload
|
||||
$scope.selectAllVariants = (exchange, selected) ->
|
||||
$scope.loadAllExchangeProducts(exchange).then ->
|
||||
$scope.setExchangeVariants(exchange, $scope.suppliedVariants(exchange.enterprise_id), selected)
|
||||
$scope.$apply()
|
||||
|
||||
@@ -18,11 +18,11 @@ angular.module('admin.orderCycles')
|
||||
$scope.cancel = (destination) ->
|
||||
$window.location = destination
|
||||
|
||||
# Used in panels/exchange_supplied_products.html
|
||||
# Used in panels/exchange_products_supplied.html
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
# Used in panels/exchange_supplied_products.html and panels/exchange_distributed_products.html
|
||||
# Used in panels/exchange_products_supplied.html and panels/exchange_products_distributed.html
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
angular.module('admin.orderCycles')
|
||||
.controller 'AdminOrderCycleExchangesCtrl', ($scope, $controller, $filter, $window, $location, $timeout, OrderCycle, Enterprise, EnterpriseFee, Schedules, RequestMonitor, ocInstance, StatusMessage) ->
|
||||
.controller 'AdminOrderCycleExchangesCtrl', ($scope, $controller, $filter, $window, $location, $timeout, OrderCycle, ExchangeProduct, Enterprise, EnterpriseFee, Schedules, RequestMonitor, ocInstance, StatusMessage) ->
|
||||
$controller('AdminEditOrderCycleCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||
|
||||
$scope.supplier_enterprises = Enterprise.producer_enterprises
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
|
||||
$scope.productsLoading = ->
|
||||
RequestMonitor.loading
|
||||
|
||||
$scope.setSelectAllVariantsCheckboxValue = (exchange, totalNumberOfVariants) ->
|
||||
exchange.select_all_variants = $scope.exchangeSelectedVariants(exchange) >= totalNumberOfVariants
|
||||
|
||||
$scope.exchangeSelectedVariants = (exchange) ->
|
||||
OrderCycle.exchangeSelectedVariants(exchange)
|
||||
@@ -29,14 +34,6 @@ angular.module('admin.orderCycles')
|
||||
OrderCycle.removeExchangeFee(exchange, index)
|
||||
$scope.order_cycle_form.$dirty = true
|
||||
|
||||
$scope.addSupplier = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addSupplier($scope.new_supplier_id)
|
||||
|
||||
$scope.addDistributor = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addDistributor($scope.new_distributor_id)
|
||||
|
||||
$scope.setPickupTimeFieldDirty = (index) ->
|
||||
$timeout ->
|
||||
pickup_time_field_name = "order_cycle_outgoing_exchange_" + index + "_pickup_time"
|
||||
@@ -44,3 +41,36 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.removeDistributionOfVariant = (variant_id) ->
|
||||
OrderCycle.removeDistributionOfVariant(variant_id)
|
||||
|
||||
$scope.loadExchangeProducts = (exchange, page = 1) ->
|
||||
enterprise = $scope.enterprises[exchange.enterprise_id]
|
||||
enterprise.supplied_products ?= []
|
||||
|
||||
return if enterprise.last_page_loaded? && enterprise.last_page_loaded >= page
|
||||
enterprise.last_page_loaded = page
|
||||
|
||||
incoming = true if $scope.view == 'incoming'
|
||||
params = { exchange_id: exchange.id, enterprise_id: exchange.enterprise_id, order_cycle_id: $scope.order_cycle.id, incoming: incoming, page: page}
|
||||
ExchangeProduct.index params, (products, num_of_pages, num_of_products) ->
|
||||
enterprise.num_of_pages = num_of_pages
|
||||
enterprise.num_of_products = num_of_products
|
||||
enterprise.supplied_products.push products...
|
||||
|
||||
$scope.loadMoreExchangeProducts = (exchange) ->
|
||||
$scope.loadExchangeProducts(exchange, $scope.enterprises[exchange.enterprise_id].last_page_loaded + 1)
|
||||
|
||||
$scope.loadAllExchangeProducts = (exchange) ->
|
||||
enterprise = $scope.enterprises[exchange.enterprise_id]
|
||||
|
||||
if enterprise.last_page_loaded < enterprise.num_of_pages
|
||||
for page_to_load in [(enterprise.last_page_loaded + 1)..enterprise.num_of_pages]
|
||||
RequestMonitor.load $scope.loadExchangeProducts(exchange, page_to_load).$promise
|
||||
|
||||
RequestMonitor.loadQueue
|
||||
|
||||
# initialize exchange products panel if not yet done
|
||||
$scope.exchangeProdutsPanelInitialized = []
|
||||
$scope.initializeExchangeProductsPanel = (exchange) ->
|
||||
return if $scope.exchangeProdutsPanelInitialized[exchange.enterprise_id]
|
||||
RequestMonitor.load $scope.loadExchangeProducts(exchange).$promise
|
||||
$scope.exchangeProdutsPanelInitialized[exchange.enterprise_id] = true
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleOutgoingCtrl', ($scope, $controller, $filter, $location, OrderCycle, ocInstance, StatusMessage) ->
|
||||
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||
|
||||
$scope.productSuppliedToOrderCycle = (product) ->
|
||||
OrderCycle.productSuppliedToOrderCycle(product)
|
||||
$scope.view = 'outgoing'
|
||||
|
||||
$scope.variantSuppliedToOrderCycle = (variant) ->
|
||||
OrderCycle.variantSuppliedToOrderCycle(variant)
|
||||
@@ -10,6 +9,15 @@ angular.module('admin.orderCycles').controller 'AdminOrderCycleOutgoingCtrl', ($
|
||||
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
|
||||
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
|
||||
|
||||
$scope.exchangeTotalVariants = (exchange) ->
|
||||
totalNumberOfVariants = $scope.incomingExchangeVariantsFor(exchange.enterprise_id).length
|
||||
$scope.setSelectAllVariantsCheckboxValue(exchange, totalNumberOfVariants)
|
||||
totalNumberOfVariants
|
||||
|
||||
$scope.addDistributor = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addDistributor $scope.new_distributor_id
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, $controller, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, $controller, $window, OrderCycle, Enterprise, EnterpriseFee, ExchangeProduct, StatusMessage, Schedules, RequestMonitor, ocInstance) ->
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
$scope.order_cycle = OrderCycle.new {coordinator_id: ocInstance.coordinator_id}, =>
|
||||
# TODO: make this a get method, which only fetches one enterprise
|
||||
$scope.enterprises = Enterprise.index {coordinator_id: ocInstance.coordinator_id}, (enterprises) =>
|
||||
$scope.init(enterprises)
|
||||
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
|
||||
|
||||
$scope.init = (enterprises) ->
|
||||
enterprise = enterprises[Object.keys(enterprises)[0]]
|
||||
OrderCycle.addSupplier enterprise.id
|
||||
OrderCycle.addDistributor enterprise.id
|
||||
OrderCycle.order_cycle.coordinator_id = enterprise.id
|
||||
|
||||
OrderCycle.addDistributor enterprise.id, $scope.setOutgoingExchange
|
||||
OrderCycle.addSupplier enterprise.id, $scope.loadExchangeProducts
|
||||
|
||||
$scope.setOutgoingExchange = ->
|
||||
$scope.outgoing_exchange = OrderCycle.order_cycle.outgoing_exchanges[0]
|
||||
|
||||
# All variants start as checked
|
||||
OrderCycle.setExchangeVariants(OrderCycle.order_cycle.incoming_exchanges[0],
|
||||
Enterprise.suppliedVariants(enterprise.id), true)
|
||||
$scope.loadExchangeProducts = ->
|
||||
$scope.incoming_exchange = OrderCycle.order_cycle.incoming_exchanges[0]
|
||||
|
||||
OrderCycle.order_cycle.coordinator_id = enterprise.id
|
||||
params = { enterprise_id: $scope.incoming_exchange.enterprise_id, incoming: true }
|
||||
ExchangeProduct.index params, $scope.storeProductsAndSelectAllVariants
|
||||
|
||||
$scope.storeProductsAndSelectAllVariants = (products) ->
|
||||
$scope.enterprises[$scope.incoming_exchange.enterprise_id].supplied_products = products
|
||||
|
||||
# All variants start as checked
|
||||
OrderCycle.setExchangeVariants($scope.incoming_exchange,
|
||||
Enterprise.suppliedVariants($scope.incoming_exchange.enterprise_id), true)
|
||||
|
||||
$scope.removeDistributionOfVariant = angular.noop
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $controller, $location, $window, OrderCycle, Enterprise, EnterpriseFee, Schedules, RequestMonitor, StatusMessage, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $controller, $location, $window, OrderCycle, Enterprise, EnterpriseFee, ExchangeProduct, Schedules, RequestMonitor, StatusMessage, ocInstance) ->
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
$scope.orderCycleId = ->
|
||||
@@ -11,6 +11,12 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
|
||||
|
||||
$scope.init = ->
|
||||
$scope.outgoing_exchange = OrderCycle.order_cycle.outgoing_exchanges[0]
|
||||
$scope.loadExchangeProducts()
|
||||
|
||||
$scope.loadExchangeProducts = ->
|
||||
exchange = OrderCycle.order_cycle.incoming_exchanges[0]
|
||||
ExchangeProduct.index { exchange_id: exchange.id }, (products) ->
|
||||
$scope.enterprises[exchange.enterprise_id].supplied_products = products
|
||||
|
||||
$scope.removeDistributionOfVariant = angular.noop
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) ->
|
||||
enterprises: {}
|
||||
producer_enterprises: []
|
||||
hub_enterprises: []
|
||||
supplied_products: []
|
||||
loaded: false
|
||||
|
||||
index: (params={}, callback=null) ->
|
||||
@@ -22,9 +21,6 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) ->
|
||||
@producer_enterprises.push(enterprise) if enterprise.is_primary_producer
|
||||
@hub_enterprises.push(enterprise) if enterprise.sells == 'any'
|
||||
|
||||
for product in enterprise.supplied_products
|
||||
@supplied_products.push(product)
|
||||
|
||||
@loaded = true
|
||||
(callback || angular.noop)(@enterprises)
|
||||
|
||||
@@ -39,13 +35,4 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) ->
|
||||
variant.id for variant in product.variants
|
||||
else
|
||||
[product.master_id]
|
||||
|
||||
totalVariants: (enterprise) ->
|
||||
numVariants = 0
|
||||
|
||||
if enterprise
|
||||
counts = for product in enterprise.supplied_products
|
||||
numVariants += if product.variants.length == 0 then 1 else product.variants.length
|
||||
|
||||
numVariants
|
||||
})
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
angular.module('admin.orderCycles').factory('ExchangeProduct', ($resource) ->
|
||||
ExchangeProductResource = $resource('/api/exchanges/:exchange_id/products.json', {}, {
|
||||
'index': { method: 'GET' }
|
||||
'variant_count': { method: 'GET', params: { action_name: "variant_count" }}
|
||||
})
|
||||
{
|
||||
ExchangeProductResource: ExchangeProductResource
|
||||
|
||||
index: (params={}, callback=null) ->
|
||||
ExchangeProductResource.index params, (data) =>
|
||||
(callback || angular.noop)(data.products, data.pagination.pages, data.pagination.results)
|
||||
|
||||
countVariants: (params={}, callback=null) ->
|
||||
ExchangeProductResource.variant_count params, (data) =>
|
||||
(callback || angular.noop)(data.count)
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, StatusMessage, Panels) ->
|
||||
angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $timeout, StatusMessage, Panels) ->
|
||||
OrderCycleResource = $resource '/admin/order_cycles/:action_name/:order_cycle_id.json', {}, {
|
||||
'index': { method: 'GET', isArray: true}
|
||||
'new' : { method: 'GET', params: { action_name: "new" } }
|
||||
@@ -44,11 +44,15 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
|
||||
@removeDistributionOfVariant(variant.id) if exchange.incoming
|
||||
|
||||
|
||||
addSupplier: (new_supplier_id) ->
|
||||
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []})
|
||||
addSupplier: (new_supplier_id, callback) ->
|
||||
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []})
|
||||
$timeout ->
|
||||
(callback || angular.noop)()
|
||||
|
||||
addDistributor: (new_distributor_id) ->
|
||||
this.order_cycle.outgoing_exchanges.push({enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: []})
|
||||
addDistributor: (new_distributor_id, callback) ->
|
||||
this.order_cycle.outgoing_exchanges.push({ enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: [] })
|
||||
$timeout ->
|
||||
(callback || angular.noop)()
|
||||
|
||||
removeExchange: (exchange) ->
|
||||
if exchange.incoming
|
||||
@@ -71,18 +75,6 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
|
||||
removeExchangeFee: (exchange, index) ->
|
||||
exchange.enterprise_fees.splice(index, 1)
|
||||
|
||||
productSuppliedToOrderCycle: (product) ->
|
||||
product_variant_ids = (variant.id for variant in product.variants)
|
||||
variant_ids = [product.master_id].concat(product_variant_ids)
|
||||
incomingExchangesVariants = this.incomingExchangesVariants()
|
||||
|
||||
# TODO: This is an O(n^2) implementation of set intersection and thus is slooow.
|
||||
# Use a better algorithm if needed.
|
||||
# Also, incomingExchangesVariants is called every time, when it only needs to be
|
||||
# called once per change to incoming variants. Some sort of caching?
|
||||
ids = (variant_id for variant_id in variant_ids when incomingExchangesVariants.indexOf(variant_id) != -1)
|
||||
ids.length > 0
|
||||
|
||||
variantSuppliedToOrderCycle: (variant) ->
|
||||
this.incomingExchangesVariants().indexOf(variant.id) != -1
|
||||
|
||||
@@ -143,7 +135,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
|
||||
delete(service.order_cycle.exchanges)
|
||||
service.loaded = true
|
||||
|
||||
(callback || angular.noop)(service.order_cycle)
|
||||
$timeout ->
|
||||
(callback || angular.noop)(service.order_cycle)
|
||||
|
||||
this.order_cycle
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor, Orders, SortOptions, $window, $filter) ->
|
||||
angular.module("admin.orders").controller "ordersCtrl", ($scope, $timeout, RequestMonitor, Orders, SortOptions, $window, $filter) ->
|
||||
$scope.RequestMonitor = RequestMonitor
|
||||
$scope.pagination = Orders.pagination
|
||||
$scope.orders = Orders.all
|
||||
@@ -13,6 +13,7 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
|
||||
$scope.selected = false
|
||||
$scope.select_all = false
|
||||
$scope.poll = 0
|
||||
$scope.rowStatus = {}
|
||||
|
||||
$scope.initialise = ->
|
||||
$scope.per_page = 15
|
||||
@@ -69,6 +70,23 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
|
||||
$scope.fetchResults()
|
||||
, true
|
||||
|
||||
$scope.capturePayment = (order) ->
|
||||
$scope.rowAction('capture', order)
|
||||
|
||||
$scope.shipOrder = (order) ->
|
||||
$scope.rowAction('ship', order)
|
||||
|
||||
$scope.rowAction = (action, order) ->
|
||||
$scope.rowStatus[order.id] = "loading"
|
||||
|
||||
Orders[action](order).$promise.then (data) ->
|
||||
$scope.rowStatus[order.id] = "success"
|
||||
$timeout(->
|
||||
$scope.rowStatus[order.id] = null
|
||||
, 1500)
|
||||
, (error) ->
|
||||
$scope.rowStatus[order.id] = "error"
|
||||
|
||||
$scope.changePage = (newPage) ->
|
||||
$scope.page = newPage
|
||||
$scope.fetchResults(newPage)
|
||||
|
||||
@@ -5,4 +5,14 @@ angular.module("admin.resources").factory 'OrderResource', ($resource) ->
|
||||
method: 'GET'
|
||||
'update':
|
||||
method: 'PUT'
|
||||
'capture':
|
||||
url: '/api/orders/:id/capture.json'
|
||||
method: 'PUT'
|
||||
params:
|
||||
id: '@id'
|
||||
'ship':
|
||||
url: '/api/orders/:id/ship.json'
|
||||
method: 'PUT'
|
||||
params:
|
||||
id: '@id'
|
||||
})
|
||||
|
||||
@@ -44,5 +44,19 @@ angular.module("admin.resources").factory 'Orders', ($q, OrderResource, RequestM
|
||||
changed.push attr unless attr is "$$hashKey"
|
||||
changed
|
||||
|
||||
capture: (order) ->
|
||||
@processAction('capture', order)
|
||||
|
||||
ship: (order) ->
|
||||
@processAction('ship', order)
|
||||
|
||||
processAction: (action, order) ->
|
||||
OrderResource[action] {id: order.number}, (data) =>
|
||||
if data.id
|
||||
angular.merge(order, data)
|
||||
data
|
||||
, (response) =>
|
||||
response.data
|
||||
|
||||
resetAttribute: (order, attribute) ->
|
||||
order[attribute] = @pristineByID[order.id][attribute]
|
||||
|
||||
@@ -20,4 +20,4 @@ angular.module("admin.subscriptions").controller "ProductsPanelController", ($sc
|
||||
keys = Object.keys(response.data.errors)
|
||||
StatusMessage.display 'failure', response.data.errors[keys[0]][0]
|
||||
else
|
||||
StatusMessage.display 'success', t('js.changes_saved')
|
||||
StatusMessage.display 'failure', t('js.admin.subscriptions.error_saving')
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
.row.exchange-distributed-products
|
||||
.sixteen.columns.alpha.omega
|
||||
.row.exchange-distributed-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'}
|
||||
.sixteen.columns.alpha.omega{ 'ng-show' => 'enterprises[exchange.enterprise_id].supplied_products.length != 0' }
|
||||
%div{ 'ng-include' => "'admin/panels/exchange_products_panel_header.html'" }
|
||||
|
||||
.exchange-select-all-variants
|
||||
%label
|
||||
%input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants',
|
||||
@@ -7,11 +9,10 @@
|
||||
'ng-model' => 'exchange.select_all_variants',
|
||||
'ng-change' => 'setExchangeVariants(exchange, incomingExchangeVariantsFor(exchange.enterprise_id), exchange.select_all_variants)',
|
||||
'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants' }
|
||||
{{ 'admin.select_all' | t }}
|
||||
{{ 'js.admin.panels.exchange_products.select_all_products' | t:{ total_number_of_products: enterprises[exchange.enterprise_id].num_of_products } }}
|
||||
|
||||
.exchange-products
|
||||
-# Scope product list based on permissions the current user has to view variants in this exchange
|
||||
.exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' }
|
||||
.exchange-products{ 'ng-hide' => 'productsLoading()' }
|
||||
.exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products | filter:visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges' }
|
||||
.exchange-product-details
|
||||
%label
|
||||
%img{'ng-src' => '{{ product.image_url }}'}
|
||||
@@ -26,3 +27,5 @@
|
||||
'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
|
||||
'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' }
|
||||
{{ variant.label }}
|
||||
|
||||
%div{ 'ng-include' => "'admin/panels/exchange_products_panel_footer.html'" }
|
||||
@@ -0,0 +1,11 @@
|
||||
.pagination{ 'ng-show' => 'enterprises[exchange.enterprise_id].last_page_loaded < enterprises[exchange.enterprise_id].num_of_pages && !productsLoading()'}
|
||||
.button{ 'ng-click' => 'loadMoreExchangeProducts(exchange)' }
|
||||
{{ 'js.admin.panels.exchange_products.load_more_products' | t }}
|
||||
.button{ 'ng-click' => 'loadAllExchangeProducts(exchange)' }
|
||||
{{ 'js.admin.panels.exchange_products.load_all_products' | t }}
|
||||
|
||||
.sixteen.columns.alpha#loading{ 'ng-show' => 'productsLoading()' }
|
||||
%br
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
%h1
|
||||
{{ 'js.admin.panels.exchange_products.loading_products' | t }}
|
||||
@@ -0,0 +1,5 @@
|
||||
.exchange-load-all-variants
|
||||
%div
|
||||
{{ 'js.admin.panels.exchange_products.products_loaded' | t:{ num_of_products_loaded: enterprises[exchange.enterprise_id].supplied_products.length, total_number_of_products: enterprises[exchange.enterprise_id].num_of_products } }}
|
||||
%a{ 'ng-click' => 'loadAllExchangeProducts(exchange)', 'ng-show' => 'enterprises[exchange.enterprise_id].last_page_loaded < enterprises[exchange.enterprise_id].num_of_pages' }
|
||||
{{ 'js.admin.panels.exchange_products.load_all_products' | t }}
|
||||
@@ -0,0 +1,12 @@
|
||||
.row.exchange-supplied-products
|
||||
.sixteen.columns.alpha.omega
|
||||
.exchange-select-all-variants
|
||||
%label
|
||||
%input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants',
|
||||
value: 1,
|
||||
'ng-model' => 'exchange.select_all_variants',
|
||||
'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)',
|
||||
'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' }
|
||||
{{ 'admin.select_all' | t }}
|
||||
|
||||
%div{ 'ng-include' => "'admin/panels/exchange_products_supplied_list.html'" }
|
||||
@@ -0,0 +1,16 @@
|
||||
.row.exchange-supplied-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'}
|
||||
.sixteen.columns.alpha.omega{ 'ng-show' => 'enterprises[exchange.enterprise_id].supplied_products.length != 0' }
|
||||
%div{ 'ng-include' => "'admin/panels/exchange_products_panel_header.html'" }
|
||||
|
||||
.exchange-select-all-variants
|
||||
%label
|
||||
%input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants',
|
||||
value: 1,
|
||||
'ng-model' => 'exchange.select_all_variants',
|
||||
'ng-change' => 'selectAllVariants(exchange, exchange.select_all_variants)',
|
||||
'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' }
|
||||
{{ 'js.admin.panels.exchange_products.select_all_products' | t:{ total_number_of_products: enterprises[exchange.enterprise_id].num_of_products } }}
|
||||
|
||||
%div{ 'ng-include' => "'admin/panels/exchange_products_supplied_list.html'" }
|
||||
|
||||
%div{ 'ng-include' => "'admin/panels/exchange_products_panel_footer.html'" }
|
||||
@@ -0,0 +1,16 @@
|
||||
.exchange-products{ 'ng-hide' => 'productsLoading()' }
|
||||
.exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'}
|
||||
.exchange-product-details
|
||||
%label
|
||||
%img{'ng-src' => '{{ product.image_url }}'}
|
||||
{{ product.name }}
|
||||
|
||||
.exchange-product-variant{'ng-repeat' => 'variant in product.variants'}
|
||||
%label
|
||||
%input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
|
||||
value: 1,
|
||||
'ng-model' => 'exchange.variants[variant.id]',
|
||||
'ofn-sync-distributions' => '{{ variant.id }}',
|
||||
'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
|
||||
'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' }
|
||||
{{ variant.label }}
|
||||
@@ -1,29 +0,0 @@
|
||||
.row.exchange-supplied-products
|
||||
.sixteen.columns.alpha.omega
|
||||
.exchange-select-all-variants
|
||||
%label
|
||||
%input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants',
|
||||
value: 1,
|
||||
'ng-model' => 'exchange.select_all_variants',
|
||||
'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)',
|
||||
'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' }
|
||||
{{ 'admin.select_all' | t }}
|
||||
|
||||
.exchange-products
|
||||
-# No need to scope product list based on permissions, because if an incoming exchange is visible,
|
||||
-# then all of the variants within it should be visible. May change in the future?
|
||||
.exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'}
|
||||
.exchange-product-details
|
||||
%label
|
||||
%img{'ng-src' => '{{ product.image_url }}'}
|
||||
{{ product.name }}
|
||||
|
||||
.exchange-product-variant{'ng-repeat' => 'variant in product.variants'}
|
||||
%label
|
||||
%input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
|
||||
value: 1,
|
||||
'ng-model' => 'exchange.variants[variant.id]',
|
||||
'ofn-sync-distributions' => '{{ variant.id }}',
|
||||
'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
|
||||
'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' }
|
||||
{{ variant.label }}
|
||||
@@ -1,9 +1,9 @@
|
||||
#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
|
||||
.container
|
||||
.eight.columns.alpha
|
||||
.seven.columns.alpha
|
||||
%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 } }
|
||||
.nine.columns.omega.text-right{ ng: { transclude: true } }
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
@import '../variables';
|
||||
|
||||
.row-loading {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.row-loading-icons {
|
||||
margin-left: 3em;
|
||||
position: absolute;
|
||||
|
||||
.spinner {
|
||||
border: 0;
|
||||
width: 2.3em;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 2.3em;
|
||||
opacity: .75;
|
||||
|
||||
&::before {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: $spree-green;
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: $warning-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
4
app/assets/stylesheets/admin/components/tooltip.css.scss
Normal file
4
app/assets/stylesheets/admin/components/tooltip.css.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
#powerTip {
|
||||
max-width: 240px;
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -104,9 +104,13 @@ form.order_cycle {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.exchange-load-all-variants {
|
||||
margin: 0px 5px 20px 5px;
|
||||
}
|
||||
|
||||
.exchange-select-all-variants {
|
||||
clear: both;
|
||||
margin: 5px;
|
||||
margin: 15px 5px 25px 5px;
|
||||
}
|
||||
|
||||
.exchange-products {
|
||||
@@ -209,6 +213,7 @@ table#listing_enterprise_groups {
|
||||
#loading {
|
||||
text-align: center;
|
||||
img.spinner {
|
||||
border: 0px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ module Admin
|
||||
def index
|
||||
order_params = params[:q].andand.delete :order
|
||||
|
||||
orders = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
order_permissions = ::Permissions::Order.new(spree_current_user)
|
||||
orders = order_permissions.
|
||||
editable_orders.ransack(order_params).result
|
||||
|
||||
line_items = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
line_items = order_permissions.
|
||||
editable_line_items.where(order_id: orders).
|
||||
includes(variant: { option_values: :option_type }).
|
||||
ransack(params[:q]).result.
|
||||
|
||||
@@ -28,15 +28,12 @@ module Admin
|
||||
|
||||
def bulk_update
|
||||
@enterprise_fee_set = EnterpriseFeeSet.new(params[:enterprise_fee_set])
|
||||
if @enterprise_fee_set.save
|
||||
redirect_path = main_app.admin_enterprise_fees_path
|
||||
if params.key? :enterprise_id
|
||||
redirect_path = main_app.admin_enterprise_fees_path(enterprise_id: params[:enterprise_id])
|
||||
end
|
||||
redirect_to redirect_path, notice: I18n.t(:enterprise_fees_update_notice)
|
||||
|
||||
if @enterprise_fee_set.save
|
||||
redirect_to redirect_path, notice: I18n.t(:enterprise_fees_update_notice)
|
||||
else
|
||||
render :index
|
||||
redirect_to redirect_path,
|
||||
flash: { error: @enterprise_fee_set.errors.full_messages.to_sentence }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,5 +70,13 @@ module Admin
|
||||
def current_enterprise
|
||||
Enterprise.find params[:enterprise_id] if params.key? :enterprise_id
|
||||
end
|
||||
|
||||
def redirect_path
|
||||
if params.key? :enterprise_id
|
||||
return main_app.admin_enterprise_fees_path(enterprise_id: params[:enterprise_id])
|
||||
end
|
||||
|
||||
main_app.admin_enterprise_fees_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,7 +66,7 @@ module Admin
|
||||
|
||||
if @enterprise.update_attributes(attributes)
|
||||
flash[:success] = I18n.t(:enterprise_register_success_notice, enterprise: @enterprise.name)
|
||||
redirect_to admin_path
|
||||
redirect_to admin_dashboard_path
|
||||
else
|
||||
flash[:error] = I18n.t(:enterprise_register_error, enterprise: @enterprise.name)
|
||||
render :welcome, layout: "spree/layouts/bare_admin"
|
||||
|
||||
@@ -22,7 +22,7 @@ module Admin
|
||||
redirect_to main_app.edit_admin_enterprise_path(stripe_account.enterprise)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash[:error] = "Failed to disconnect Stripe."
|
||||
redirect_to spree.admin_path
|
||||
redirect_to spree.admin_dashboard_path
|
||||
end
|
||||
|
||||
def status
|
||||
|
||||
92
app/controllers/api/exchange_products_controller.rb
Normal file
92
app/controllers/api/exchange_products_controller.rb
Normal file
@@ -0,0 +1,92 @@
|
||||
# This controller lists products that can be added to an exchange
|
||||
module Api
|
||||
class ExchangeProductsController < Api::BaseController
|
||||
DEFAULT_PAGE = 1
|
||||
DEFAULT_PER_PAGE = 100
|
||||
|
||||
skip_authorization_check only: [:index]
|
||||
|
||||
# If exchange_id is present in the URL:
|
||||
# Lists Products that can be added to that Exchange
|
||||
#
|
||||
# If exchange_id is not present in the URL:
|
||||
# Lists Products of the Enterprise given that can be added to the given Order Cycle
|
||||
# In this case parameters are: enterprise_id, order_cycle_id and incoming
|
||||
# (order_cycle_id is not necessary for incoming exchanges)
|
||||
def index
|
||||
if params[:exchange_id].present?
|
||||
load_data_from_exchange
|
||||
else
|
||||
load_data_from_other_params
|
||||
end
|
||||
|
||||
render_variant_count && return if params[:action_name] == "variant_count"
|
||||
|
||||
render_paginated_products paginated_products
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_variant_count
|
||||
render text: {
|
||||
count: Spree::Variant.
|
||||
not_master.
|
||||
where(product_id: products).
|
||||
count
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def products
|
||||
ExchangeProductsRenderer.
|
||||
new(@order_cycle, spree_current_user).
|
||||
exchange_products(@incoming, @enterprise)
|
||||
end
|
||||
|
||||
def paginated_products
|
||||
products.
|
||||
page(params[:page] || DEFAULT_PAGE).
|
||||
per(params[:per_page] || DEFAULT_PER_PAGE)
|
||||
end
|
||||
|
||||
def load_data_from_exchange
|
||||
exchange = Exchange.find_by_id(params[:exchange_id])
|
||||
|
||||
@order_cycle = exchange.order_cycle
|
||||
@incoming = exchange.incoming
|
||||
@enterprise = exchange.sender
|
||||
end
|
||||
|
||||
def load_data_from_other_params
|
||||
@enterprise = Enterprise.find_by_id(params[:enterprise_id])
|
||||
|
||||
if params[:order_cycle_id]
|
||||
@order_cycle = OrderCycle.find_by_id(params[:order_cycle_id])
|
||||
elsif !params[:incoming]
|
||||
raise "order_cycle_id is required to list products for new outgoing exchange"
|
||||
end
|
||||
@incoming = params[:incoming]
|
||||
end
|
||||
|
||||
def render_paginated_products(paginated_products)
|
||||
serializer = ActiveModel::ArraySerializer.new(
|
||||
paginated_products,
|
||||
each_serializer: Api::Admin::ForOrderCycle::SuppliedProductSerializer,
|
||||
order_cycle: @order_cycle
|
||||
)
|
||||
|
||||
render text: {
|
||||
products: serializer,
|
||||
pagination: pagination_data(paginated_products)
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def pagination_data(paginated_products)
|
||||
{
|
||||
results: paginated_products.total_count,
|
||||
pages: paginated_products.num_pages,
|
||||
page: (params[:page] || DEFAULT_PAGE).to_i,
|
||||
per_page: (params[:per_page] || DEFAULT_PER_PAGE).to_i
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,8 +16,38 @@ module Api
|
||||
}
|
||||
end
|
||||
|
||||
def ship
|
||||
authorize! :admin, order
|
||||
|
||||
if order.ship
|
||||
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
|
||||
else
|
||||
render json: { error: I18n.t('api.orders.failed_to_update') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def capture
|
||||
authorize! :admin, order
|
||||
|
||||
pending_payment = order.pending_payments.first
|
||||
|
||||
return payment_capture_failed unless order.payment_required? && pending_payment
|
||||
|
||||
if pending_payment.capture!
|
||||
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
|
||||
else
|
||||
payment_capture_failed
|
||||
end
|
||||
rescue Spree::Core::GatewayError => e
|
||||
error_during_processing(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def payment_capture_failed
|
||||
render json: { error: t(:payment_processing_failed) }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def serialized_orders(orders)
|
||||
ActiveModel::ArraySerializer.new(
|
||||
orders,
|
||||
|
||||
@@ -42,8 +42,8 @@ module Api
|
||||
def destroy
|
||||
authorize! :delete, Spree::Product
|
||||
@product = find_product(params[:id])
|
||||
@product.update_attribute(:deleted_at, Time.zone.now)
|
||||
@product.variants_including_master.update_all(deleted_at: Time.zone.now)
|
||||
authorize! :delete, @product
|
||||
@product.destroy
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: :no_content
|
||||
end
|
||||
|
||||
@@ -71,14 +71,6 @@ module Api
|
||||
render_paged_products @products
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
authorize! :delete, Spree::Product
|
||||
@product = find_product(params[:product_id])
|
||||
authorize! :delete, @product
|
||||
@product.destroy
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: :no_content
|
||||
end
|
||||
|
||||
# POST /api/products/:product_id/clone
|
||||
#
|
||||
def clone
|
||||
|
||||
@@ -35,18 +35,12 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
@variant = scope.find(params[:variant_id])
|
||||
authorize! :delete, @variant
|
||||
|
||||
VariantDeleter.new.delete(@variant)
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: :no_content
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize! :delete, Spree::Variant
|
||||
@variant = scope.find(params[:id])
|
||||
@variant.destroy
|
||||
authorize! :delete, @variant
|
||||
|
||||
VariantDeleter.new.delete(@variant)
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: :no_content
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ require 'open_food_network/address_finder'
|
||||
class CheckoutController < Spree::CheckoutController
|
||||
layout 'darkswarm'
|
||||
|
||||
# We need pessimistic locking to avoid race conditions.
|
||||
# Otherwise we fail on duplicate indexes or end up with negative stock.
|
||||
prepend_around_filter CurrentOrderLocker, only: :update
|
||||
|
||||
prepend_before_filter :check_hub_ready_for_checkout
|
||||
prepend_before_filter :check_order_cycle_expiry
|
||||
prepend_before_filter :require_order_cycle
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
module Spree
|
||||
module Admin
|
||||
AdjustmentsController.class_eval do
|
||||
class AdjustmentsController < ResourceController
|
||||
belongs_to 'spree/order', find_by: :number
|
||||
destroy.after :reload_order
|
||||
|
||||
prepend_before_filter :set_included_tax, only: [:create, :update]
|
||||
before_filter :set_default_tax_rate, only: :edit
|
||||
before_filter :enable_updates, only: :update
|
||||
|
||||
private
|
||||
|
||||
def reload_order
|
||||
@order.reload
|
||||
end
|
||||
|
||||
def collection
|
||||
parent.adjustments.eligible
|
||||
end
|
||||
|
||||
# Choose a default tax rate to show on the edit form. The adjustment stores its included
|
||||
# tax in dollars, but doesn't store the source of the tax (ie. TaxRate that generated it).
|
||||
# We guess which tax rate here, choosing:
|
||||
@@ -15,28 +26,34 @@ module Spree
|
||||
# When we have to go with 2, we show an error message to ask the admin to check that the
|
||||
# correct tax is being applied.
|
||||
def set_default_tax_rate
|
||||
if @adjustment.included_tax > 0
|
||||
trs = TaxRate.match(@order)
|
||||
tr_yielding_matching_tax = trs.select { |tr| tr.compute_tax(@adjustment.amount) == @adjustment.included_tax }.first.andand.id
|
||||
tr_valid_for_order = TaxRate.match(@order).first.andand.id
|
||||
return if @adjustment.included_tax <= 0
|
||||
|
||||
@tax_rate_id = tr_yielding_matching_tax || tr_valid_for_order
|
||||
tax_rates = TaxRate.match(@order)
|
||||
tax_rate_with_matching_tax = find_tax_rate_with_matching_tax(tax_rates)
|
||||
tax_rate_valid_for_order = tax_rates.first.andand.id
|
||||
|
||||
if tr_yielding_matching_tax.nil?
|
||||
@adjustment.errors.add :tax_rate_id, I18n.t(:adjustments_tax_rate_error)
|
||||
end
|
||||
@tax_rate_id = tax_rate_with_matching_tax || tax_rate_valid_for_order
|
||||
|
||||
return unless tax_rate_with_matching_tax.nil?
|
||||
|
||||
@adjustment.errors.add :tax_rate_id, I18n.t(:adjustments_tax_rate_error)
|
||||
end
|
||||
|
||||
def find_tax_rate_with_matching_tax(tax_rates)
|
||||
tax_rates_yielding_matching_tax = tax_rates.select do |tr|
|
||||
tr.compute_tax(@adjustment.amount) == @adjustment.included_tax
|
||||
end
|
||||
tax_rates_yielding_matching_tax.first.andand.id
|
||||
end
|
||||
|
||||
def set_included_tax
|
||||
included_tax = 0
|
||||
if params[:tax_rate_id].present?
|
||||
tax_rate = TaxRate.find params[:tax_rate_id]
|
||||
amount = params[:adjustment][:amount].to_f
|
||||
params[:adjustment][:included_tax] = tax_rate.compute_tax amount
|
||||
|
||||
else
|
||||
params[:adjustment][:included_tax] = 0
|
||||
included_tax = tax_rate.compute_tax amount
|
||||
end
|
||||
params[:adjustment][:included_tax] = included_tax
|
||||
end
|
||||
|
||||
# Spree 2.0 keeps shipping fee adjustments open unless they are manually
|
||||
142
app/controllers/spree/admin/base_controller.rb
Normal file
142
app/controllers/spree/admin/base_controller.rb
Normal file
@@ -0,0 +1,142 @@
|
||||
module Spree
|
||||
module Admin
|
||||
class BaseController < Spree::BaseController
|
||||
ssl_required
|
||||
|
||||
helper 'spree/admin/navigation'
|
||||
layout '/spree/layouts/admin'
|
||||
|
||||
include I18nHelper
|
||||
|
||||
before_filter :authorize_admin
|
||||
before_filter :set_locale
|
||||
before_filter :warn_invalid_order_cycles, if: :html_request?
|
||||
|
||||
# Warn the user when they have an active order cycle with hubs that are not ready
|
||||
# for checkout (ie. does not have valid shipping and payment methods).
|
||||
def warn_invalid_order_cycles
|
||||
distributors = active_distributors_not_ready_for_checkout
|
||||
|
||||
return if distributors.empty? || flash[:notice].present?
|
||||
|
||||
flash[:notice] = active_distributors_not_ready_for_checkout_message(distributors)
|
||||
end
|
||||
|
||||
# This is in Spree::Core::ControllerHelpers::Auth
|
||||
# But you can't easily reopen modules in Ruby
|
||||
def unauthorized
|
||||
if try_spree_current_user
|
||||
flash[:error] = t(:authorization_failure)
|
||||
redirect_to '/unauthorized'
|
||||
else
|
||||
store_location
|
||||
redirect_to root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def model_class
|
||||
const_name = controller_name.classify
|
||||
return "Spree::#{const_name}".constantize if Spree.const_defined?(const_name)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def action
|
||||
params[:action].to_sym
|
||||
end
|
||||
|
||||
def authorize_admin
|
||||
if respond_to?(:model_class, true) && model_class
|
||||
record = model_class
|
||||
else
|
||||
# This allows specificity for each non-resource controller
|
||||
# (to be consistent with "authorize_resource :class => false", see https://github.com/ryanb/cancan/blob/60cf6a67ef59c0c9b63bc27ea0101125c4193ea6/lib/cancan/controller_resource.rb#L146)
|
||||
record = self.class.to_s.
|
||||
sub("Controller", "").
|
||||
underscore.split('/').last.singularize.to_sym
|
||||
end
|
||||
authorize! :admin, record
|
||||
authorize! resource_authorize_action, record
|
||||
end
|
||||
|
||||
def resource_authorize_action
|
||||
action
|
||||
end
|
||||
|
||||
def flash_message_for(object, event_sym)
|
||||
resource_desc = object.class.model_name.human
|
||||
resource_desc += " \"#{object.name}\"" if object.respond_to?(:name) && object.name.present?
|
||||
Spree.t(event_sym, resource: resource_desc)
|
||||
end
|
||||
|
||||
def render_js_for_destroy
|
||||
render partial: '/spree/admin/shared/destroy'
|
||||
end
|
||||
|
||||
# Index request for JSON needs to pass a CSRF token in order to prevent JSON Hijacking
|
||||
def check_json_authenticity
|
||||
return unless request.format.js? || request.format.json?
|
||||
|
||||
return unless protect_against_forgery?
|
||||
|
||||
auth_token = params[request_forgery_protection_token]
|
||||
return if auth_token && form_authenticity_token == CGI.unescape(auth_token)
|
||||
|
||||
raise(ActionController::InvalidAuthenticityToken)
|
||||
end
|
||||
|
||||
def config_locale
|
||||
Spree::Backend::Config[:locale]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def active_distributors_not_ready_for_checkout
|
||||
ocs = OrderCycle.managed_by(spree_current_user).active
|
||||
distributors = ocs.includes(:distributors).map(&:distributors).flatten.uniq
|
||||
Enterprise.where('enterprises.id IN (?)', distributors).not_ready_for_checkout
|
||||
end
|
||||
|
||||
def active_distributors_not_ready_for_checkout_message(distributors)
|
||||
distributor_names = distributors.map(&:name).join ', '
|
||||
|
||||
if distributors.count > 1
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_plural,
|
||||
distributor_names: distributor_names)
|
||||
else
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_singular,
|
||||
distributor_names: distributor_names)
|
||||
end
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
|
||||
def json_request?
|
||||
request.format.json?
|
||||
end
|
||||
|
||||
def render_as_json(data, options = {})
|
||||
ams_prefix = options.delete :ams_prefix
|
||||
if [Array, ActiveRecord::Relation].include? data.class
|
||||
render options.merge(json: data, each_serializer: serializer(ams_prefix))
|
||||
else
|
||||
render options.merge(json: data, serializer: serializer(ams_prefix))
|
||||
end
|
||||
end
|
||||
|
||||
def serializer(ams_prefix)
|
||||
unless ams_prefix.nil? || ams_prefix_whitelist.include?(ams_prefix.to_sym)
|
||||
raise "Suffix '#{ams_prefix}' not found in ams_prefix_whitelist for #{self.class.name}."
|
||||
end
|
||||
|
||||
prefix = ams_prefix.andand.classify || ""
|
||||
name = controller_name.classify
|
||||
"::Api::Admin::#{prefix}#{name}Serializer".constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,105 +0,0 @@
|
||||
require 'spree/core/controller_helpers/respond_with_decorator'
|
||||
|
||||
Spree::Admin::BaseController.class_eval do
|
||||
include I18nHelper
|
||||
|
||||
layout 'spree/layouts/admin'
|
||||
|
||||
before_filter :set_locale
|
||||
before_filter :warn_invalid_order_cycles, if: :html_request?
|
||||
|
||||
# Warn the user when they have an active order cycle with hubs that are not ready
|
||||
# for checkout (ie. does not have valid shipping and payment methods).
|
||||
def warn_invalid_order_cycles
|
||||
distributors = active_distributors_not_ready_for_checkout
|
||||
|
||||
if distributors.any? && flash[:notice].nil?
|
||||
flash[:notice] = active_distributors_not_ready_for_checkout_message(distributors)
|
||||
end
|
||||
end
|
||||
|
||||
# Override Spree method
|
||||
# It's a shame Spree doesn't just let CanCan handle this in it's own way
|
||||
def authorize_admin
|
||||
if respond_to?(:model_class, true) && model_class
|
||||
record = model_class
|
||||
else
|
||||
# this line changed to allow specificity for each non-resource controller (to be consistent with "authorize_resource :class => false", see https://github.com/ryanb/cancan/blob/60cf6a67ef59c0c9b63bc27ea0101125c4193ea6/lib/cancan/controller_resource.rb#L146)
|
||||
record = self.class.to_s.sub("Controller", "").underscore.split('/').last.singularize.to_sym
|
||||
end
|
||||
authorize! :admin, record
|
||||
authorize! resource_authorize_action, record
|
||||
end
|
||||
|
||||
def resource_authorize_action
|
||||
action
|
||||
end
|
||||
|
||||
# This is in Spree::Core::ControllerHelpers::Auth
|
||||
# But you can't easily reopen modules in Ruby
|
||||
def unauthorized
|
||||
if try_spree_current_user
|
||||
flash[:error] = t(:authorization_failure)
|
||||
redirect_to '/unauthorized'
|
||||
else
|
||||
store_location
|
||||
redirect_to root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def model_class
|
||||
const_name = controller_name.classify
|
||||
if Spree.const_defined?(const_name)
|
||||
return "Spree::#{const_name}".constantize
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def active_distributors_not_ready_for_checkout
|
||||
ocs = OrderCycle.managed_by(spree_current_user).active
|
||||
distributors = ocs.includes(:distributors).map(&:distributors).flatten.uniq
|
||||
Enterprise.where('enterprises.id IN (?)', distributors).not_ready_for_checkout
|
||||
end
|
||||
|
||||
def active_distributors_not_ready_for_checkout_message(distributors)
|
||||
distributor_names = distributors.map(&:name).join ', '
|
||||
|
||||
if distributors.count > 1
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_plural, distributor_names: distributor_names)
|
||||
else
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_singular, distributor_names: distributor_names)
|
||||
end
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
|
||||
def json_request?
|
||||
request.format.json?
|
||||
end
|
||||
|
||||
def render_as_json(data, options = {})
|
||||
ams_prefix = options.delete :ams_prefix
|
||||
if [Array, ActiveRecord::Relation].include? data.class
|
||||
render options.merge(json: data, each_serializer: serializer(ams_prefix))
|
||||
else
|
||||
render options.merge(json: data, serializer: serializer(ams_prefix))
|
||||
end
|
||||
end
|
||||
|
||||
def serializer(ams_prefix)
|
||||
if ams_prefix.nil? || ams_prefix_whitelist.include?(ams_prefix.to_sym)
|
||||
prefix = ams_prefix.andand.classify || ""
|
||||
name = controller_name.classify
|
||||
"Api::Admin::#{prefix}#{name}Serializer".constantize
|
||||
else
|
||||
raise "Suffix '#{ams_prefix}' not found in ams_prefix_whitelist for #{self.class.name}."
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,8 +5,7 @@ module Spree
|
||||
@preferences_general = [:site_name, :default_seo_title, :default_meta_keywords,
|
||||
:default_meta_description, :site_url, :bugherd_api_key]
|
||||
@preferences_security = [:allow_ssl_in_production,
|
||||
:allow_ssl_in_staging, :allow_ssl_in_development_and_test,
|
||||
:check_for_spree_alerts]
|
||||
:allow_ssl_in_staging, :allow_ssl_in_development_and_test]
|
||||
@preferences_currency = [:display_currency, :hide_cents]
|
||||
end
|
||||
|
||||
@@ -20,18 +19,6 @@ module Spree
|
||||
|
||||
redirect_to edit_admin_general_settings_path
|
||||
end
|
||||
|
||||
def dismiss_alert
|
||||
return unless request.xhr? && params[:alert_id]
|
||||
|
||||
dismissed = Spree::Config[:dismissed_spree_alerts] || ''
|
||||
Spree::Config.set(dismissed_spree_alerts: dismissed.
|
||||
split(',').
|
||||
push(params[:alert_id]).
|
||||
join(','))
|
||||
filter_dismissed_alerts
|
||||
render nothing: true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
79
app/controllers/spree/admin/overview_controller.rb
Normal file
79
app/controllers/spree/admin/overview_controller.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
# this clas was inspired (heavily) from the mephisto admin architecture
|
||||
module Spree
|
||||
module Admin
|
||||
class OverviewController < Spree::Admin::BaseController
|
||||
def index
|
||||
@enterprises = Enterprise
|
||||
.managed_by(spree_current_user)
|
||||
.order('is_primary_producer ASC, name')
|
||||
@product_count = Spree::Product.active.managed_by(spree_current_user).count
|
||||
@order_cycle_count = OrderCycle.active.managed_by(spree_current_user).count
|
||||
|
||||
if first_access
|
||||
redirect_to enterprises_path
|
||||
else
|
||||
render dashboard_view
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Checks whether the user is accessing the admin for the first time
|
||||
#
|
||||
# @return [Boolean]
|
||||
def first_access
|
||||
outside_referral && incomplete_enterprise_registration?
|
||||
end
|
||||
|
||||
# Checks whether the request comes from another admin page or not
|
||||
#
|
||||
# @return [Boolean]
|
||||
def outside_referral
|
||||
!URI(request.referer.to_s).path.match(%r{/admin})
|
||||
end
|
||||
|
||||
# Checks that all of the enterprises owned by the current user have a 'sells'
|
||||
# property specified, which indicates that the registration process has been
|
||||
# completed
|
||||
#
|
||||
# @return [Boolean]
|
||||
def incomplete_enterprise_registration?
|
||||
@incomplete_enterprise_registration ||= spree_current_user
|
||||
.owned_enterprises
|
||||
.where(sells: 'unspecified')
|
||||
.exists?
|
||||
end
|
||||
|
||||
# Returns the appropriate enterprise path for the current user
|
||||
#
|
||||
# @return [String]
|
||||
def enterprises_path
|
||||
if managed_enterprises.size == 1
|
||||
@enterprise = @enterprises.first
|
||||
main_app.welcome_admin_enterprise_path(@enterprise)
|
||||
else
|
||||
main_app.admin_enterprises_path
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the appropriate dashboard view for the current user
|
||||
#
|
||||
# @return [String]
|
||||
def dashboard_view
|
||||
if managed_enterprises.size == 1
|
||||
@enterprise = @enterprises.first
|
||||
:single_enterprise_dashboard
|
||||
else
|
||||
:multi_enterprise_dashboard
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the list of enterprises the current user is manager of
|
||||
#
|
||||
# @return [ActiveRecord::Relation<Enterprise>]
|
||||
def managed_enterprises
|
||||
spree_current_user.enterprises
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,74 +0,0 @@
|
||||
Spree::Admin::OverviewController.class_eval do
|
||||
def index
|
||||
@enterprises = Enterprise
|
||||
.managed_by(spree_current_user)
|
||||
.order('is_primary_producer ASC, name')
|
||||
@product_count = Spree::Product.active.managed_by(spree_current_user).count
|
||||
@order_cycle_count = OrderCycle.active.managed_by(spree_current_user).count
|
||||
|
||||
if first_access
|
||||
redirect_to enterprises_path
|
||||
else
|
||||
render dashboard_view
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Checks whether the user is accessing the admin for the first time
|
||||
#
|
||||
# @return [Boolean]
|
||||
def first_access
|
||||
outside_referral && incomplete_enterprise_registration?
|
||||
end
|
||||
|
||||
# Checks whether the request comes from another admin page or not
|
||||
#
|
||||
# @return [Boolean]
|
||||
def outside_referral
|
||||
!URI(request.referer.to_s).path.match(%r{/admin})
|
||||
end
|
||||
|
||||
# Checks that all of the enterprises owned by the current user have a 'sells'
|
||||
# property specified, which indicates that the registration process has been
|
||||
# completed
|
||||
#
|
||||
# @return [Boolean]
|
||||
def incomplete_enterprise_registration?
|
||||
@incomplete_enterprise_registration ||= spree_current_user
|
||||
.owned_enterprises
|
||||
.where(sells: 'unspecified')
|
||||
.exists?
|
||||
end
|
||||
|
||||
# Returns the appropriate enterprise path for the current user
|
||||
#
|
||||
# @return [String]
|
||||
def enterprises_path
|
||||
if managed_enterprises.size == 1
|
||||
@enterprise = @enterprises.first
|
||||
main_app.welcome_admin_enterprise_path(@enterprise)
|
||||
else
|
||||
main_app.admin_enterprises_path
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the appropriate dashboard view for the current user
|
||||
#
|
||||
# @return [String]
|
||||
def dashboard_view
|
||||
if managed_enterprises.size == 1
|
||||
@enterprise = @enterprises.first
|
||||
:single_enterprise_dashboard
|
||||
else
|
||||
:multi_enterprise_dashboard
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the list of enterprises the current user is manager of
|
||||
#
|
||||
# @return [ActiveRecord::Relation<Enterprise>]
|
||||
def managed_enterprises
|
||||
spree_current_user.enterprises
|
||||
end
|
||||
end
|
||||
317
app/controllers/spree/admin/reports_controller.rb
Normal file
317
app/controllers/spree/admin/reports_controller.rb
Normal file
@@ -0,0 +1,317 @@
|
||||
require 'csv'
|
||||
|
||||
require 'open_food_network/reports/list'
|
||||
require 'open_food_network/order_and_distributor_report'
|
||||
require 'open_food_network/products_and_inventory_report'
|
||||
require 'open_food_network/lettuce_share_report'
|
||||
require 'open_food_network/group_buy_report'
|
||||
require 'open_food_network/order_grouper'
|
||||
require 'open_food_network/customers_report'
|
||||
require 'open_food_network/users_and_enterprises_report'
|
||||
require 'open_food_network/order_cycle_management_report'
|
||||
require 'open_food_network/packing_report'
|
||||
require 'open_food_network/sales_tax_report'
|
||||
require 'open_food_network/xero_invoices_report'
|
||||
require 'open_food_network/bulk_coop_report'
|
||||
require 'open_food_network/payments_report'
|
||||
require 'open_food_network/orders_and_fulfillments_report'
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class ReportsController < Spree::Admin::BaseController
|
||||
include Spree::ReportsHelper
|
||||
|
||||
helper_method :render_content?
|
||||
|
||||
before_filter :cache_search_state
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
before_filter :load_data,
|
||||
only: [:customers, :products_and_inventory, :order_cycle_management, :packing]
|
||||
|
||||
respond_to :html
|
||||
|
||||
def report_types
|
||||
OpenFoodNetwork::Reports::List.all
|
||||
end
|
||||
|
||||
def index
|
||||
@reports = authorized_reports
|
||||
respond_with(@reports)
|
||||
end
|
||||
|
||||
def customers
|
||||
@report_types = report_types[:customers]
|
||||
@report_type = params[:report_type]
|
||||
@report = OpenFoodNetwork::CustomersReport.new spree_current_user, params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv], "customers_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def order_cycle_management
|
||||
params[:q] ||= {}
|
||||
|
||||
@report_types = report_types[:order_cycle_management]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrderCycleManagementReport.new spree_current_user,
|
||||
params,
|
||||
render_content?
|
||||
@table = @report.table_items
|
||||
|
||||
render_report(@report.header, @table, params[:csv],
|
||||
"order_cycle_management_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def packing
|
||||
params[:q] ||= {}
|
||||
|
||||
@report_types = report_types[:packing]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::PackingReport.new spree_current_user, params, render_content?
|
||||
@table = order_grouper_table
|
||||
|
||||
render_report(@report.header, @table, params[:csv], "packing_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def orders_and_distributors
|
||||
@report = OpenFoodNetwork::OrderAndDistributorReport.new spree_current_user,
|
||||
params,
|
||||
render_content?
|
||||
@search = @report.search
|
||||
csv_file_name = "orders_and_distributors_#{timestamp}.csv"
|
||||
render_report(@report.header, @report.table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def sales_tax
|
||||
@distributors = my_distributors
|
||||
@report_type = params[:report_type]
|
||||
@report = OpenFoodNetwork::SalesTaxReport.new spree_current_user, params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv], "sales_tax.csv")
|
||||
end
|
||||
|
||||
def bulk_coop
|
||||
# -- Prepare form options
|
||||
@distributors = my_distributors
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::BulkCoopReport.new spree_current_user, params, render_content?
|
||||
@table = order_grouper_table
|
||||
csv_file_name = "bulk_coop_#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def payments
|
||||
# -- Prepare Form Options
|
||||
@distributors = my_distributors
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::PaymentsReport.new spree_current_user, params, render_content?
|
||||
@table = order_grouper_table
|
||||
csv_file_name = "payments_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def orders_and_fulfillment
|
||||
params[:q] ||= orders_and_fulfillment_default_filters
|
||||
|
||||
# -- Prepare Form Options
|
||||
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
# My distributors and any distributors distributing products I supply
|
||||
@distributors = permissions.visible_enterprises_for_order_reports.is_distributor
|
||||
# My suppliers and any suppliers supplying products I distribute
|
||||
@suppliers = permissions.visible_enterprises_for_order_reports.is_primary_producer
|
||||
|
||||
@order_cycles = my_order_cycles
|
||||
|
||||
@report_types = report_types[:orders_and_fulfillment]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
@include_blank = I18n.t(:all)
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user,
|
||||
params,
|
||||
render_content?
|
||||
@table = order_grouper_table
|
||||
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def products_and_inventory
|
||||
@report_types = report_types[:products_and_inventory]
|
||||
@report = if params[:report_type] != 'lettuce_share'
|
||||
OpenFoodNetwork::ProductsAndInventoryReport.new spree_current_user,
|
||||
params,
|
||||
render_content?
|
||||
else
|
||||
OpenFoodNetwork::LettuceShareReport.new spree_current_user,
|
||||
params,
|
||||
render_content?
|
||||
end
|
||||
|
||||
render_report @report.header,
|
||||
@report.table,
|
||||
params[:csv],
|
||||
"products_and_inventory_#{timestamp}.csv"
|
||||
end
|
||||
|
||||
def users_and_enterprises
|
||||
@report = OpenFoodNetwork::UsersAndEnterprisesReport.new params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv],
|
||||
"users_and_enterprises_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def xero_invoices
|
||||
params[:q] ||= {}
|
||||
|
||||
@distributors = my_distributors
|
||||
@order_cycles = my_order_cycles
|
||||
|
||||
@report = OpenFoodNetwork::XeroInvoicesReport.new(spree_current_user,
|
||||
params,
|
||||
render_content?)
|
||||
render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def model_class
|
||||
Spree::Admin::ReportsController
|
||||
end
|
||||
|
||||
# Some actions are changing the `params` object. That is unfortunate Spree
|
||||
# behavior and we are building on it. So we have to look at `params` early
|
||||
# to check if we are searching or just displaying a report search form.
|
||||
def cache_search_state
|
||||
search_keys = [
|
||||
# search parameter for ransack
|
||||
:q,
|
||||
# common in all reports, only set for CSV rendering
|
||||
:csv,
|
||||
# `button` is included in all forms. It's not important for searching,
|
||||
# but the Users & Enterprises report doesn't have any other parameter
|
||||
# for an empty search. So we use this one to display data.
|
||||
:button,
|
||||
# Some reports use filtering by enterprise or order cycle
|
||||
:distributor_id,
|
||||
:supplier_id,
|
||||
:order_cycle_id,
|
||||
# Xero Invoices can be filtered by date
|
||||
:invoice_date,
|
||||
:due_date
|
||||
]
|
||||
@searching = search_keys.any? { |key| params.key? key }
|
||||
end
|
||||
|
||||
# We don't want to render data unless search params are supplied.
|
||||
# Compiling data can take a long time.
|
||||
def render_content?
|
||||
@searching
|
||||
end
|
||||
|
||||
def render_report(header, table, create_csv, csv_file_name)
|
||||
send_data csv_report(header, table), filename: csv_file_name if create_csv
|
||||
@header = header
|
||||
@table = table
|
||||
# Rendering HTML is the default.
|
||||
end
|
||||
|
||||
def csv_report(header, table)
|
||||
CSV.generate do |csv|
|
||||
csv << header
|
||||
table.each { |row| csv << row }
|
||||
end
|
||||
end
|
||||
|
||||
def load_data
|
||||
@distributors = my_distributors
|
||||
@suppliers = my_suppliers | suppliers_of_products_distributed_by(@distributors)
|
||||
@order_cycles = my_order_cycles
|
||||
end
|
||||
|
||||
# Load managed distributor enterprises of current user
|
||||
def my_distributors
|
||||
Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
end
|
||||
|
||||
# Load managed producer enterprises of current user
|
||||
def my_suppliers
|
||||
Enterprise.is_primary_producer.managed_by(spree_current_user)
|
||||
end
|
||||
|
||||
def suppliers_of_products_distributed_by(distributors)
|
||||
distributors.map { |d| Spree::Product.in_distributor(d).includes(:supplier).all }.
|
||||
flatten.map(&:supplier).uniq
|
||||
end
|
||||
|
||||
# Load order cycles the current user has access to
|
||||
def my_order_cycles
|
||||
OrderCycle.
|
||||
active_or_complete.
|
||||
accessible_by(spree_current_user).
|
||||
order('orders_close_at DESC')
|
||||
end
|
||||
|
||||
def order_grouper_table
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
order_grouper.table(@report.table_items)
|
||||
end
|
||||
|
||||
def authorized_reports
|
||||
all_reports = [
|
||||
:orders_and_distributors,
|
||||
:bulk_coop,
|
||||
:payments,
|
||||
:orders_and_fulfillment,
|
||||
:customers,
|
||||
:products_and_inventory,
|
||||
:users_and_enterprises,
|
||||
:enterprise_fee_summary,
|
||||
:order_cycle_management,
|
||||
:sales_tax,
|
||||
:xero_invoices,
|
||||
:packing
|
||||
]
|
||||
reports = all_reports.select { |action| can? action, Spree::Admin::ReportsController }
|
||||
reports.map { |report| [report, describe_report(report)] }.to_h
|
||||
end
|
||||
|
||||
def describe_report(report)
|
||||
name = I18n.t(:name, scope: [:admin, :reports, report])
|
||||
description = begin
|
||||
I18n.t!(:description, scope: [:admin, :reports, report])
|
||||
rescue I18n::MissingTranslationData
|
||||
render_to_string(
|
||||
partial: "#{report}_description",
|
||||
layout: false,
|
||||
locals: { report_types: report_types[report] }
|
||||
).html_safe
|
||||
end
|
||||
{ name: name, url: url_for_report(report), description: description }
|
||||
end
|
||||
|
||||
def url_for_report(report)
|
||||
public_send("#{report}_admin_reports_url".to_sym)
|
||||
rescue NoMethodError
|
||||
url_for([:new, :admin, :reports, report.to_s.singularize])
|
||||
end
|
||||
|
||||
def timestamp
|
||||
Time.zone.now.strftime("%Y%m%d")
|
||||
end
|
||||
|
||||
def orders_and_fulfillment_default_filters
|
||||
now = Time.zone.now
|
||||
{ completed_at_gt: (now - 1.month).beginning_of_day,
|
||||
completed_at_lt: (now + 1.day).beginning_of_day }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,287 +0,0 @@
|
||||
require 'csv'
|
||||
|
||||
require 'open_food_network/reports/list'
|
||||
require 'open_food_network/order_and_distributor_report'
|
||||
require 'open_food_network/products_and_inventory_report'
|
||||
require 'open_food_network/lettuce_share_report'
|
||||
require 'open_food_network/group_buy_report'
|
||||
require 'open_food_network/order_grouper'
|
||||
require 'open_food_network/customers_report'
|
||||
require 'open_food_network/users_and_enterprises_report'
|
||||
require 'open_food_network/order_cycle_management_report'
|
||||
require 'open_food_network/packing_report'
|
||||
require 'open_food_network/sales_tax_report'
|
||||
require 'open_food_network/xero_invoices_report'
|
||||
require 'open_food_network/bulk_coop_report'
|
||||
require 'open_food_network/payments_report'
|
||||
require 'open_food_network/orders_and_fulfillments_report'
|
||||
|
||||
Spree::Admin::ReportsController.class_eval do
|
||||
include Spree::ReportsHelper
|
||||
|
||||
helper_method :render_content?
|
||||
|
||||
before_filter :cache_search_state
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management, :packing]
|
||||
|
||||
def report_types
|
||||
OpenFoodNetwork::Reports::List.all
|
||||
end
|
||||
|
||||
# Override spree reports list.
|
||||
def index
|
||||
@reports = authorized_reports
|
||||
respond_with(@reports)
|
||||
end
|
||||
|
||||
def customers
|
||||
@report_types = report_types[:customers]
|
||||
@report_type = params[:report_type]
|
||||
@report = OpenFoodNetwork::CustomersReport.new spree_current_user, params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv], "customers_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def order_cycle_management
|
||||
params[:q] ||= {}
|
||||
|
||||
@report_types = report_types[:order_cycle_management]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrderCycleManagementReport.new spree_current_user, params, render_content?
|
||||
@table = @report.table_items
|
||||
|
||||
render_report(@report.header, @table, params[:csv], "order_cycle_management_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def packing
|
||||
params[:q] ||= {}
|
||||
|
||||
@report_types = report_types[:packing]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::PackingReport.new spree_current_user, params, render_content?
|
||||
@table = order_grouper_table
|
||||
|
||||
render_report(@report.header, @table, params[:csv], "packing_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def orders_and_distributors
|
||||
@report = OpenFoodNetwork::OrderAndDistributorReport.new spree_current_user, params, render_content?
|
||||
@search = @report.search
|
||||
csv_file_name = "orders_and_distributors_#{timestamp}.csv"
|
||||
render_report(@report.header, @report.table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def sales_tax
|
||||
@distributors = my_distributors
|
||||
@report_type = params[:report_type]
|
||||
@report = OpenFoodNetwork::SalesTaxReport.new spree_current_user, params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv], "sales_tax.csv")
|
||||
end
|
||||
|
||||
def bulk_coop
|
||||
# -- Prepare form options
|
||||
@distributors = my_distributors
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::BulkCoopReport.new spree_current_user, params, render_content?
|
||||
@table = order_grouper_table
|
||||
csv_file_name = "bulk_coop_#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def payments
|
||||
# -- Prepare Form Options
|
||||
@distributors = my_distributors
|
||||
@report_type = params[:report_type]
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::PaymentsReport.new spree_current_user, params, render_content?
|
||||
@table = order_grouper_table
|
||||
csv_file_name = "payments_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def orders_and_fulfillment
|
||||
params[:q] ||= orders_and_fulfillment_default_filters
|
||||
|
||||
# -- Prepare Form Options
|
||||
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
# My distributors and any distributors distributing products I supply
|
||||
@distributors = permissions.visible_enterprises_for_order_reports.is_distributor
|
||||
# My suppliers and any suppliers supplying products I distribute
|
||||
@suppliers = permissions.visible_enterprises_for_order_reports.is_primary_producer
|
||||
|
||||
@order_cycles = my_order_cycles
|
||||
|
||||
@report_types = report_types[:orders_and_fulfillment]
|
||||
@report_type = params[:report_type]
|
||||
|
||||
@include_blank = I18n.t(:all)
|
||||
|
||||
# -- Build Report with Order Grouper
|
||||
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new(permissions,
|
||||
params, render_content?)
|
||||
@table = order_grouper_table
|
||||
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
|
||||
|
||||
render_report(@report.header, @table, params[:csv], csv_file_name)
|
||||
end
|
||||
|
||||
def products_and_inventory
|
||||
@report_types = report_types[:products_and_inventory]
|
||||
@report = if params[:report_type] != 'lettuce_share'
|
||||
OpenFoodNetwork::ProductsAndInventoryReport.new spree_current_user, params, render_content?
|
||||
else
|
||||
OpenFoodNetwork::LettuceShareReport.new spree_current_user, params, render_content?
|
||||
end
|
||||
render_report(@report.header, @report.table, params[:csv], "products_and_inventory_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def users_and_enterprises
|
||||
@report = OpenFoodNetwork::UsersAndEnterprisesReport.new params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv], "users_and_enterprises_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def xero_invoices
|
||||
params[:q] ||= {}
|
||||
|
||||
@distributors = my_distributors
|
||||
@order_cycles = my_order_cycles
|
||||
|
||||
@report = OpenFoodNetwork::XeroInvoicesReport.new spree_current_user, params, render_content?
|
||||
render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Some actions are changing the `params` object. That is unfortunate Spree
|
||||
# behavior and we are building on it. So we have to look at `params` early
|
||||
# to check if we are searching or just displaying a report search form.
|
||||
def cache_search_state
|
||||
search_keys = [
|
||||
# search parameter for ransack
|
||||
:q,
|
||||
# common in all reports, only set for CSV rendering
|
||||
:csv,
|
||||
# `button` is included in all forms. It's not important for searching,
|
||||
# but the Users & Enterprises report doesn't have any other parameter
|
||||
# for an empty search. So we use this one to display data.
|
||||
:button,
|
||||
# Some reports use filtering by enterprise or order cycle
|
||||
:distributor_id,
|
||||
:supplier_id,
|
||||
:order_cycle_id,
|
||||
# Xero Invoices can be filtered by date
|
||||
:invoice_date,
|
||||
:due_date
|
||||
]
|
||||
@searching = search_keys.any? { |key| params.key? key }
|
||||
end
|
||||
|
||||
# We don't want to render data unless search params are supplied.
|
||||
# Compiling data can take a long time.
|
||||
def render_content?
|
||||
@searching
|
||||
end
|
||||
|
||||
def render_report(header, table, create_csv, csv_file_name)
|
||||
send_data csv_report(header, table), filename: csv_file_name if create_csv
|
||||
@header = header
|
||||
@table = table
|
||||
# Rendering HTML is the default.
|
||||
end
|
||||
|
||||
def csv_report(header, table)
|
||||
CSV.generate do |csv|
|
||||
csv << header
|
||||
table.each { |row| csv << row }
|
||||
end
|
||||
end
|
||||
|
||||
def load_data
|
||||
@distributors = my_distributors
|
||||
@suppliers = my_suppliers | suppliers_of_products_distributed_by(@distributors)
|
||||
@order_cycles = my_order_cycles
|
||||
end
|
||||
|
||||
# Load managed distributor enterprises of current user
|
||||
def my_distributors
|
||||
Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
end
|
||||
|
||||
# Load managed producer enterprises of current user
|
||||
def my_suppliers
|
||||
Enterprise.is_primary_producer.managed_by(spree_current_user)
|
||||
end
|
||||
|
||||
def suppliers_of_products_distributed_by(distributors)
|
||||
distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq
|
||||
end
|
||||
|
||||
# Load order cycles the current user has access to
|
||||
def my_order_cycles
|
||||
OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
|
||||
end
|
||||
|
||||
def order_grouper_table
|
||||
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
|
||||
order_grouper.table(@report.table_items)
|
||||
end
|
||||
|
||||
def authorized_reports
|
||||
all_reports = [
|
||||
:orders_and_distributors,
|
||||
:bulk_coop,
|
||||
:payments,
|
||||
:orders_and_fulfillment,
|
||||
:customers,
|
||||
:products_and_inventory,
|
||||
:sales_total,
|
||||
:users_and_enterprises,
|
||||
:enterprise_fee_summary,
|
||||
:order_cycle_management,
|
||||
:sales_tax,
|
||||
:xero_invoices,
|
||||
:packing
|
||||
]
|
||||
reports = all_reports.select { |action| can? action, Spree::Admin::ReportsController }
|
||||
reports.map { |report| [report, describe_report(report)] }.to_h
|
||||
end
|
||||
|
||||
def describe_report(report)
|
||||
name = I18n.t(:name, scope: [:admin, :reports, report])
|
||||
description = begin
|
||||
I18n.t!(:description, scope: [:admin, :reports, report])
|
||||
rescue I18n::MissingTranslationData
|
||||
render_to_string(
|
||||
partial: "#{report}_description",
|
||||
layout: false,
|
||||
locals: { report_types: report_types[report] }
|
||||
).html_safe
|
||||
end
|
||||
{ name: name, url: url_for_report(report), description: description }
|
||||
end
|
||||
|
||||
def url_for_report(report)
|
||||
public_send("#{report}_admin_reports_url".to_sym)
|
||||
rescue NoMethodError
|
||||
url_for([:new, :admin, :reports, report.to_s.singularize])
|
||||
end
|
||||
|
||||
def timestamp
|
||||
Time.zone.now.strftime("%Y%m%d")
|
||||
end
|
||||
|
||||
def orders_and_fulfillment_default_filters
|
||||
now = Time.zone.now
|
||||
{ completed_at_gt: (now - 1.month).beginning_of_day,
|
||||
completed_at_lt: (now + 1.day).beginning_of_day }
|
||||
end
|
||||
end
|
||||
275
app/controllers/spree/admin/resource_controller.rb
Normal file
275
app/controllers/spree/admin/resource_controller.rb
Normal file
@@ -0,0 +1,275 @@
|
||||
require 'action_callbacks'
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class ResourceController < Spree::Admin::BaseController
|
||||
helper_method :new_object_url, :edit_object_url, :object_url, :collection_url
|
||||
before_filter :load_resource, except: [:update_positions]
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :resource_not_found
|
||||
rescue_from CanCan::AccessDenied, with: :unauthorized
|
||||
|
||||
respond_to :html
|
||||
respond_to :js, except: [:show, :index]
|
||||
|
||||
def new
|
||||
invoke_callbacks(:new_action, :before)
|
||||
respond_with(@object) do |format|
|
||||
format.html { render layout: !request.xhr? }
|
||||
format.js { render layout: false }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_with(@object) do |format|
|
||||
format.html { render layout: !request.xhr? }
|
||||
format.js { render layout: false }
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
invoke_callbacks(:update, :before)
|
||||
if @object.update_attributes(params[object_name])
|
||||
invoke_callbacks(:update, :after)
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
respond_with(@object) do |format|
|
||||
format.html { redirect_to location_after_save }
|
||||
format.js { render layout: false }
|
||||
end
|
||||
else
|
||||
invoke_callbacks(:update, :fails)
|
||||
respond_with(@object)
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
invoke_callbacks(:create, :before)
|
||||
@object.attributes = params[object_name]
|
||||
if @object.save
|
||||
invoke_callbacks(:create, :after)
|
||||
flash[:success] = flash_message_for(@object, :successfully_created)
|
||||
respond_with(@object) do |format|
|
||||
format.html { redirect_to location_after_save }
|
||||
format.js { render layout: false }
|
||||
end
|
||||
else
|
||||
invoke_callbacks(:create, :fails)
|
||||
respond_with(@object)
|
||||
end
|
||||
end
|
||||
|
||||
def update_positions
|
||||
params[:positions].each do |id, index|
|
||||
model_class.where(id: id).update_all(position: index)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js { render text: 'Ok' }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
invoke_callbacks(:destroy, :before)
|
||||
if @object.destroy
|
||||
invoke_callbacks(:destroy, :after)
|
||||
flash[:success] = flash_message_for(@object, :successfully_removed)
|
||||
respond_with(@object) do |format|
|
||||
format.html { redirect_to collection_url }
|
||||
format.js { render partial: "spree/admin/shared/destroy" }
|
||||
end
|
||||
else
|
||||
invoke_callbacks(:destroy, :fails)
|
||||
respond_with(@object) do |format|
|
||||
format.html { redirect_to collection_url }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def resource_not_found
|
||||
flash[:error] = flash_message_for(model_class.new, :not_found)
|
||||
redirect_to collection_url
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :parent_data
|
||||
attr_accessor :callbacks
|
||||
|
||||
def belongs_to(model_name, options = {})
|
||||
@parent_data ||= {}
|
||||
@parent_data[:model_name] = model_name
|
||||
@parent_data[:model_class] = model_name.to_s.classify.constantize
|
||||
@parent_data[:find_by] = options[:find_by] || :id
|
||||
end
|
||||
|
||||
def new_action
|
||||
@callbacks ||= {}
|
||||
@callbacks[:new_action] ||= ActionCallbacks.new
|
||||
end
|
||||
|
||||
def create
|
||||
@callbacks ||= {}
|
||||
@callbacks[:create] ||= ActionCallbacks.new
|
||||
end
|
||||
|
||||
def update
|
||||
@callbacks ||= {}
|
||||
@callbacks[:update] ||= ActionCallbacks.new
|
||||
end
|
||||
|
||||
def destroy
|
||||
@callbacks ||= {}
|
||||
@callbacks[:destroy] ||= ActionCallbacks.new
|
||||
end
|
||||
end
|
||||
|
||||
def model_class
|
||||
"Spree::#{controller_name.classify}".constantize
|
||||
end
|
||||
|
||||
def model_name
|
||||
parent_data[:model_name].gsub('spree/', '')
|
||||
end
|
||||
|
||||
def object_name
|
||||
controller_name.singularize
|
||||
end
|
||||
|
||||
def load_resource
|
||||
if member_action?
|
||||
@object ||= load_resource_instance
|
||||
|
||||
# call authorize! a third time (called twice already in Admin::BaseController)
|
||||
# this time we pass the actual instance so fine-grained abilities can control
|
||||
# access to individual records, not just entire models.
|
||||
authorize! action, @object
|
||||
|
||||
instance_variable_set("@#{object_name}", @object)
|
||||
|
||||
# If we don't have access, clear the object
|
||||
unless can? action, @object
|
||||
instance_variable_set("@#{object_name}", nil)
|
||||
end
|
||||
|
||||
authorize! action, @object
|
||||
else
|
||||
@collection ||= collection
|
||||
|
||||
# note: we don't call authorize here as the collection method should use
|
||||
# CanCan's accessible_by method to restrict the actual records returned
|
||||
|
||||
instance_variable_set("@#{controller_name}", @collection)
|
||||
end
|
||||
end
|
||||
|
||||
def load_resource_instance
|
||||
if new_actions.include?(action)
|
||||
build_resource
|
||||
elsif params[:id]
|
||||
find_resource
|
||||
end
|
||||
end
|
||||
|
||||
def parent_data
|
||||
self.class.parent_data
|
||||
end
|
||||
|
||||
def parent
|
||||
return nil if parent_data.blank?
|
||||
|
||||
@parent ||= parent_data[:model_class].
|
||||
public_send("find_by_#{parent_data[:find_by]}", params["#{model_name}_id"])
|
||||
instance_variable_set("@#{model_name}", @parent)
|
||||
end
|
||||
|
||||
def find_resource
|
||||
if parent_data.present?
|
||||
parent.public_send(controller_name).find(params[:id])
|
||||
else
|
||||
model_class.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def build_resource
|
||||
if parent_data.present?
|
||||
parent.public_send(controller_name).build
|
||||
else
|
||||
model_class.new
|
||||
end
|
||||
end
|
||||
|
||||
def collection
|
||||
return parent.public_send(controller_name) if parent_data.present?
|
||||
|
||||
if model_class.respond_to?(:accessible_by) &&
|
||||
!current_ability.has_block?(params[:action], model_class)
|
||||
model_class.accessible_by(current_ability, action)
|
||||
else
|
||||
model_class.scoped
|
||||
end
|
||||
end
|
||||
|
||||
def location_after_save
|
||||
collection_url
|
||||
end
|
||||
|
||||
def invoke_callbacks(action, callback_type)
|
||||
callbacks = self.class.callbacks || {}
|
||||
return if callbacks[action].nil?
|
||||
|
||||
case callback_type.to_sym
|
||||
when :before then callbacks[action].before_methods.each { |method| __send__ method }
|
||||
when :after then callbacks[action].after_methods.each { |method| __send__ method }
|
||||
when :fails then callbacks[action].fails_methods.each { |method| __send__ method }
|
||||
end
|
||||
end
|
||||
|
||||
# URL helpers
|
||||
|
||||
def new_object_url(options = {})
|
||||
if parent_data.present?
|
||||
spree.new_polymorphic_url([:admin, parent, model_class], options)
|
||||
else
|
||||
spree.new_polymorphic_url([:admin, model_class], options)
|
||||
end
|
||||
end
|
||||
|
||||
def edit_object_url(object, options = {})
|
||||
if parent_data.present?
|
||||
spree.public_send "edit_admin_#{model_name}_#{object_name}_url", parent, object, options
|
||||
else
|
||||
spree.public_send "edit_admin_#{object_name}_url", object, options
|
||||
end
|
||||
end
|
||||
|
||||
def object_url(object = nil, options = {})
|
||||
target = object || @object
|
||||
if parent_data.present?
|
||||
spree.public_send "admin_#{model_name}_#{object_name}_url", parent, target, options
|
||||
else
|
||||
spree.public_send "admin_#{object_name}_url", target, options
|
||||
end
|
||||
end
|
||||
|
||||
def collection_url(options = {})
|
||||
if parent_data.present?
|
||||
spree.polymorphic_url([:admin, parent, model_class], options)
|
||||
else
|
||||
spree.polymorphic_url([:admin, model_class], options)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index]
|
||||
end
|
||||
|
||||
def member_action?
|
||||
!collection_actions.include? action
|
||||
end
|
||||
|
||||
def new_actions
|
||||
[:new, :create]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
module AuthorizeOnLoadResource
|
||||
def load_resource
|
||||
super
|
||||
|
||||
if member_action?
|
||||
# If we don't have access, clear the object
|
||||
unless can? action, @object
|
||||
instance_variable_set("@#{object_name}", nil)
|
||||
end
|
||||
|
||||
authorize! action, @object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Spree::Admin::ResourceController.prepend(AuthorizeOnLoadResource)
|
||||
|
||||
Spree::Admin::ResourceController.class_eval do
|
||||
rescue_from CanCan::AccessDenied, with: :unauthorized
|
||||
end
|
||||
@@ -176,7 +176,7 @@ module Spree
|
||||
previous_states = @order.adjustments.each_with_object({}) do |adjustment, hash|
|
||||
hash[adjustment.id] = adjustment.state
|
||||
end
|
||||
@order.adjustments.each(&:open)
|
||||
@order.adjustments.each { |adjustment| adjustment.fire_events(:open) }
|
||||
|
||||
yield
|
||||
|
||||
|
||||
11
app/helpers/admin/enterprises_helper.rb
Normal file
11
app/helpers/admin/enterprises_helper.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module Admin
|
||||
module EnterprisesHelper
|
||||
def add_check_if_single(count)
|
||||
if count == 1
|
||||
{ checked: true }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -20,12 +20,27 @@ module ShopHelper
|
||||
)
|
||||
end
|
||||
|
||||
def shop_tabs
|
||||
def base_shop_tabs(column_sizes)
|
||||
[
|
||||
{ name: 'about', title: t(:shopping_tabs_about, distributor: current_distributor.name), cols: 6 },
|
||||
{ name: 'producers', title: t(:label_producers), cols: 2 },
|
||||
{ name: 'contact', title: t(:shopping_tabs_contact), cols: 2 },
|
||||
{ name: 'groups', title: t(:label_groups), cols: 2 },
|
||||
{ name: 'about', cols: column_sizes[0],
|
||||
title: t(:shopping_tabs_about, distributor: current_distributor.name) },
|
||||
{ name: 'producers', cols: column_sizes[1],
|
||||
title: t(:label_producers) },
|
||||
{ name: 'contact', cols: column_sizes[2],
|
||||
title: t(:shopping_tabs_contact) }
|
||||
]
|
||||
end
|
||||
|
||||
def tabs_with_groups
|
||||
tabs = base_shop_tabs([6, 2, 2])
|
||||
tabs << { name: 'groups', title: t(:label_groups), cols: 2 }
|
||||
end
|
||||
|
||||
def tabs_without_groups
|
||||
base_shop_tabs([4, 4, 4])
|
||||
end
|
||||
|
||||
def shop_tabs
|
||||
current_distributor.groups.present? ? tabs_with_groups : tabs_without_groups
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'spree/admin/base_helper'
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module BaseHelper
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'spree/admin/navigation_helper'
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module NavigationHelper
|
||||
|
||||
@@ -25,7 +25,7 @@ module Calculator
|
||||
end
|
||||
|
||||
def line_item_weight(line_item)
|
||||
if line_item.final_weight_volume.present?
|
||||
if final_weight_volume_present?(line_item)
|
||||
weight_per_final_weight_volume(line_item)
|
||||
else
|
||||
weight_per_variant(line_item) * line_item.quantity
|
||||
@@ -33,13 +33,18 @@ module Calculator
|
||||
end
|
||||
|
||||
def weight_per_variant(line_item)
|
||||
line_item.variant.andand.weight || 0
|
||||
if variant_unit(line_item) == 'weight'
|
||||
# The calculator price is per_kg so we need to convert unit_value to kg
|
||||
convert_g_to_kg(line_item.variant.andand.unit_value)
|
||||
else
|
||||
line_item.variant.andand.weight || 0
|
||||
end
|
||||
end
|
||||
|
||||
def weight_per_final_weight_volume(line_item)
|
||||
if line_item.variant.product.andand.variant_unit == 'weight'
|
||||
# Divided by 1000 because grams is the base weight unit and the calculator price is per_kg
|
||||
line_item.final_weight_volume / 1000.0
|
||||
if variant_unit(line_item) == 'weight'
|
||||
# The calculator price is per_kg so we need to convert final_weight_volume to kg
|
||||
convert_g_to_kg(line_item.final_weight_volume)
|
||||
else
|
||||
weight_per_variant(line_item) * quantity_implied_in_final_weight_volume(line_item)
|
||||
end
|
||||
@@ -51,5 +56,19 @@ module Calculator
|
||||
def quantity_implied_in_final_weight_volume(line_item)
|
||||
(1.0 * line_item.final_weight_volume / line_item.variant.unit_value).round(3)
|
||||
end
|
||||
|
||||
def final_weight_volume_present?(line_item)
|
||||
line_item.respond_to?(:final_weight_volume) && line_item.final_weight_volume.present?
|
||||
end
|
||||
|
||||
def variant_unit(line_item)
|
||||
line_item.variant.product.andand.variant_unit
|
||||
end
|
||||
|
||||
def convert_g_to_kg(value)
|
||||
return 0 unless value
|
||||
|
||||
value / 1000
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
class EnterpriseRelationshipPermission < ActiveRecord::Base
|
||||
default_scope order('name')
|
||||
default_scope { order('name') }
|
||||
end
|
||||
|
||||
@@ -142,9 +142,9 @@ class OrderCycle < ActiveRecord::Base
|
||||
oc.name = I18n.t("models.order_cycle.cloned_order_cycle_name", order_cycle: oc.name)
|
||||
oc.orders_open_at = oc.orders_close_at = nil
|
||||
oc.coordinator_fee_ids = coordinator_fee_ids
|
||||
# rubocop:disable Metrics/LineLength
|
||||
# rubocop:disable Layout/LineLength
|
||||
oc.preferred_product_selection_from_coordinator_inventory_only = preferred_product_selection_from_coordinator_inventory_only
|
||||
# rubocop:enable Metrics/LineLength
|
||||
# rubocop:enable Layout/LineLength
|
||||
oc.save!
|
||||
exchanges.each { |e| e.clone!(oc) }
|
||||
oc.reload
|
||||
|
||||
@@ -2,7 +2,7 @@ class ProducerProperty < ActiveRecord::Base
|
||||
belongs_to :producer, class_name: 'Enterprise', touch: true
|
||||
belongs_to :property, class_name: 'Spree::Property'
|
||||
|
||||
default_scope order("#{table_name}.position")
|
||||
default_scope { order("#{table_name}.position") }
|
||||
|
||||
scope :ever_sold_by, ->(shop) {
|
||||
joins(producer: { supplied_products: { variants: { exchanges: :order_cycle } } }).
|
||||
|
||||
@@ -214,7 +214,9 @@ module ProductImport
|
||||
end
|
||||
|
||||
def accepted_mimetype
|
||||
File.extname(@file.path).in?('.csv', '.xls', '.xlsx', '.ods') ? @file.path.split('.').last.to_sym : false
|
||||
return false unless ['.csv'].include? File.extname(@file.path)
|
||||
|
||||
@file.path.split('.').last.to_sym
|
||||
end
|
||||
|
||||
def headers
|
||||
|
||||
@@ -45,11 +45,11 @@ module ProductImport
|
||||
|
||||
next if @enterprises_index.key? enterprise_name
|
||||
|
||||
enterprise = Enterprise.find_by_name(enterprise_name, select: 'id, is_primary_producer')
|
||||
enterprise = Enterprise.select([:id, :is_primary_producer]).
|
||||
where(name: enterprise_name).first
|
||||
|
||||
@enterprises_index[enterprise_name] =
|
||||
{ id: enterprise.try(:id),
|
||||
is_primary_producer: enterprise.try(:is_primary_producer) }
|
||||
{ id: enterprise.try(:id), is_primary_producer: enterprise.try(:is_primary_producer) }
|
||||
end
|
||||
@enterprises_index
|
||||
end
|
||||
@@ -60,7 +60,8 @@ module ProductImport
|
||||
next unless entry.producer
|
||||
|
||||
producer_name = entry.producer
|
||||
producer_id = @producers_index[producer_name] || Enterprise.find_by_name(producer_name, select: 'id, name').try(:id)
|
||||
producer_id = @producers_index[producer_name] ||
|
||||
Enterprise.select([:id, :name]).where(name: producer_name).first.try(:id)
|
||||
@producers_index[producer_name] = producer_id
|
||||
end
|
||||
@producers_index
|
||||
@@ -70,7 +71,8 @@ module ProductImport
|
||||
@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)
|
||||
category_id = @categories_index[category_name] ||
|
||||
Spree::Taxon.select([:id, :name]).where(name: category_name).first.try(:id)
|
||||
@categories_index[category_name] = category_id
|
||||
end
|
||||
@categories_index
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
module Spree
|
||||
class User < ActiveRecord::Base
|
||||
include Core::UserBanners
|
||||
|
||||
devise :database_authenticatable, :token_authenticatable, :registerable, :recoverable,
|
||||
:rememberable, :trackable, :validatable, :encryptable, encryptor: 'authlogic_sha512'
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ class SubscriptionLineItem < ActiveRecord::Base
|
||||
validates :variant, presence: true
|
||||
validates :quantity, presence: true, numericality: { only_integer: true }
|
||||
|
||||
default_scope { order('id ASC') }
|
||||
|
||||
def total_estimate
|
||||
(price_estimate || 0) * (quantity || 0)
|
||||
end
|
||||
@@ -22,6 +24,4 @@ class SubscriptionLineItem < ActiveRecord::Base
|
||||
def price
|
||||
price_estimate
|
||||
end
|
||||
|
||||
default_scope order('id ASC')
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ class VariantOverride < ActiveRecord::Base
|
||||
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
|
||||
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
|
||||
default_scope where(permission_revoked_at: nil)
|
||||
default_scope { where(permission_revoked_at: nil) }
|
||||
|
||||
scope :for_hubs, lambda { |hubs|
|
||||
where(hub_id: hubs)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require 'open_food_network/enterprise_issue_validator'
|
||||
|
||||
class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :managed, :supplied_products,
|
||||
attributes :id, :name, :managed,
|
||||
:issues_summary_supplier, :issues_summary_distributor,
|
||||
:is_primary_producer, :is_distributor, :sells
|
||||
|
||||
@@ -25,20 +25,14 @@ class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer
|
||||
Enterprise.managed_by(options[:spree_current_user]).include? object
|
||||
end
|
||||
|
||||
def supplied_products
|
||||
serializer = Api::Admin::ForOrderCycle::SuppliedProductSerializer
|
||||
ActiveModel::ArraySerializer.new(products, each_serializer: serializer,
|
||||
order_cycle: order_cycle)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def products_scope
|
||||
products_relation = object.supplied_products
|
||||
if order_cycle.prefers_product_selection_from_coordinator_inventory_only?
|
||||
object.supplied_products.visible_for(order_cycle.coordinator)
|
||||
else
|
||||
object.supplied_products
|
||||
products_relation = products_relation.visible_for(order_cycle.coordinator)
|
||||
end
|
||||
products_relation.order(:name)
|
||||
end
|
||||
|
||||
def products
|
||||
|
||||
@@ -14,7 +14,8 @@ class Api::Admin::ForOrderCycle::SuppliedProductSerializer < ActiveModel::Serial
|
||||
end
|
||||
|
||||
def variants
|
||||
variants = if order_cycle.prefers_product_selection_from_coordinator_inventory_only?
|
||||
variants = if order_cycle.present? &&
|
||||
order_cycle.prefers_product_selection_from_coordinator_inventory_only?
|
||||
object.variants.visible_for(order_cycle.coordinator)
|
||||
else
|
||||
object.variants
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
attributes :id, :number, :user_id, :full_name, :email, :phone, :completed_at, :display_total,
|
||||
:edit_path, :state, :payment_state, :shipment_state,
|
||||
:payments_path, :ship_path, :ready_to_ship, :created_at,
|
||||
:distributor_name, :special_instructions, :payment_capture_path,
|
||||
:payments_path, :ready_to_ship, :ready_to_capture, :created_at,
|
||||
:distributor_name, :special_instructions,
|
||||
:item_total, :adjustment_total, :payment_total, :total
|
||||
|
||||
has_one :distributor, serializer: Api::Admin::IdSerializer
|
||||
@@ -28,15 +28,9 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
spree_routes_helper.admin_order_payments_path(object)
|
||||
end
|
||||
|
||||
def ship_path
|
||||
spree_routes_helper.fire_admin_order_path(object, e: 'ship')
|
||||
end
|
||||
|
||||
def payment_capture_path
|
||||
def ready_to_capture
|
||||
pending_payment = object.pending_payments.first
|
||||
return '' unless object.payment_required? && pending_payment
|
||||
|
||||
spree_routes_helper.fire_admin_order_payment_path(object, pending_payment.id, e: 'capture')
|
||||
object.payment_required? && pending_payment
|
||||
end
|
||||
|
||||
def ready_to_ship
|
||||
|
||||
23
app/services/action_callbacks.rb
Normal file
23
app/services/action_callbacks.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
class ActionCallbacks
|
||||
attr_reader :before_methods
|
||||
attr_reader :after_methods
|
||||
attr_reader :fails_methods
|
||||
|
||||
def initialize
|
||||
@before_methods = []
|
||||
@after_methods = []
|
||||
@fails_methods = []
|
||||
end
|
||||
|
||||
def before(method)
|
||||
@before_methods << method
|
||||
end
|
||||
|
||||
def after(method)
|
||||
@after_methods << method
|
||||
end
|
||||
|
||||
def fails(method)
|
||||
@fails_methods << method
|
||||
end
|
||||
end
|
||||
40
app/services/current_order_locker.rb
Normal file
40
app/services/current_order_locker.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# Locks a controller's current order including its variants.
|
||||
#
|
||||
# It should be used when making major changes like checking out the order.
|
||||
# It can keep stock checking in sync and prevent overselling of an item.
|
||||
class CurrentOrderLocker
|
||||
# This interface follows the ActionController filters convention:
|
||||
#
|
||||
# https://guides.rubyonrails.org/action_controller_overview.html#filters
|
||||
#
|
||||
def self.around(controller)
|
||||
lock_order_and_variants(controller.current_order) { yield }
|
||||
end
|
||||
|
||||
# Locking will not prevent all access to these rows. Other processes are
|
||||
# only waiting if they try to lock one of these rows as well.
|
||||
#
|
||||
# https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
|
||||
#
|
||||
def self.lock_order_and_variants(order)
|
||||
return yield if order.nil?
|
||||
|
||||
order.with_lock do
|
||||
lock_variants_of(order)
|
||||
yield
|
||||
end
|
||||
end
|
||||
private_class_method :lock_order_and_variants
|
||||
|
||||
# There are many places in which stock is stored in the database. Row locking
|
||||
# on variant level ensures that there are no conflicts even when an item is
|
||||
# sold through multiple shops.
|
||||
def self.lock_variants_of(order)
|
||||
variant_ids = order.line_items.select(:variant_id)
|
||||
|
||||
# Ordering the variants by id prevents deadlocks. Plucking the ids sends
|
||||
# the locking query without building Spree::Variant objects.
|
||||
Spree::Variant.where(id: variant_ids).order(:id).lock.pluck(:id)
|
||||
end
|
||||
private_class_method :lock_variants_of
|
||||
end
|
||||
86
app/services/exchange_products_renderer.rb
Normal file
86
app/services/exchange_products_renderer.rb
Normal file
@@ -0,0 +1,86 @@
|
||||
class ExchangeProductsRenderer
|
||||
def initialize(order_cycle, user)
|
||||
@order_cycle = order_cycle
|
||||
@user = user
|
||||
end
|
||||
|
||||
def exchange_products(incoming, enterprise)
|
||||
if incoming
|
||||
products_for_incoming_exchange(enterprise)
|
||||
else
|
||||
products_for_outgoing_exchange
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def products_for_incoming_exchange(enterprise)
|
||||
supplied_products(enterprise.id)
|
||||
end
|
||||
|
||||
def supplied_products(enterprises_query_matcher)
|
||||
products_relation = Spree::Product.where(supplier_id: enterprises_query_matcher)
|
||||
|
||||
if @order_cycle.present? &&
|
||||
@order_cycle.prefers_product_selection_from_coordinator_inventory_only?
|
||||
products_relation = products_relation.visible_for(@order_cycle.coordinator)
|
||||
end
|
||||
|
||||
products_relation
|
||||
end
|
||||
|
||||
def products_for_outgoing_exchange
|
||||
supplied_products(enterprises_for_outgoing_exchange.select(:id)).
|
||||
includes(:variants).
|
||||
where("spree_variants.id": incoming_exchanges_variants)
|
||||
end
|
||||
|
||||
def incoming_exchanges_variants
|
||||
return @incoming_exchanges_variants if @incoming_exchanges_variants.present?
|
||||
|
||||
@incoming_exchanges_variants = []
|
||||
visible_incoming_exchanges.each do |incoming_exchange|
|
||||
@incoming_exchanges_variants.push(
|
||||
*incoming_exchange.variants.merge(
|
||||
visible_incoming_variants(incoming_exchange.sender)
|
||||
).map(&:id).to_a
|
||||
)
|
||||
end
|
||||
@incoming_exchanges_variants
|
||||
end
|
||||
|
||||
def visible_incoming_exchanges
|
||||
OpenFoodNetwork::OrderCyclePermissions.
|
||||
new(@user, @order_cycle).
|
||||
visible_exchanges.
|
||||
by_enterprise_name.
|
||||
incoming
|
||||
end
|
||||
|
||||
def visible_incoming_variants(incoming_exchange_sender)
|
||||
variants_relation = permitted_incoming_variants(incoming_exchange_sender)
|
||||
|
||||
if @order_cycle.prefers_product_selection_from_coordinator_inventory_only?
|
||||
variants_relation = variants_relation.visible_for(@order_cycle.coordinator)
|
||||
end
|
||||
|
||||
variants_relation
|
||||
end
|
||||
|
||||
def permitted_incoming_variants(incoming_exchange_sender)
|
||||
OpenFoodNetwork::OrderCyclePermissions.
|
||||
new(@user, @order_cycle).
|
||||
visible_variants_for_incoming_exchanges_from(incoming_exchange_sender)
|
||||
end
|
||||
|
||||
def enterprises_for_outgoing_exchange
|
||||
enterprises = OpenFoodNetwork::OrderCyclePermissions.
|
||||
new(@user, @order_cycle)
|
||||
.visible_enterprises
|
||||
return enterprises if enterprises.empty?
|
||||
|
||||
enterprises.includes(
|
||||
supplied_products: [:supplier, :variants, master: [:images]]
|
||||
)
|
||||
end
|
||||
end
|
||||
98
app/services/permissions/order.rb
Normal file
98
app/services/permissions/order.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
module Permissions
|
||||
class Order
|
||||
def initialize(user)
|
||||
@user = user
|
||||
@permissions = OpenFoodNetwork::Permissions.new(@user)
|
||||
end
|
||||
|
||||
# Find orders that the user can see
|
||||
def visible_orders
|
||||
Spree::Order.
|
||||
with_line_items_variants_and_products_outer.
|
||||
where(visible_orders_where_values)
|
||||
end
|
||||
|
||||
# Any orders that the user can edit
|
||||
def editable_orders
|
||||
Spree::Order.where(
|
||||
managed_orders_where_values.
|
||||
or(coordinated_orders_where_values)
|
||||
)
|
||||
end
|
||||
|
||||
def visible_line_items
|
||||
Spree::LineItem.where(id:
|
||||
editable_line_items.select(:id) |
|
||||
produced_line_items.select("spree_line_items.id"))
|
||||
end
|
||||
|
||||
# Any line items that I can edit
|
||||
def editable_line_items
|
||||
Spree::LineItem.where(order_id: editable_orders.select(:id))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def visible_orders_where_values
|
||||
# Grouping keeps the 2 where clauses from produced_orders_where_values inside parentheses
|
||||
# This way it makes the OR work between the 3 types of orders:
|
||||
# produced, managed and coordinated
|
||||
Spree::Order.arel_table.
|
||||
grouping(produced_orders_where_values).
|
||||
or(managed_orders_where_values).
|
||||
or(coordinated_orders_where_values)
|
||||
end
|
||||
|
||||
# Any orders placed through any hub that I manage
|
||||
def managed_orders_where_values
|
||||
Spree::Order.
|
||||
where(distributor_id: @permissions.managed_enterprises.select("enterprises.id")).
|
||||
where_values.
|
||||
reduce(:and)
|
||||
end
|
||||
|
||||
# Any order that is placed through an order cycle one of my managed enterprises coordinates
|
||||
def coordinated_orders_where_values
|
||||
Spree::Order.
|
||||
where(order_cycle_id: @permissions.coordinated_order_cycles.select(:id)).
|
||||
where_values.
|
||||
reduce(:and)
|
||||
end
|
||||
|
||||
def produced_orders_where_values
|
||||
Spree::Order.with_line_items_variants_and_products_outer.
|
||||
where(
|
||||
distributor_id: granted_distributor_ids,
|
||||
spree_products: { supplier_id: enterprises_with_associated_orders }
|
||||
).
|
||||
where_values.
|
||||
reduce(:and)
|
||||
end
|
||||
|
||||
def enterprises_with_associated_orders
|
||||
# Any orders placed through hubs that my producers have granted P-OC,
|
||||
# and which contain their products. This is pretty complicated but it's looking for order
|
||||
# where at least one of my producers has granted P-OC to the distributor
|
||||
# AND the order contains products of at least one of THE SAME producers
|
||||
@permissions.related_enterprises_granting(:add_to_order_cycle, to: granted_distributor_ids).
|
||||
merge(@permissions.managed_enterprises.is_primary_producer)
|
||||
end
|
||||
|
||||
def granted_distributor_ids
|
||||
@granted_distributor_ids ||= @permissions.related_enterprises_granted(
|
||||
:add_to_order_cycle,
|
||||
by: @permissions.managed_enterprises.is_primary_producer.select("enterprises.id")
|
||||
).select("enterprises.id")
|
||||
end
|
||||
|
||||
# Any from visible orders, where the product is produced by one of my managed producers
|
||||
def produced_line_items
|
||||
Spree::LineItem.where(order_id: visible_orders.select("DISTINCT spree_orders.id")).
|
||||
joins(:product).
|
||||
where(spree_products:
|
||||
{
|
||||
supplier_id: @permissions.managed_enterprises.is_primary_producer.select("enterprises.id")
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -66,7 +66,7 @@ class ProductsRenderer
|
||||
.split(",").map { |id| "spree_products.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ") + ", spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC"
|
||||
"spree_products.name ASC, spree_products.id"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class SearchOrders
|
||||
attr_reader :params, :current_user
|
||||
|
||||
def fetch_orders
|
||||
@search = OpenFoodNetwork::Permissions.new(current_user).editable_orders.ransack(params[:q])
|
||||
@search = ::Permissions::Order.new(current_user).editable_orders.ransack(params[:q])
|
||||
|
||||
return paginated_results if using_pagination?
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
%tr{ ng: { class: "'#{type} #{type}-{{ exchange.enterprise_id }}'" } }
|
||||
%td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }}
|
||||
%td.products.panel-toggle.text-center{ name: "products" }
|
||||
{{ exchangeSelectedVariants(exchange) }} /
|
||||
- if type == 'supplier'
|
||||
{{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }}
|
||||
- else
|
||||
{{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }}
|
||||
{{ exchangeSelectedVariants(exchange) }} / {{ exchangeTotalVariants(exchange) }}
|
||||
= t('.selected')
|
||||
- if type == 'supplier'
|
||||
%td.receival-details
|
||||
@@ -34,11 +30,11 @@
|
||||
|
||||
- if type == 'supplier'
|
||||
%tr.panel-row{ object: "exchange",
|
||||
panels: "{products: 'exchange_supplied_products'}",
|
||||
locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,suppliedVariants,removeDistributionOfVariant",
|
||||
panels: "{products: 'exchange_products_supplied'}",
|
||||
locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,selectAllVariants,suppliedVariants,removeDistributionOfVariant,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading",
|
||||
colspan: 4 }
|
||||
- if type == 'distributor'
|
||||
%tr.panel-row{ object: "exchange",
|
||||
panels: "{products: 'exchange_distributed_products', tags: 'exchange_tags'}",
|
||||
locals: "$index,order_cycle,exchange,supplied_products,setExchangeVariants,incomingExchangeVariantsFor,productSuppliedToOrderCycle,variantSuppliedToOrderCycle",
|
||||
panels: "{products: 'exchange_products_distributed', tags: 'exchange_tags'}",
|
||||
locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading",
|
||||
colspan: 5 }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
%table.exchanges
|
||||
%tbody{ng: {repeat: "exchange in order_cycle.incoming_exchanges"}}
|
||||
%tr.products
|
||||
%td{ ng: { include: "'admin/panels/exchange_supplied_products.html'" } }
|
||||
%td{ ng: { include: "'admin/panels/exchange_products_simple.html'" } }
|
||||
|
||||
%br/
|
||||
= label_tag t('.fees')
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
= render 'wizard_progress'
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
%input{ type: "button", value: t('.previous'), ng: { click: "cancel('#{main_app.edit_admin_order_cycle_path(@order_cycle)}')", disabled: "order_cycle_form.$dirty" } }
|
||||
%input.red{ type: "button", value: t('.save'), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
|
||||
%input.red{ type: "button", value: t('.save_and_next'), ng: { click: "submit($event, '#{main_app.admin_order_cycle_outgoing_path(@order_cycle)}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
|
||||
%input{ type: "button", value: t('.next'), ng: { click: "cancel('#{main_app.admin_order_cycle_outgoing_path(@order_cycle)}')", disabled: "order_cycle_form.$dirty" } }
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
= render 'wizard_progress'
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
%input{ type: "button", value: t('.previous'), ng: { click: "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", disabled: "order_cycle_form.$dirty" } }
|
||||
%input.red{ type: "button", value: t('.save'), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
|
||||
%input.red{ type: "button", value: t('.save_and_back_to_list'), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } }
|
||||
%input{ type: "button", ng: { value: "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", click: "cancel('#{main_app.admin_order_cycles_path}')" } }
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
%p
|
||||
= t(".email_userguide_html", link: link_to(t(".userguide"), ContentConfig.user_guide_link))
|
||||
%p
|
||||
= t(".email_admin_html", link: link_to(t(".admin_panel"), spree.admin_url))
|
||||
= t(".email_admin_html", link: link_to(t(".admin_panel"), spree.admin_dashboard_url))
|
||||
|
||||
%p
|
||||
= t(".email_community_html", link: link_to(t(".join_community"), ContentConfig.community_forum_url))
|
||||
|
||||
@@ -8,38 +8,6 @@
|
||||
.small-12.medium-8.medium-offset-2.columns.text-center
|
||||
.alert-box
|
||||
= render 'shared/register_call'
|
||||
.row
|
||||
.small-12.medium-4.medium-offset-2.columns.text-center
|
||||
%h6
|
||||
= t '.footer_global_headline'
|
||||
%p
|
||||
%a{href: "http://www.openfoodnetwork.org", target: "_blank"}
|
||||
= t '.footer_global_home'
|
||||
%span |
|
||||
%a{href: "http://www.openfoodnetwork.org/news/", target: "_blank"}
|
||||
= t '.footer_global_news'
|
||||
%span |
|
||||
%a{href: "http://www.openfoodnetwork.org/about/history-team/", target: "_blank"}
|
||||
= t '.footer_global_about'
|
||||
%span |
|
||||
%a{href: "http://www.openfoodnetwork.org/contact/", target: "_blank"}
|
||||
= t '.footer_global_contact'
|
||||
|
||||
.small-12.medium-4.columns.text-center
|
||||
%h6
|
||||
= t '.footer_sites_headline'
|
||||
%p
|
||||
%a{href: "http://dev.openfoodnetwork.org", target: "_blank"}
|
||||
= t '.footer_sites_developer'
|
||||
%span |
|
||||
%a{href: "http://community.openfoodnetwork.org", target: "_blank"}
|
||||
= t '.footer_sites_community'
|
||||
%span |
|
||||
%a{href: "http://www.openfoodnetwork.org/platform/user-guide/", target: "_blank"}
|
||||
= t '.footer_sites_userguide'
|
||||
|
||||
.medium-2.columns.text-center
|
||||
/ Placeholder
|
||||
|
||||
.footer-local
|
||||
.row
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
%section.right{"ng-cloak" => true}
|
||||
%span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
|
||||
%a.icon{href: main_app.checkout_path}
|
||||
%a.icon{href: main_app.cart_path}
|
||||
%span
|
||||
= t '.cart'
|
||||
%span.count
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
- if admin_user? or enterprise_user?
|
||||
%li
|
||||
%a{href: spree.admin_path, target:'_blank'}
|
||||
%a{href: spree.admin_dashboard_path, target:'_blank'}
|
||||
%i.ofn-i_021-tools
|
||||
= t 'label_administration'
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
- if admin_user? or enterprise_user?
|
||||
%li
|
||||
%a{href: spree.admin_path, target:'_blank'}
|
||||
%a{href: spree.admin_dashboard_path, target:'_blank'}
|
||||
%i.ofn-i_021-tools
|
||||
= t 'label_admin'
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@
|
||||
|
||||
= render :partial => 'adjustments_table'
|
||||
|
||||
= button_link_to t(:continue), @order.cart? ? new_admin_order_payment_url(@order) : admin_orders_url, :icon => 'icon-arrow-right'
|
||||
= button_link_to t(:continue), admin_order_payments_url(@order), :icon => 'icon-arrow-right'
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
%th.actions
|
||||
%tbody
|
||||
%tr{ng: {repeat: 'order in orders track by $index', class: {even: "'even'", odd: "'odd'"}}, 'ng-class' => "'state-{{order.state}}'"}
|
||||
%tr{ng: {repeat: 'order in orders track by order.id', class: {even: "'even'", odd: "'odd'"}}, 'ng-class' => "{'state-{{order.state}}': true, 'row-loading': rowStatus[order.id] == 'loading'}"}
|
||||
%td.align-center
|
||||
%input{type: 'checkbox', 'ng-model' => 'checkboxes[order.id]', 'ng-change' => 'toggleSelection(order.id)'}
|
||||
%td.align-center
|
||||
@@ -78,11 +78,15 @@
|
||||
%td.align-center
|
||||
%span{'ng-bind-html' => 'order.display_total'}
|
||||
%td.actions
|
||||
%div.row-loading-icons
|
||||
%img.spinner{src: "/assets/spinning-circles.svg", ng: {show: 'rowStatus[order.id] == "loading"'} }
|
||||
%i.success.icon-ok-sign{ng: {show: 'rowStatus[order.id] == "success"'} }
|
||||
%i.error.icon-remove-sign.with-tip{ng: {show: 'rowStatus[order.id] == "error"'}, 'ofn-with-tip' => t('.order_not_updated')}
|
||||
%a.icon_link.with-tip.icon-edit.no-text{'ng-href' => '{{order.edit_path}}', 'data-action' => 'edit', 'ofn-with-tip' => t('.edit')}
|
||||
%div{'ng-if' => 'order.ready_to_ship'}
|
||||
%a.icon-road.icon_link.with-tip.no-text{'ng-href' => '{{order.ship_path}}', 'data-action' => 'ship', 'data-confirm' => t(:are_you_sure), 'data-method' => 'put', rel: 'nofollow', 'ofn-with-tip' => t('.ship')}
|
||||
%div{'ng-if' => 'order.payment_capture_path'}
|
||||
%a.icon-capture.icon_link.no-text{'ng-href' => '{{order.payment_capture_path}}', 'data-action' => 'capture', 'data-method' => 'put', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
|
||||
%button.icon-road.icon_link.with-tip.no-text{'ng-click' => 'shipOrder(order)', 'data-confirm' => t(:are_you_sure), rel: 'nofollow', 'ofn-with-tip' => t('.ship')}
|
||||
%div{'ng-if' => 'order.ready_to_capture'}
|
||||
%button.icon-capture.icon_link.no-text{'ng-click' => 'capturePayment(order)', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
|
||||
|
||||
.orders-loading{'ng-show' => 'RequestMonitor.loading'}
|
||||
.row
|
||||
|
||||
@@ -44,14 +44,14 @@
|
||||
%td{ :align => "right" }
|
||||
= t :invoice_billing_address
|
||||
%br
|
||||
%strong= @order.ship_address.full_name
|
||||
%strong= @order.bill_address.full_name
|
||||
- if @order.andand.customer.andand.code.present?
|
||||
%br
|
||||
= "Code: #{@order.customer.code}"
|
||||
%br
|
||||
= @order.ship_address.address_part1
|
||||
= @order.bill_address.address_part1
|
||||
%br
|
||||
= @order.ship_address.address_part2
|
||||
= @order.bill_address.address_part2
|
||||
|
||||
= render 'spree/admin/orders/invoice_table2'
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<% content_for :page_title do %>
|
||||
<%= t(:listing_reports) %>
|
||||
<% end %>
|
||||
|
||||
<table class="index">
|
||||
<thead>
|
||||
<tr data-hook="reports_header">
|
||||
<th><%= t(:name) %></th>
|
||||
<th><%= t(:description) %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @reports.each do |key, value| %>
|
||||
<tr data-hook="reports_row">
|
||||
<td><%= link_to value[:name], value[:url] %></td>
|
||||
<td><%= value[:description] %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
13
app/views/spree/admin/reports/index.html.haml
Normal file
13
app/views/spree/admin/reports/index.html.haml
Normal file
@@ -0,0 +1,13 @@
|
||||
- content_for :page_title do
|
||||
= t(:listing_reports)
|
||||
|
||||
%table.index
|
||||
%thead
|
||||
%tr
|
||||
%th= t(:name)
|
||||
%th= t(:description)
|
||||
%tbody
|
||||
- @reports.each do |key, value|
|
||||
%tr
|
||||
%td= link_to value[:name], value[:url]
|
||||
%td= value[:description]
|
||||
@@ -1,24 +0,0 @@
|
||||
%tr{"data-hook" => "address1"}
|
||||
%td
|
||||
= t(:admin_shared_address_1):
|
||||
%td= f.text_field :address1
|
||||
%tr{"data-hook" => "address2" }
|
||||
%td
|
||||
= t(:admin_shared_address_2):
|
||||
%td= f.text_field :address2
|
||||
%tr{"data-hook" => "city" }
|
||||
%td
|
||||
= t(:admin_share_city):
|
||||
%td= f.text_field :city
|
||||
%tr{"data-hook" => "zipcode" }
|
||||
%td
|
||||
= t(:admin_share_zipcode):
|
||||
%td= f.text_field :zipcode
|
||||
%tr{"data-hook" => "country" }
|
||||
%td
|
||||
= t(:admin_share_country):
|
||||
%td= f.collection_select(:country_id, available_countries, :id, :name)
|
||||
%tr{"data-hook" => "state" }
|
||||
%td
|
||||
= t(:admin_share_state):
|
||||
%td= f.collection_select(:state_id, f.object.country.states, :id, :name)
|
||||
@@ -12,7 +12,7 @@
|
||||
%span.four.columns
|
||||
%span.three.columns.alpha
|
||||
%label
|
||||
= check_box klass, :distributor_ids, { multiple: true }, hub.id, nil
|
||||
= check_box klass, :distributor_ids, { multiple: true }.merge(add_check_if_single(@hubs.count)), hub.id, nil
|
||||
= hub.name
|
||||
%a.one.column.omega{ href: "#{main_app.edit_admin_enterprise_path(hub)}" }
|
||||
%span.icon-arrow-right
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= tab :dashboard, :route => :admin, :icon => 'icon-dashboard'
|
||||
= tab :overview, label: 'dashboard', url: spree.admin_dashboard_path, icon: 'icon-dashboard'
|
||||
= tab :products, :option_types, :properties, :variants, :product_properties, :taxons, :url => admin_products_path, :icon => 'icon-th-large'
|
||||
= tab :order_cycles, :url => main_app.admin_order_cycles_path, :icon => 'icon-refresh'
|
||||
= tab :orders, :payments, :creditcard_payments, :shipments, :credit_cards, :return_authorizations, :url => admin_orders_path('q[s]' => 'completed_at desc'), :icon => 'icon-shopping-cart'
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
= Spree.t(:loading)
|
||||
\...
|
||||
|
||||
= render :partial => 'spree/admin/shared/alert', :collection => session[:alerts]
|
||||
|
||||
%header#header{"data-hook" => ""}
|
||||
.container
|
||||
%figure.columns.five{"data-hook" => "logo-wrapper"}
|
||||
= link_to image_tag(Spree::Config[:admin_interface_logo], :id => 'logo'), spree.admin_path
|
||||
= link_to image_tag(Spree::Config[:admin_interface_logo], :id => 'logo'), spree.admin_dashboard_path
|
||||
%nav.columns.eleven{"data-hook" => "admin_login_navigation_bar"}
|
||||
= render :partial => 'spree/layouts/admin/login_nav'
|
||||
|
||||
|
||||
@@ -14,11 +14,10 @@
|
||||
.progress-message
|
||||
= t(:loading)
|
||||
\...
|
||||
= render :partial => 'spree/admin/shared/alert', :collection => session[:alerts]
|
||||
|
||||
%header#header{"data-hook" => ""}
|
||||
.container
|
||||
%figure.columns.five{"data-hook" => "logo-wrapper"}= link_to image_tag(Spree::Config[:admin_interface_logo], :id => 'logo'), spree.admin_path
|
||||
%figure.columns.five{"data-hook" => "logo-wrapper"}= link_to image_tag(Spree::Config[:admin_interface_logo], :id => 'logo'), spree.admin_dashboard_path
|
||||
%nav.columns.eleven{"data-hook" => "admin_login_navigation_bar"}
|
||||
= render partial: "spree/layouts/admin/login_nav"
|
||||
|
||||
|
||||
1
config/initializers/paper_trail.rb
Normal file
1
config/initializers/paper_trail.rb
Normal file
@@ -0,0 +1 @@
|
||||
PaperTrail.config.track_associations = false
|
||||
@@ -2,6 +2,8 @@ ar:
|
||||
language_name: "الانجيلزي"
|
||||
activerecord:
|
||||
attributes:
|
||||
enterprise_fee:
|
||||
fee_type: نوع الرسوم
|
||||
spree/order:
|
||||
payment_state: حالة الدفعة
|
||||
shipment_state: حالة الشحن
|
||||
@@ -48,6 +50,8 @@ ar:
|
||||
shipping_method_ids: "طرق الشحن"
|
||||
payment_method_ids: "طريقة الدفع"
|
||||
errors:
|
||||
messages:
|
||||
inclusion: "غير مدرجة في القائمة"
|
||||
models:
|
||||
subscription_validator:
|
||||
attributes:
|
||||
@@ -262,6 +266,7 @@ ar:
|
||||
cancel: "إلغاء"
|
||||
save: "حفظ"
|
||||
edit: "تعديل"
|
||||
update: "تحديث"
|
||||
delete: "حذف"
|
||||
admin:
|
||||
begins_at: يبدأ عند
|
||||
@@ -846,7 +851,6 @@ ar:
|
||||
save_and_back_to_list: "حفظ والعودة إلى القائمة"
|
||||
choose_products_from: "اختر المنتجات من:"
|
||||
incoming:
|
||||
previous: "السابق"
|
||||
save: "حفظ"
|
||||
save_and_next: "حفظ والتالي"
|
||||
next: "التالى"
|
||||
@@ -985,9 +989,6 @@ ar:
|
||||
name: العملاء
|
||||
products_and_inventory:
|
||||
name: المنتجات والمخزون
|
||||
sales_total:
|
||||
name: إجمالي المبيعات
|
||||
description: إجمالي المبيعات لجميع الطلبات
|
||||
users_and_enterprises:
|
||||
name: المستخدمين والمؤسسات
|
||||
description: ملكية الشركات وحالتها
|
||||
@@ -1126,15 +1127,6 @@ ar:
|
||||
selling_on_ofn: "هل أنت مهتم بالحصول على شبكة الغذاء المفتوح؟"
|
||||
register: "سجل هنا"
|
||||
footer:
|
||||
footer_global_headline: "شبكة الغذاء المفتوحة العالمية"
|
||||
footer_global_home: "الصفحة الرئيسية"
|
||||
footer_global_news: "أخبار"
|
||||
footer_global_about: "حول"
|
||||
footer_global_contact: "اتصل"
|
||||
footer_sites_headline: "مواقع شبكة الغذاء المفتوحة"
|
||||
footer_sites_developer: "مطور"
|
||||
footer_sites_community: "تواصل اجتماعي"
|
||||
footer_sites_userguide: "دليل المستخدم"
|
||||
footer_secure: "آمن وموثوق به."
|
||||
footer_secure_text: "تستخدم شبكة الغذاء المفتوح تشفير( SSL 2048 بت RSA) في كل مكان للحفاظ على خصوصية معلومات التسوق والدفع. لا تقوم خوادمنا بتخزين تفاصيل بطاقة الائتمان الخاصة بك ، وتتم معالجة المدفوعات بواسطة خدمات متوافقة مع PCI."
|
||||
footer_contact_headline: "أبق على اتصال"
|
||||
@@ -2425,6 +2417,12 @@ ar:
|
||||
severity: خطورة
|
||||
description: وصف
|
||||
resolve: حل
|
||||
exchange_products:
|
||||
load_more_products: "تحميل المزيد من المنتجات"
|
||||
load_all_products: "تحميل جميع المنتجات"
|
||||
select_all_products: "حدد جميع المنتجات %{total_number_of_products}"
|
||||
products_loaded: "%{num_of_products_loaded} من %{total_number_of_products} المنتجات المحملة"
|
||||
loading_products: "تحميل المنتجات"
|
||||
tag_rules:
|
||||
shipping_method_tagged_top: "طرق الشحن الموسومة"
|
||||
shipping_method_tagged_bottom: "هي:"
|
||||
@@ -2507,6 +2505,7 @@ ar:
|
||||
customer_placeholder: "customer@example.org"
|
||||
valid_email_error: "من فضلك أدخل بريد أليكترونى صحيح"
|
||||
subscriptions:
|
||||
error_saving: "خطأ في حفظ الاشتراك"
|
||||
new:
|
||||
please_select_a_shop: "يرجى اختيار متجر"
|
||||
insufficient_stock: "مخزون غير متوفر ، تبقى %{on_hand}"
|
||||
@@ -2707,7 +2706,6 @@ ar:
|
||||
allow_ssl_in_development_and_test: "السماح باستخدام طبقة المقابس الآمنة SSL عندما تكون في أوضاع التطوير والاختبار"
|
||||
allow_ssl_in_production: "السماح باستخدام طبقة المقابس الآمنة في وضع الإنتاج"
|
||||
allow_ssl_in_staging: "اسمح باستخدام طبقة المقابس الآمنة في وضع التدريج"
|
||||
check_for_spree_alerts: "تحقق من وجود تنبيهات Spree"
|
||||
currency_decimal_mark: "العملة العشرية"
|
||||
currency_settings: "إعدادات العملة"
|
||||
currency_symbol_position: ضع "رمز العملة قبل أو بعد مبلغ الدولار؟"
|
||||
@@ -2872,6 +2870,37 @@ ar:
|
||||
form:
|
||||
name: "الاسم"
|
||||
presentation: "عرض"
|
||||
return_authorizations:
|
||||
index:
|
||||
new_return_authorization: "عودة الترخيص جديد"
|
||||
return_authorizations: "عودة التراخيص"
|
||||
back_to_orders_list: "العودة إلى قائمة الطلبات"
|
||||
rma_number: "رقم RMA"
|
||||
status: "الحالة"
|
||||
amount: "القيمة"
|
||||
cannot_create_returns: "لا يمكن إنشاء عوائد لأن هذا الطلب لا يحتوي على وحدات مشحونة."
|
||||
continue: "تابع"
|
||||
new:
|
||||
new_return_authorization: "عودة الترخيص جديد"
|
||||
back_to_return_authorizations_list: "العودة إلى قائمة عودة الترخيص"
|
||||
continue: "تابع"
|
||||
edit:
|
||||
receive: "استلام"
|
||||
are_you_sure: "هل أنت واثق؟"
|
||||
return_authorization: "عودة التراخيص"
|
||||
form:
|
||||
product: "المنتج"
|
||||
quantity_shipped: "الكمية التي تم شحنها"
|
||||
quantity_returned: "الكمية المرتجعة"
|
||||
return_quantity: "عودة الكمية"
|
||||
amount: "القيمة"
|
||||
rma_value: "قيمة RMA"
|
||||
reason: "السبب"
|
||||
stock_location: "موقع المخزن"
|
||||
states:
|
||||
authorized: "مخول"
|
||||
received: "تم الاستلام"
|
||||
canceled: "ألغيت"
|
||||
orders:
|
||||
index:
|
||||
listing_orders: "لائحة الطلبات"
|
||||
|
||||
@@ -2,6 +2,8 @@ ca:
|
||||
language_name: "Català"
|
||||
activerecord:
|
||||
attributes:
|
||||
enterprise_fee:
|
||||
fee_type: Tipus de comissió
|
||||
spree/order:
|
||||
payment_state: Estat del Pagament
|
||||
shipment_state: Estat de la Tramesa
|
||||
@@ -262,6 +264,7 @@ ca:
|
||||
cancel: "Cancel·lar"
|
||||
save: "Desa"
|
||||
edit: "Editar"
|
||||
update: "Actualitzar"
|
||||
delete: "Suprimir"
|
||||
admin:
|
||||
begins_at: Comença a
|
||||
@@ -433,9 +436,12 @@ ca:
|
||||
infinity: "Infinit"
|
||||
to_order_tip: "Els articles preparats per encàrrec no tenen un nivell fixat d'existències, com ara pa fet sota comanda."
|
||||
back_to_products_list: "Torna a la llista de productes"
|
||||
editing_product: "Editant el producte"
|
||||
tabs:
|
||||
product_details: "Detalls del producte"
|
||||
group_buy_options: "Opcions de compra en grup"
|
||||
images: "Imatges"
|
||||
variants: "Variants"
|
||||
product_properties: "Propietats del producte"
|
||||
product_import:
|
||||
title: Importació de productes
|
||||
@@ -832,10 +838,35 @@ ca:
|
||||
loading_flash:
|
||||
loading_order_cycles: CÀRREGUES CICLES DE COMANDA
|
||||
loading: CARREGANT...
|
||||
new:
|
||||
create: "Crear"
|
||||
cancel: "Cancel·lar"
|
||||
back_to_list: "Tornar a la llista"
|
||||
edit:
|
||||
advanced_settings: Configuració avançada
|
||||
update_and_close: Actualitza i tanca
|
||||
choose_products_from: 'Trieu Productes des de:'
|
||||
advanced_settings: "Configuració avançada"
|
||||
save: "Desa"
|
||||
save_and_next: "Desa i següent"
|
||||
next: "Següent"
|
||||
cancel: "Cancel·lar"
|
||||
back_to_list: "Tornar a la llista"
|
||||
save_and_back_to_list: "Desa i torna a la llista"
|
||||
choose_products_from: "Trieu Productes des de:"
|
||||
incoming:
|
||||
save: "Desa"
|
||||
save_and_next: "Desa i següent"
|
||||
next: "Següent"
|
||||
cancel: "Cancel·lar"
|
||||
back_to_list: "Tornar a la llista"
|
||||
outgoing:
|
||||
previous: "Anterior"
|
||||
save: "Desa"
|
||||
save_and_back_to_list: "Desa i torna a la llista"
|
||||
cancel: "Cancel·lar"
|
||||
back_to_list: "Tornar a la llista"
|
||||
wizard_progress:
|
||||
edit: "1. Configuració general"
|
||||
incoming: "2. Productes entrants"
|
||||
outgoing: "3. Productes sortints"
|
||||
exchange_form:
|
||||
pickup_time_tip: Quan les comandes d'aquest cicle de comandes estiguin preparades per a les consumidores
|
||||
pickup_instructions_placeholder: "Instruccions de recollida"
|
||||
@@ -862,6 +893,7 @@ ca:
|
||||
any_enterprise: "Qualsevol organització"
|
||||
any_schedule: "Qualsevol programació"
|
||||
form:
|
||||
general_settings: "Configuració general"
|
||||
incoming: Entrant
|
||||
supplier: Proveïdora
|
||||
receival_details: Detalls de recepció
|
||||
@@ -958,9 +990,6 @@ ca:
|
||||
name: Consumidores
|
||||
products_and_inventory:
|
||||
name: Productes & Inventari
|
||||
sales_total:
|
||||
name: Total de vendes
|
||||
description: Total de vendes per a totes les comandes
|
||||
users_and_enterprises:
|
||||
name: Usuaris & Organitzacions
|
||||
description: Propietat i estatus de l'organització
|
||||
@@ -1099,15 +1128,6 @@ ca:
|
||||
selling_on_ofn: "Estàs interessat en formar part d'Open Food Network?"
|
||||
register: "Registra't aquí"
|
||||
footer:
|
||||
footer_global_headline: "OFN Global"
|
||||
footer_global_home: "Inici"
|
||||
footer_global_news: "Notícies"
|
||||
footer_global_about: "Sobre"
|
||||
footer_global_contact: "Contacte"
|
||||
footer_sites_headline: "Pàgines d'OFN"
|
||||
footer_sites_developer: "Desenvolupador"
|
||||
footer_sites_community: "Comunitat"
|
||||
footer_sites_userguide: "Guia de l'usuari"
|
||||
footer_secure: "Segur i de confiança."
|
||||
footer_secure_text: "Open Food Network utilitza el xifrat SSL (RSA de 2048 bits) a tot arreu per mantenir les vostres dades comercials i de pagament privades. Els nostres servidors no emmagatzemen els detalls de la targeta de crèdit i els pagaments es processen mitjançant serveis compatibles amb PCI."
|
||||
footer_contact_headline: "Mantén el contacte"
|
||||
@@ -2682,7 +2702,6 @@ ca:
|
||||
allow_ssl_in_development_and_test: "Permet que s'utilitzi SSL quan s'utilitzi el desenvolupament i els modes de prova"
|
||||
allow_ssl_in_production: "Permet que SSL s'utilitzi en mode de producció"
|
||||
allow_ssl_in_staging: "Permet que SSL s'utilitzi en mode de staging"
|
||||
check_for_spree_alerts: "Comproveu si hi ha alertes Spree"
|
||||
currency_decimal_mark: "Separador decimal de moneda"
|
||||
currency_settings: "Configuració de moneda"
|
||||
currency_symbol_position: Posa "símbol de moneda abans o després d'una quantitat"?
|
||||
@@ -2782,6 +2801,12 @@ ca:
|
||||
minimal_amount: "Quantitat mínima"
|
||||
normal_amount: "Quantitat normal"
|
||||
discount_amount: "Import de descompte"
|
||||
no_images_found: "No s'han trobat imatges"
|
||||
new_image: "Nova imatge"
|
||||
filename: "Nom de l'arxiu"
|
||||
alt_text: "Text alternatiu"
|
||||
thumbnail: "Miniatura"
|
||||
back_to_images_list: "Torna a la llista d’imatges"
|
||||
email: Correu electrònic
|
||||
account_updated: "Compte actualitzat!"
|
||||
email_updated: "El compte s’actualitzarà un cop es confirmi el nou correu electrònic."
|
||||
@@ -2793,6 +2818,7 @@ ca:
|
||||
zipcode: Codi postal
|
||||
weight: Pes (per kg)
|
||||
error_user_destroy_with_orders: "No es poden esborrar usuaris amb comandes completades"
|
||||
options: "Opcions"
|
||||
actions:
|
||||
update: "Actualitzar"
|
||||
errors:
|
||||
@@ -2824,6 +2850,53 @@ ca:
|
||||
product_properties:
|
||||
index:
|
||||
inherits_properties_checkbox_hint: "heredar propietats de %{supplier}? (llevat que es sobreescrigui a dalt)"
|
||||
add_product_properties: "Afegeix propietats del producte"
|
||||
select_from_prototype: "Seleccioneu d'un prototip"
|
||||
properties:
|
||||
index:
|
||||
properties: "Propietats"
|
||||
new_property: "Nova propietat"
|
||||
name: "Nom"
|
||||
presentation: "Presentació"
|
||||
new:
|
||||
new_property: "Nova propietat"
|
||||
edit:
|
||||
editing_property: "Edició de propietats"
|
||||
back_to_properties_list: "Torna a la llista de propietats"
|
||||
form:
|
||||
name: "Nom"
|
||||
presentation: "Presentació"
|
||||
return_authorizations:
|
||||
index:
|
||||
new_return_authorization: "Nova autorització de devolució"
|
||||
return_authorizations: "Autoritzacions de devolució"
|
||||
back_to_orders_list: "Tornar a la llista de comandes"
|
||||
rma_number: "Número RMA"
|
||||
status: "Estat"
|
||||
amount: "Quantitat"
|
||||
cannot_create_returns: "No es poden crear devolucions ja que aquesta comanda no té cap unitat enviada."
|
||||
continue: "Continua"
|
||||
new:
|
||||
new_return_authorization: "Nova autorització de devolució"
|
||||
back_to_return_authorizations_list: "Tornar a la llista d'autorització"
|
||||
continue: "Continua"
|
||||
edit:
|
||||
receive: "rebre"
|
||||
are_you_sure: "Estàs segur?"
|
||||
return_authorization: "Autorització de devolució"
|
||||
form:
|
||||
product: "Producte"
|
||||
quantity_shipped: "Quantitat enviada"
|
||||
quantity_returned: "Quantitat retornada"
|
||||
return_quantity: "Devolució de la quantitat"
|
||||
amount: "Quantitat"
|
||||
rma_value: "Valor RMA"
|
||||
reason: "Raó"
|
||||
stock_location: "Ubicació d'estoc"
|
||||
states:
|
||||
authorized: "Autoritzat"
|
||||
received: "Rebut"
|
||||
canceled: "Cancel·lat"
|
||||
orders:
|
||||
index:
|
||||
listing_orders: "Llistat comandes"
|
||||
@@ -3014,12 +3087,23 @@ ca:
|
||||
index:
|
||||
sku: "Número de referència (SKU)"
|
||||
price: "Preu"
|
||||
options: "Opcions"
|
||||
no_results: "Sense resultats"
|
||||
to_add_variants_you_must_first_define: "Per afegir variants, primer heu de definir"
|
||||
option_types: "Tipus d'opcions"
|
||||
option_values: "Valors d’opció"
|
||||
and: "i"
|
||||
new_variant: "Nova variant"
|
||||
show_active: "Mostra actiu"
|
||||
show_deleted: "Mostra esborrats"
|
||||
new:
|
||||
new_variant: "Nova variant"
|
||||
form:
|
||||
cost_price: "Preu de cost"
|
||||
sku: "Número de referència (SKU)"
|
||||
price: "Preu"
|
||||
display_as: "Mostra com"
|
||||
display_name: "Nom de visualització"
|
||||
autocomplete:
|
||||
producer_name: "Productor"
|
||||
unit: "Unitat"
|
||||
@@ -3159,3 +3243,19 @@ ca:
|
||||
allow_charges?: "Permetre càrrecs?"
|
||||
localized_number:
|
||||
invalid_format: té un format no vàlid. Si us plau introdueix un número.
|
||||
api:
|
||||
invalid_api_key: "No s’ha especificat una clau d’API vàlida (%{key})."
|
||||
unauthorized: "No teniu autorització per realitzar aquesta acció."
|
||||
invalid_resource: "Recurs invàlid. Corregiu els errors i torneu-ho a provar."
|
||||
resource_not_found: "No s'ha trobat el recurs que buscaves."
|
||||
access: "Accés a l'API"
|
||||
key: "Clau"
|
||||
clear_key: "Esborra la clau"
|
||||
regenerate_key: "Regenerar la clau"
|
||||
no_key: "Sense clau"
|
||||
generate_key: "Generar clau d’API"
|
||||
key_generated: "Clau generada"
|
||||
key_cleared: "La clau esborrada"
|
||||
shipment:
|
||||
cannot_ready: "No es pot fer l'enviament."
|
||||
invalid_taxonomy_id: "Identificador de taxonomia no vàlid."
|
||||
|
||||
@@ -2,6 +2,8 @@ de_DE:
|
||||
language_name: "Deutsch"
|
||||
activerecord:
|
||||
attributes:
|
||||
enterprise_fee:
|
||||
fee_type: Art der Gebühr
|
||||
spree/order:
|
||||
payment_state: Zahlungsstatus
|
||||
shipment_state: Lieferstatus
|
||||
@@ -113,6 +115,10 @@ de_DE:
|
||||
subject: "%{enterprise} ist jetzt auf %{sitename}"
|
||||
email_welcome: "Willkommen"
|
||||
email_registered: "ist jetzt Teil von"
|
||||
email_userguide_html: "Das Benutzerhandbuch mit detaillierter Unterstützung für die Einrichtung Ihres Produzenten oder Hubs finden Sie hier: %{link}"
|
||||
userguide: "Öffnen Sie das Food Network-Benutzerhandbuch"
|
||||
email_admin_html: "Sie können Ihr Konto verwalten, indem Sie sich bei %{link} anmelden oder auf das Zahnrad oben rechts auf der Startseite klicken und Administration auswählen."
|
||||
admin_panel: "Administrationsmenü"
|
||||
email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Reden Sie doch mit. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum prägt, was als nächstes passiert. %{link}"
|
||||
join_community: "Treten Sie der Community bei"
|
||||
invite_manager:
|
||||
@@ -258,6 +264,7 @@ de_DE:
|
||||
cancel: "Abbrechen"
|
||||
save: "Speichern"
|
||||
edit: "Bearbeiten"
|
||||
update: "Aktualisieren"
|
||||
delete: "Löschen"
|
||||
admin:
|
||||
begins_at: Beginnt um
|
||||
@@ -429,9 +436,12 @@ de_DE:
|
||||
infinity: "Unendlichkeit"
|
||||
to_order_tip: "Artikel, die auf Bestellung hergestellt werden, haben keinen festgelegten Lagerbestand."
|
||||
back_to_products_list: "Zurück zur Produktliste"
|
||||
editing_product: "Produkt bearbeiten"
|
||||
tabs:
|
||||
product_details: "Produktdetails"
|
||||
group_buy_options: "Gruppenkaufoptionen"
|
||||
images: "Bilder"
|
||||
variants: "Varianten"
|
||||
product_properties: "Produkteigenschaften"
|
||||
product_import:
|
||||
title: Produkte importieren
|
||||
@@ -534,6 +544,7 @@ de_DE:
|
||||
title: Katalog
|
||||
description: Verwenden Sie diese Seite, um Bestände für Ihre Unternehmen zu verwalten. Alle hier eingestellten Produktdetails überschreiben diejenigen, die auf der Seite "Produkte" eingestellt sind
|
||||
enable_reset?: Lagerbestand zurücksetzbar?
|
||||
default_stock: "Standardbestand"
|
||||
inherit?: Übernehmen?
|
||||
add: Hinzufügen
|
||||
hide: Verbergen
|
||||
@@ -827,10 +838,35 @@ de_DE:
|
||||
loading_flash:
|
||||
loading_order_cycles: LADEN VON AUFTRAGSZYKLEN
|
||||
loading: WIRD GELADEN...
|
||||
new:
|
||||
create: "Neu"
|
||||
cancel: "Abbrechen"
|
||||
back_to_list: "Zurück zur Liste"
|
||||
edit:
|
||||
advanced_settings: Erweiterte Einstellungen
|
||||
update_and_close: Aktualisieren und schließen
|
||||
choose_products_from: 'Wählen Sie Produkte von:'
|
||||
advanced_settings: "Erweiterte Einstellungen"
|
||||
save: "Speichern"
|
||||
save_and_next: "Speichern und weiter"
|
||||
next: "Weiter"
|
||||
cancel: "Abbrechen"
|
||||
back_to_list: "Zurück zur Liste"
|
||||
save_and_back_to_list: "Speichern und zurück zur Liste"
|
||||
choose_products_from: "Wählen Sie Produkte von:"
|
||||
incoming:
|
||||
save: "Speichern"
|
||||
save_and_next: "Speichern und weiter"
|
||||
next: "Weiter"
|
||||
cancel: "Abbrechen"
|
||||
back_to_list: "Zurück zur Liste"
|
||||
outgoing:
|
||||
previous: "Bisherige"
|
||||
save: "Speichern"
|
||||
save_and_back_to_list: "Speichern und zurück zur Liste"
|
||||
cancel: "Abbrechen"
|
||||
back_to_list: "Zurück zur Liste"
|
||||
wizard_progress:
|
||||
edit: "1. Allgemeine Einstellungen"
|
||||
incoming: "2. Eingehende Produkte"
|
||||
outgoing: "3. Ausgehende Produkte"
|
||||
exchange_form:
|
||||
pickup_time_tip: Wenn Bestellungen von diesem OC für den Kunden bereit sind
|
||||
pickup_instructions_placeholder: "Abholungsinformationen"
|
||||
@@ -857,6 +893,7 @@ de_DE:
|
||||
any_enterprise: "Alle Unternehmen"
|
||||
any_schedule: "Alle Zeitpläne"
|
||||
form:
|
||||
general_settings: "Allgemeine Einstellungen"
|
||||
incoming: Eingehend
|
||||
supplier: Anbieter
|
||||
receival_details: Lieferinformation
|
||||
@@ -953,9 +990,6 @@ de_DE:
|
||||
name: Kunden
|
||||
products_and_inventory:
|
||||
name: Produkte und Katalog
|
||||
sales_total:
|
||||
name: Gesamtumsatz
|
||||
description: Gesamtumsatz für alle Bestellungen
|
||||
users_and_enterprises:
|
||||
name: Benutzer und Unternehmen
|
||||
description: Unternehmenseigentum & Status
|
||||
@@ -1094,15 +1128,6 @@ de_DE:
|
||||
selling_on_ofn: "Interesse am Open Food Network?"
|
||||
register: "Hier anmelden"
|
||||
footer:
|
||||
footer_global_headline: "OFN Global"
|
||||
footer_global_home: "Startseite"
|
||||
footer_global_news: "Aktuelles"
|
||||
footer_global_about: "Über Uns"
|
||||
footer_global_contact: "Kontakt"
|
||||
footer_sites_headline: "OFN Webseiten"
|
||||
footer_sites_developer: "Entwickler"
|
||||
footer_sites_community: "Community"
|
||||
footer_sites_userguide: "Benutzerhandbuch"
|
||||
footer_secure: "Sicher und vertrauenswürdig."
|
||||
footer_secure_text: "Open Food Network verwendet überall SSL-Verschlüsselung (2048 Bit RSA), um Ihre Einkaufs- und Zahlungsinformationen geheim zu halten. Unsere Server speichern Ihre Kreditkartendetails nicht und Zahlungen werden von PCI-konformen Dienstleistern verarbeitet."
|
||||
footer_contact_headline: "In Verbindung bleiben"
|
||||
@@ -2677,7 +2702,6 @@ de_DE:
|
||||
allow_ssl_in_development_and_test: "Erlauben Sie SSL in Entwicklungs- und Testmodi"
|
||||
allow_ssl_in_production: "Zulassen, dass SSL im Produktionsmodus verwendet wird"
|
||||
allow_ssl_in_staging: "Zulassen, dass SSL im Staging-Modus verwendet wird"
|
||||
check_for_spree_alerts: "Suchen Sie nach Spree-Benachrichtigungen"
|
||||
currency_decimal_mark: "Dezimalzeichen der Währung"
|
||||
currency_settings: "Währungseinstellungen"
|
||||
currency_symbol_position: Setzen Sie "Währungssymbol vor oder nach dem Dollarbetrag?"
|
||||
@@ -2777,6 +2801,12 @@ de_DE:
|
||||
minimal_amount: "Minimale Menge"
|
||||
normal_amount: "Normaler Betrag"
|
||||
discount_amount: "Rabattbetrag"
|
||||
no_images_found: "Keine Bilder gefunden"
|
||||
new_image: "Neues Bild"
|
||||
filename: "Dateiname"
|
||||
alt_text: "alternativer Text"
|
||||
thumbnail: "Miniaturansicht"
|
||||
back_to_images_list: "Zurück zur Bilderliste"
|
||||
email: Email
|
||||
account_updated: "Konto aktualisiert!"
|
||||
email_updated: "Das Konto wird aktualisiert, sobald die neue E-Mail bestätigt wurde."
|
||||
@@ -2788,6 +2818,7 @@ de_DE:
|
||||
zipcode: Postleitzahl
|
||||
weight: Gewicht (pro kg)
|
||||
error_user_destroy_with_orders: "Benutzer mit abgeschlossenen Bestellungen dürfen nicht gelöscht werden"
|
||||
options: "Optionen"
|
||||
actions:
|
||||
update: "Aktualisieren"
|
||||
errors:
|
||||
@@ -2819,6 +2850,53 @@ de_DE:
|
||||
product_properties:
|
||||
index:
|
||||
inherits_properties_checkbox_hint: "Vererben Eigenschaften von %{supplier}? (außer oben aufgehoben)"
|
||||
add_product_properties: "Produkteigenschaften hinzufügen"
|
||||
select_from_prototype: "Wählen Sie Aus Prototyp"
|
||||
properties:
|
||||
index:
|
||||
properties: "Eigenschaften"
|
||||
new_property: "Neues Eigentum"
|
||||
name: "Name"
|
||||
presentation: "Präsentation"
|
||||
new:
|
||||
new_property: "Neues Eigentum"
|
||||
edit:
|
||||
editing_property: "Eigenschaft bearbeiten"
|
||||
back_to_properties_list: "Zurück zur Eigenschaftenliste"
|
||||
form:
|
||||
name: "Name"
|
||||
presentation: "Präsentation"
|
||||
return_authorizations:
|
||||
index:
|
||||
new_return_authorization: "Neue Rücksendegenehmigung"
|
||||
return_authorizations: "Rückgabeberechtigungen"
|
||||
back_to_orders_list: "Zurück zur Bestellliste"
|
||||
rma_number: "RMA-Nummer"
|
||||
status: "Status"
|
||||
amount: "Betrag"
|
||||
cannot_create_returns: "Retouren können nicht erstellt werden, da für diese Bestellung keine Versandeinheiten vorhanden sind."
|
||||
continue: "Fortsetzen"
|
||||
new:
|
||||
new_return_authorization: "Neue Rücksendegenehmigung"
|
||||
back_to_return_authorizations_list: "Zurück zur Autorisierungsliste"
|
||||
continue: "Fortsetzen"
|
||||
edit:
|
||||
receive: "erhalten"
|
||||
are_you_sure: "Bist du sicher?"
|
||||
return_authorization: "Rücksendegenehmigung"
|
||||
form:
|
||||
product: "Produkt"
|
||||
quantity_shipped: "Menge ausgeliefert"
|
||||
quantity_returned: "Menge zurückgegeben"
|
||||
return_quantity: "Rückgabemenge"
|
||||
amount: "Betrag"
|
||||
rma_value: "RMA-Wert"
|
||||
reason: "Grund"
|
||||
stock_location: "Lagerort"
|
||||
states:
|
||||
authorized: "Autorisiert"
|
||||
received: "Empfangen"
|
||||
canceled: "Abgesagt"
|
||||
orders:
|
||||
index:
|
||||
listing_orders: "Bestellungen auflisten"
|
||||
@@ -3009,10 +3087,23 @@ de_DE:
|
||||
index:
|
||||
sku: "Artikelnummer"
|
||||
price: "Preis"
|
||||
options: "Optionen"
|
||||
no_results: "Keine Ergebnisse"
|
||||
to_add_variants_you_must_first_define: "Um Varianten hinzuzufügen, müssen Sie zuerst definieren"
|
||||
option_types: "Optionstypen"
|
||||
option_values: "Optionswerte"
|
||||
and: "und"
|
||||
new_variant: "Neue Variante"
|
||||
show_active: "Aktiv anzeigen"
|
||||
show_deleted: "Show gelöscht"
|
||||
new:
|
||||
new_variant: "Neue Variante"
|
||||
form:
|
||||
cost_price: "Selbstkostenpreis"
|
||||
sku: "Artikelnummer"
|
||||
price: "Preis"
|
||||
display_as: "Angezeigt als"
|
||||
display_name: "Anzeigename"
|
||||
autocomplete:
|
||||
producer_name: "Produzent"
|
||||
unit: "Einheit"
|
||||
@@ -3152,3 +3243,19 @@ de_DE:
|
||||
allow_charges?: "Gebühren erlauben?"
|
||||
localized_number:
|
||||
invalid_format: hat ein ungültiges Format. Bitte Ziffern eingeben.
|
||||
api:
|
||||
invalid_api_key: "Ungültiger API-Schlüssel (%{key}) angegeben."
|
||||
unauthorized: "Sie sind nicht berechtigt, diese Aktion auszuführen."
|
||||
invalid_resource: "Ungültige Ressource. Bitte beheben Sie die Fehler und versuchen Sie es erneut."
|
||||
resource_not_found: "Die gesuchte Ressource wurde nicht gefunden."
|
||||
access: "API-Zugriff"
|
||||
key: "Schlüssel"
|
||||
clear_key: "Schlüssel löschen"
|
||||
regenerate_key: "Schlüssel neu generieren"
|
||||
no_key: "Kein Schlüssel"
|
||||
generate_key: "API-Schlüssel generieren"
|
||||
key_generated: "Schlüssel generiert"
|
||||
key_cleared: "Schlüssel gelöscht"
|
||||
shipment:
|
||||
cannot_ready: "Versand nicht möglich."
|
||||
invalid_taxonomy_id: "Ungültige Taxonomie-ID"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user