WIP: develop to foodsoft master diff #39

Draft
philipp wants to merge 12 commits from develop into master
103 changed files with 3260 additions and 2473 deletions

View file

@ -266,7 +266,7 @@ Metrics/AbcSize:
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, inherit_mode. # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, inherit_mode.
# AllowedMethods: refine # AllowedMethods: refine
Metrics/BlockLength: Metrics/BlockLength:
Max: 210 Max: 212
# Offense count: 6 # Offense count: 6
# Configuration parameters: CountBlocks. # Configuration parameters: CountBlocks.
@ -451,6 +451,24 @@ RSpec/DescribedClass:
- "spec/models/ordergroup_spec.rb" - "spec/models/ordergroup_spec.rb"
- "spec/models/user_spec.rb" - "spec/models/user_spec.rb"
# Offense count: 15
# This cop supports unsafe autocorrection (--autocorrect-all).
RSpec/EmptyExampleGroup:
Exclude:
- 'spec/requests/api/article_categories_spec.rb'
- 'spec/requests/api/configs_spec.rb'
- 'spec/requests/api/financial_transaction_classes_spec.rb'
- 'spec/requests/api/financial_transaction_types_spec.rb'
- 'spec/requests/api/financial_transactions_spec.rb'
- 'spec/requests/api/navigations_spec.rb'
- 'spec/requests/api/order_articles_spec.rb'
- 'spec/requests/api/orders_spec.rb'
- 'spec/requests/api/user/financial_transactions_spec.rb'
- 'spec/requests/api/user/group_order_articles_spec.rb'
- 'spec/requests/api/user/users_spec.rb'
# Offense count: 65 # Offense count: 65
# Configuration parameters: CountAsOne. # Configuration parameters: CountAsOne.
RSpec/ExampleLength: RSpec/ExampleLength:
@ -581,6 +599,14 @@ RSpec/ScatteredSetup:
- "spec/integration/balancing_spec.rb" - "spec/integration/balancing_spec.rb"
- "spec/integration/login_spec.rb" - "spec/integration/login_spec.rb"
# Offense count: 4
# Configuration parameters: AllowedPatterns, IgnoredPatterns.
# SupportedStyles: snake_case, camelCase
RSpec/VariableName:
EnforcedStyle: snake_case
AllowedPatterns:
- ^Authorization$
# Offense count: 1 # Offense count: 1
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:

View file

@ -1 +1 @@
2.6.9 2.7.2

View file

@ -1,4 +1,4 @@
FROM ruby:2.6 FROM ruby:2.7
RUN supercronicUrl=https://github.com/aptible/supercronic/releases/download/v0.1.3/supercronic-linux-amd64 && \ RUN supercronicUrl=https://github.com/aptible/supercronic/releases/download/v0.1.3/supercronic-linux-amd64 && \
supercronicBin=/usr/local/bin/supercronic && \ supercronicBin=/usr/local/bin/supercronic && \
@ -15,13 +15,16 @@ ENV PORT=3000 \
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY . ./ COPY Gemfile Gemfile.lock ./
COPY plugins/ ./plugins
COPY config/ ./config
# install dependencies and generate crontab # install dependencies and generate crontab
RUN buildDeps='libmagic-dev' && \ RUN buildDeps='libmagic-dev' && \
apt-get update && \ apt-get update && \
apt-get install --no-install-recommends -y $buildDeps && \ apt-get install --no-install-recommends -y $buildDeps && \
echo 'gem: --no-document' >> ~/.gemrc && \ echo 'gem: --no-document' >> ~/.gemrc && \
gem install bundler && \
bundle config build.nokogiri "--use-system-libraries" && \ bundle config build.nokogiri "--use-system-libraries" && \
bundle install --deployment --without development test -j 4 && \ bundle install --deployment --without development test -j 4 && \
apt-get purge -y --auto-remove $buildDeps && \ apt-get purge -y --auto-remove $buildDeps && \
@ -29,6 +32,8 @@ RUN buildDeps='libmagic-dev' && \
\ \
bundle exec whenever >crontab bundle exec whenever >crontab
COPY . ./
# compile assets with temporary mysql server # compile assets with temporary mysql server
RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \ RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
export SECRET_KEY_BASE=thisisnotimportantnow && \ export SECRET_KEY_BASE=thisisnotimportantnow && \

View file

@ -1,4 +1,4 @@
FROM ruby:2.6 FROM ruby:2.7
# Install dependencies # Install dependencies
RUN deps='libmagic-dev chromium nodejs' && \ RUN deps='libmagic-dev chromium nodejs' && \
@ -19,6 +19,7 @@ ENV PORT=3000 \
WORKDIR /app WORKDIR /app
RUN gem install bundler
RUN bundle config build.nokogiri "--use-system-libraries" RUN bundle config build.nokogiri "--use-system-libraries"
EXPOSE 3000 EXPOSE 3000

24
Gemfile
View file

@ -1,11 +1,12 @@
# A sample Gemfile # A sample Gemfile
source "https://rubygems.org" source "https://rubygems.org"
gem "rails", '~> 5.2' gem "rails", '~> 7.0'
gem 'mail', '~> 2.7.1' # bug with mail 2.8.0 https://github.com/mikel/mail/issues/1489
gem 'sass-rails'
gem 'sassc-rails'
gem 'less-rails' gem 'less-rails'
gem 'uglifier', '>= 1.0.3'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes # See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', platforms: :ruby gem 'therubyracer', platforms: :ruby
@ -22,7 +23,7 @@ gem 'bootsnap', require: false
gem 'mysql2' gem 'mysql2'
gem 'prawn' gem 'prawn'
gem 'prawn-table' gem 'prawn-table'
gem 'haml' gem 'haml', '~> 5.0'
gem 'haml-rails' gem 'haml-rails'
gem 'kaminari' gem 'kaminari'
gem 'simple_form' gem 'simple_form'
@ -46,7 +47,8 @@ gem 'whenever', require: false # For defining cronjobs, see config/schedule.rb
gem 'ruby-units' gem 'ruby-units'
gem 'attribute_normalizer' gem 'attribute_normalizer'
gem 'ice_cube' gem 'ice_cube'
gem 'recurring_select' # At time of development 01-06-2022 mmddyyyy necessary fix for config_helper.rb form builder was not in rubygems so we pull from github, see: https://github.com/gregschmit/recurring_select/pull/152
gem 'recurring_select', git: 'https://github.com/gregschmit/recurring_select'
gem 'roo' gem 'roo'
gem 'roo-xls' gem 'roo-xls'
gem 'spreadsheet' gem 'spreadsheet'
@ -55,6 +57,8 @@ gem 'gaffe'
gem 'ruby-filemagic' gem 'ruby-filemagic'
gem 'mime-types' gem 'mime-types'
gem 'midi-smtp-server' gem 'midi-smtp-server'
gem 'rswag-api'
gem 'rswag-ui'
# we use the git version of acts_as_versioned, and need to include it in this Gemfile # we use the git version of acts_as_versioned, and need to include it in this Gemfile
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git' gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
@ -81,7 +85,8 @@ group :development do
gem 'binding_of_caller' gem 'binding_of_caller'
# gem "rails-i18n-debug" # gem "rails-i18n-debug"
# chrome debugging extension https://github.com/dejan/rails_panel # chrome debugging extension https://github.com/dejan/rails_panel
gem 'meta_request' # TODO: disabled due to https://github.com/rails/rails/issues/40781
# gem 'meta_request'
# Get infos when not using proper eager loading # Get infos when not using proper eager loading
gem 'bullet' gem 'bullet'
@ -112,10 +117,15 @@ group :test do
gem 'rspec-core' gem 'rspec-core'
gem 'rspec-rerun' gem 'rspec-rerun'
gem 'i18n-spec' gem 'i18n-spec'
gem 'rails-controller-testing'
# code coverage # code coverage
gem 'simplecov', require: false gem 'simplecov', require: false
gem 'simplecov-lcov', require: false gem 'simplecov-lcov', require: false
# api # api
gem 'apivore', require: false gem 'rswag-specs'
gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114 gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114
end end
gem "importmap-rails", "~> 1.1"
gem "terser", "~> 1.1"

View file

@ -1,3 +1,14 @@
GIT
remote: https://github.com/gregschmit/recurring_select
revision: 29febc4c4abdd6c30636c33a7d2daecb09973ecf
specs:
recurring_select (3.0.0)
coffee-rails (>= 3.1)
ice_cube (>= 0.11)
jquery-rails (>= 3.0)
rails (>= 5.2)
sass-rails (>= 4.0)
GIT GIT
remote: https://github.com/technoweenie/acts_as_versioned.git remote: https://github.com/technoweenie/acts_as_versioned.git
revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b
@ -59,67 +70,83 @@ PATH
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (5.2.8.1) actioncable (7.0.4)
actionpack (= 5.2.8.1) actionpack (= 7.0.4)
activesupport (= 7.0.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailer (5.2.8.1) actionmailbox (7.0.4)
actionpack (= 5.2.8.1) actionpack (= 7.0.4)
actionview (= 5.2.8.1) activejob (= 7.0.4)
activejob (= 5.2.8.1) activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.4)
actionpack (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activesupport (= 7.0.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.2.8.1) actionpack (7.0.4)
actionview (= 5.2.8.1) actionview (= 7.0.4)
activesupport (= 5.2.8.1) activesupport (= 7.0.4)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (5.2.8.1) actiontext (7.0.4)
activesupport (= 5.2.8.1) actionpack (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.4)
activesupport (= 7.0.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_model_serializers (0.10.13) active_model_serializers (0.10.13)
actionpack (>= 4.1, < 7.1) actionpack (>= 4.1, < 7.1)
activemodel (>= 4.1, < 7.1) activemodel (>= 4.1, < 7.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (5.2.8.1) activejob (7.0.4)
activesupport (= 5.2.8.1) activesupport (= 7.0.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.2.8.1) activemodel (7.0.4)
activesupport (= 5.2.8.1) activesupport (= 7.0.4)
activerecord (5.2.8.1) activerecord (7.0.4)
activemodel (= 5.2.8.1) activemodel (= 7.0.4)
activesupport (= 5.2.8.1) activesupport (= 7.0.4)
arel (>= 9.0) activestorage (7.0.4)
activestorage (5.2.8.1) actionpack (= 7.0.4)
actionpack (= 5.2.8.1) activejob (= 7.0.4)
activerecord (= 5.2.8.1) activerecord (= 7.0.4)
marcel (~> 1.0.0) activesupport (= 7.0.4)
activesupport (5.2.8.1) marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.4)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (>= 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
acts_as_tree (2.9.1) acts_as_tree (2.9.1)
activerecord (>= 3.0.0) activerecord (>= 3.0.0)
addressable (2.8.1) addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
apivore (1.6.2)
actionpack (>= 4, < 6)
hashie (~> 3.3)
json-schema (~> 2.5)
rspec (~> 3)
rspec-expectations (~> 3.1)
rspec-mocks (~> 3.1)
apparition (0.6.0) apparition (0.6.0)
capybara (~> 3.13, < 4) capybara (~> 3.13, < 4)
websocket-driver (>= 0.6.5) websocket-driver (>= 0.6.5)
arel (9.0.0)
ast (2.4.2) ast (2.4.2)
attribute_normalizer (1.2.0) attribute_normalizer (1.2.0)
base32 (0.3.4) base32 (0.3.4)
@ -130,15 +157,15 @@ GEM
bindex (0.8.1) bindex (0.8.1)
binding_of_caller (1.0.0) binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootsnap (1.13.0) bootsnap (1.15.0)
msgpack (~> 1.2) msgpack (~> 1.2)
bootstrap-datepicker-rails (1.9.0.1) bootstrap-datepicker-rails (1.9.0.1)
railties (>= 3.0) railties (>= 3.0)
builder (3.2.4) builder (3.2.4)
bullet (7.0.3) bullet (7.0.7)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11) uniform_notifier (~> 1.11)
capybara (3.36.0) capybara (3.38.0)
addressable addressable
matrix matrix
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
@ -170,6 +197,7 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.3.3)
date_time_attribute (0.1.2) date_time_attribute (0.1.2)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
debug_inspector (1.1.0) debug_inspector (1.1.0)
@ -182,13 +210,13 @@ GEM
diff-lcs (1.5.0) diff-lcs (1.5.0)
diffy (3.4.2) diffy (3.4.2)
docile (1.4.0) docile (1.4.0)
doorkeeper (5.6.0) doorkeeper (5.6.2)
railties (>= 5) railties (>= 5)
doorkeeper-i18n (5.2.5) doorkeeper-i18n (5.2.6)
doorkeeper (>= 5.2) doorkeeper (>= 5.2)
email_reply_trimmer (0.1.13) email_reply_trimmer (0.1.13)
erubi (1.11.0) erubi (1.12.0)
eventmachine (1.2.7) eventmachine (1.0.9.1)
exception_notification (4.5.0) exception_notification (4.5.0)
actionmailer (>= 5.2, < 8) actionmailer (>= 5.2, < 8)
activesupport (>= 5.2, < 8) activesupport (>= 5.2, < 8)
@ -199,16 +227,15 @@ GEM
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (2.22.0) faker (3.1.0)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
ffi (1.15.5) ffi (1.15.5)
gaffe (1.2.0) gaffe (1.2.0)
rails (>= 4.0.0) rails (>= 4.0.0)
globalid (1.0.0) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
haml (6.0.5) haml (5.2.2)
temple (>= 0.8.2) temple (>= 0.8.0)
thor
tilt tilt
haml-rails (2.1.0) haml-rails (2.1.0)
actionpack (>= 5.1) actionpack (>= 5.1)
@ -227,6 +254,9 @@ GEM
i18n-spec (0.6.0) i18n-spec (0.6.0)
iso iso
ice_cube (0.16.4) ice_cube (0.16.4)
importmap-rails (1.1.5)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
inherited_resources (1.13.1) inherited_resources (1.13.1)
actionpack (>= 5.2, < 7.1) actionpack (>= 5.2, < 7.1)
has_scope (~> 0.6) has_scope (~> 0.6)
@ -235,13 +265,13 @@ GEM
interception (0.5) interception (0.5)
iso (0.4.0) iso (0.4.0)
i18n i18n
jquery-rails (4.5.0) jquery-rails (4.5.1)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (2.6.2) json (2.6.3)
json-schema (2.8.1) json-schema (3.0.0)
addressable (>= 2.4) addressable (>= 2.8)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
@ -261,7 +291,7 @@ GEM
actionpack (>= 5.0) actionpack (>= 5.0)
less (~> 2.6.0) less (~> 2.6.0)
sprockets (~> 3.0) sprockets (~> 3.0)
libv8 (3.16.14.19) libv8 (3.16.14.19-x86_64-linux)
listen (3.7.1) listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
@ -282,29 +312,33 @@ GEM
thin thin
marcel (1.0.2) marcel (1.0.2)
matrix (0.4.2) matrix (0.4.2)
meta_request (0.7.3)
rack-contrib (>= 1.1, < 3)
railties (>= 3.0.0, < 7)
method_source (1.0.0) method_source (1.0.0)
midi-smtp-server (3.0.3) midi-smtp-server (3.0.3)
mime-types (3.4.1) mime-types (3.4.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105) mime-types-data (3.2022.0105)
mini_mime (1.1.2) mini_mime (1.1.2)
mini_portile2 (2.8.0) minitest (5.17.0)
minitest (5.16.3)
mono_logger (1.1.1) mono_logger (1.1.1)
msgpack (1.6.0) msgpack (1.6.0)
multi_json (1.15.0) multi_json (1.15.0)
mustermann (3.0.0) mustermann (3.0.0)
ruby2_keywords (~> 0.0.1) ruby2_keywords (~> 0.0.1)
mysql2 (0.5.4) mysql2 (0.5.4)
net-imap (0.3.4)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
timeout
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.13.10) nokogiri (1.13.10-x86_64-linux)
mini_portile2 (~> 2.8.0)
racc (~> 1.4) racc (~> 1.4)
parallel (1.22.1) parallel (1.22.1)
parser (3.1.2.1) parser (3.2.0.0)
ast (~> 2.4.1) ast (~> 2.4.1)
pdf-core (0.9.0) pdf-core (0.9.0)
polyglot (0.3.5) polyglot (0.3.5)
@ -322,75 +356,73 @@ GEM
pry-stack_explorer (0.6.1) pry-stack_explorer (0.6.1)
binding_of_caller (~> 1.0) binding_of_caller (~> 1.0)
pry (~> 0.13) pry (~> 0.13)
public_suffix (5.0.0) public_suffix (5.0.1)
puma (5.6.5) puma (6.0.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.6.1) racc (1.6.2)
rack (2.2.4) rack (2.2.5)
rack-contrib (2.3.0)
rack (~> 2.0)
rack-cors (1.1.1) rack-cors (1.1.1)
rack (>= 2.0.0) rack (>= 2.0.0)
rack-protection (3.0.4) rack-protection (3.0.5)
rack rack
rack-test (2.0.2) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails (5.2.8.1) rails (7.0.4)
actioncable (= 5.2.8.1) actioncable (= 7.0.4)
actionmailer (= 5.2.8.1) actionmailbox (= 7.0.4)
actionpack (= 5.2.8.1) actionmailer (= 7.0.4)
actionview (= 5.2.8.1) actionpack (= 7.0.4)
activejob (= 5.2.8.1) actiontext (= 7.0.4)
activemodel (= 5.2.8.1) actionview (= 7.0.4)
activerecord (= 5.2.8.1) activejob (= 7.0.4)
activestorage (= 5.2.8.1) activemodel (= 7.0.4)
activesupport (= 5.2.8.1) activerecord (= 7.0.4)
bundler (>= 1.3.0) activestorage (= 7.0.4)
railties (= 5.2.8.1) activesupport (= 7.0.4)
sprockets-rails (>= 2.0.0) bundler (>= 1.15.0)
railties (= 7.0.4)
rails-assets-listjs (0.2.0.beta.4) rails-assets-listjs (0.2.0.beta.4)
railties (>= 3.1) railties (>= 3.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4) rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1) loofah (~> 2.19, >= 2.19.1)
rails-i18n (5.1.3) rails-i18n (7.0.6)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 5.0, < 6) railties (>= 6.0.0, < 8)
rails-settings-cached (0.4.3) rails-settings-cached (0.4.3)
rails (>= 4.2.0) rails (>= 4.2.0)
rails_tokeninput (1.7.0) rails_tokeninput (1.7.0)
railties (>= 3.1.0) railties (>= 3.1.0)
railties (5.2.8.1) railties (7.0.4)
actionpack (= 5.2.8.1) actionpack (= 7.0.4)
activesupport (= 5.2.8.1) activesupport (= 7.0.4)
method_source method_source
rake (>= 0.8.7) rake (>= 12.2)
thor (>= 0.19.0, < 2.0) thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.0.6) rake (13.0.6)
ransack (2.5.0) ransack (3.2.1)
activerecord (>= 5.2.4) activerecord (>= 6.1.5)
activesupport (>= 5.2.4) activesupport (>= 6.1.5)
i18n i18n
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
recurring_select (3.0.0)
coffee-rails (>= 3.1)
ice_cube (>= 0.11)
jquery-rails (>= 3.0)
rails (>= 5.2)
sass-rails (>= 4.0)
redis (5.0.5) redis (5.0.5)
redis-client (>= 0.9.0) redis-client (>= 0.9.0)
redis-client (0.9.0) redis-client (0.11.2)
connection_pool connection_pool
redis-namespace (1.9.0) redis-namespace (1.10.0)
redis (>= 4) redis (>= 4)
ref (2.0.0) ref (2.0.0)
regexp_parser (2.6.0) regexp_parser (2.6.1)
responders (3.0.1) responders (3.0.1)
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
@ -400,57 +432,67 @@ GEM
redis-namespace (~> 1.6) redis-namespace (~> 1.6)
sinatra (>= 0.9.2) sinatra (>= 0.9.2)
rexml (3.2.5) rexml (3.2.5)
roo (2.8.3) roo (2.9.0)
nokogiri (~> 1) nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0) rubyzip (>= 1.3.0, < 3.0.0)
roo-xls (1.2.0) roo-xls (1.2.0)
nokogiri nokogiri
roo (>= 2.0.0, < 3) roo (>= 2.0.0, < 3)
spreadsheet (> 0.9.0) spreadsheet (> 0.9.0)
rspec (3.11.0) rspec (3.12.0)
rspec-core (~> 3.11.0) rspec-core (~> 3.12.0)
rspec-expectations (~> 3.11.0) rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.11.0) rspec-mocks (~> 3.12.0)
rspec-core (3.11.0) rspec-core (3.12.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-expectations (3.11.1) rspec-expectations (3.12.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-mocks (3.11.1) rspec-mocks (3.12.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-rails (5.1.2) rspec-rails (6.0.1)
actionpack (>= 5.2) actionpack (>= 6.1)
activesupport (>= 5.2) activesupport (>= 6.1)
railties (>= 5.2) railties (>= 6.1)
rspec-core (~> 3.10) rspec-core (~> 3.11)
rspec-expectations (~> 3.10) rspec-expectations (~> 3.11)
rspec-mocks (~> 3.10) rspec-mocks (~> 3.11)
rspec-support (~> 3.10) rspec-support (~> 3.11)
rspec-rerun (1.1.0) rspec-rerun (1.1.0)
rspec (~> 3.0) rspec (~> 3.0)
rspec-support (3.11.1) rspec-support (3.12.0)
rubocop (1.36.0) rswag-api (2.7.0)
railties (>= 3.1, < 7.1)
rswag-specs (2.7.0)
activesupport (>= 3.1, < 7.1)
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
rspec-core (>= 2.14)
rswag-ui (2.7.0)
actionpack (>= 3.1, < 7.1)
railties (>= 3.1, < 7.1)
rubocop (1.43.0)
json (~> 2.3) json (~> 2.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.1.2.1) parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.20.1, < 2.0) rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.21.0) rubocop-ast (1.24.1)
parser (>= 3.1.1.0) parser (>= 3.1.1.0)
rubocop-rails (2.16.1) rubocop-rails (2.17.4)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.13.2) rubocop-rspec (2.16.0)
rubocop (~> 1.33) rubocop (~> 1.33)
ruby-filemagic (0.7.3) ruby-filemagic (0.7.3)
ruby-ole (1.2.12.2) ruby-ole (1.2.12.2)
ruby-prof (1.4.3) ruby-prof (1.4.5)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby-units (3.0.0) ruby-units (3.0.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
@ -475,21 +517,21 @@ GEM
simple_form (5.1.0) simple_form (5.1.0)
actionpack (>= 5.2) actionpack (>= 5.2)
activemodel (>= 5.2) activemodel (>= 5.2)
simplecov (0.21.2) simplecov (0.22.0)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1) simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3) simplecov-html (0.12.3)
simplecov-lcov (0.8.0) simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
sinatra (3.0.4) sinatra (3.0.5)
mustermann (~> 3.0) mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4) rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.0.4) rack-protection (= 3.0.5)
tilt (~> 2.0) tilt (~> 2.0)
skinny (0.2.2) skinny (0.2.4)
eventmachine (~> 1.0) eventmachine (~> 1.0.0)
thin thin (>= 1.5, < 1.7)
spreadsheet (1.3.0) spreadsheet (1.3.0)
ruby-ole ruby-ole
sprockets (3.7.2) sprockets (3.7.2)
@ -503,17 +545,19 @@ GEM
sqlite3-ruby (1.3.3) sqlite3-ruby (1.3.3)
sqlite3 (>= 1.3.3) sqlite3 (>= 1.3.3)
table_print (1.5.7) table_print (1.5.7)
temple (0.8.2) temple (0.10.0)
terser (1.1.13)
execjs (>= 0.3.0, < 3)
therubyracer (0.12.3) therubyracer (0.12.3)
libv8 (~> 3.16.14.15) libv8 (~> 3.16.14.15)
ref ref
thin (1.8.1) thin (1.6.2)
daemons (~> 1.0, >= 1.0.9) daemons (>= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4) eventmachine (>= 1.0.0)
rack (>= 1, < 3) rack (>= 1.0.0)
thor (1.2.1) thor (1.2.1)
thread_safe (0.3.6)
tilt (2.0.11) tilt (2.0.11)
timeout (0.3.1)
ttfunk (1.7.0) ttfunk (1.7.0)
twitter-bootstrap-rails (2.2.8) twitter-bootstrap-rails (2.2.8)
actionpack (>= 3.1) actionpack (>= 3.1)
@ -522,20 +566,18 @@ GEM
railties (>= 3.1) railties (>= 3.1)
twitter-text (1.14.7) twitter-text (1.14.7)
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.10) tzinfo (2.0.5)
thread_safe (~> 0.1) concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.8.2) unf_ext (0.0.8.2)
unicode-display_width (2.3.0) unicode-display_width (2.4.2)
uniform_notifier (1.16.0) uniform_notifier (1.16.0)
web-console (3.7.0) web-console (4.2.0)
actionview (>= 5.0) actionview (>= 6.0.0)
activemodel (>= 5.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 5.0) railties (>= 6.0.0)
websocket-driver (0.7.5) websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
@ -549,15 +591,15 @@ GEM
twitter-text twitter-text
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.6)
PLATFORMS PLATFORMS
ruby x86_64-linux
DEPENDENCIES DEPENDENCIES
active_model_serializers (~> 0.10.0) active_model_serializers (~> 0.10.0)
acts_as_tree acts_as_tree
acts_as_versioned! acts_as_versioned!
apivore
apparition apparition
attribute_normalizer attribute_normalizer
better_errors better_errors
@ -582,19 +624,20 @@ DEPENDENCIES
foodsoft_polls! foodsoft_polls!
foodsoft_wiki! foodsoft_wiki!
gaffe gaffe
haml haml (~> 5.0)
haml-rails haml-rails
hashie (~> 3.4.6) hashie (~> 3.4.6)
i18n-js (~> 3.0.0.rc8) i18n-js (~> 3.0.0.rc8)
i18n-spec i18n-spec
ice_cube ice_cube
importmap-rails (~> 1.1)
inherited_resources inherited_resources
jquery-rails jquery-rails
kaminari kaminari
less-rails less-rails
listen listen
mail (~> 2.7.1)
mailcatcher mailcatcher
meta_request
midi-smtp-server midi-smtp-server
mime-types mime-types
mysql2 mysql2
@ -604,26 +647,30 @@ DEPENDENCIES
pry-stack_explorer pry-stack_explorer
puma puma
rack-cors rack-cors
rails (~> 5.2) rails (~> 7.0)
rails-assets-listjs (= 0.2.0.beta.4) rails-assets-listjs (= 0.2.0.beta.4)
rails-controller-testing
rails-i18n rails-i18n
rails-settings-cached (= 0.4.3) rails-settings-cached (= 0.4.3)
rails_tokeninput rails_tokeninput
ransack ransack
recurring_select recurring_select!
resque resque
roo roo
roo-xls roo-xls
rspec-core rspec-core
rspec-rails rspec-rails
rspec-rerun rspec-rerun
rswag-api
rswag-specs
rswag-ui
rubocop rubocop
rubocop-rails rubocop-rails
rubocop-rspec rubocop-rspec
ruby-filemagic ruby-filemagic
ruby-prof ruby-prof
ruby-units ruby-units
sass-rails sassc-rails
sd_notify sd_notify
select2-rails select2-rails
simple-navigation (~> 3.14.0) simple-navigation (~> 3.14.0)
@ -635,11 +682,11 @@ DEPENDENCIES
sprockets (< 4) sprockets (< 4)
sqlite3 (~> 1.3.6) sqlite3 (~> 1.3.6)
table_print table_print
terser (~> 1.1)
therubyracer therubyracer
twitter-bootstrap-rails (~> 2.2.8) twitter-bootstrap-rails (~> 2.2.8)
uglifier (>= 1.0.3)
web-console web-console
whenever whenever
BUNDLED WITH BUNDLED WITH
1.17.3 2.4.5

View file

@ -18,7 +18,7 @@ class Finance::FinancialTransactionsController < ApplicationController
sort = "created_on DESC" sort = "created_on DESC"
end end
@q = FinancialTransaction.search(params[:q]) @q = FinancialTransaction.ransack(params[:q])
@financial_transactions_all = @q.result(distinct: true).includes(:user).order(sort) @financial_transactions_all = @q.result(distinct: true).includes(:user).order(sort)
@financial_transactions_all = @financial_transactions_all.visible unless params[:show_hidden] @financial_transactions_all = @financial_transactions_all.visible unless params[:show_hidden]
@financial_transactions_all = @financial_transactions_all.where(ordergroup_id: @ordergroup.id) if @ordergroup @financial_transactions_all = @financial_transactions_all.where(ordergroup_id: @ordergroup.id) if @ordergroup

View file

@ -18,7 +18,7 @@ class HomeController < ApplicationController
@bank_accounts = @types.includes(:bank_account).map(&:bank_account).uniq.compact @bank_accounts = @types.includes(:bank_account).map(&:bank_account).uniq.compact
@bank_accounts = [BankAccount.last] if @bank_accounts.empty? @bank_accounts = [BankAccount.last] if @bank_accounts.empty?
else else
redirect_to root_url, alert: I18n.t('group_orders.errors.no_member') redirect_to root_path, alert: I18n.t('group_orders.errors.no_member')
end end
end end
@ -26,7 +26,7 @@ class HomeController < ApplicationController
if @current_user.update(user_params) if @current_user.update(user_params)
@current_user.ordergroup.update(ordergroup_params) if ordergroup_params @current_user.ordergroup.update(ordergroup_params) if ordergroup_params
session[:locale] = @current_user.locale session[:locale] = @current_user.locale
redirect_to my_profile_url, notice: I18n.t('home.changes_saved') redirect_to my_profile_path, notice: I18n.t('home.changes_saved')
else else
render :profile render :profile
end end
@ -64,7 +64,7 @@ class HomeController < ApplicationController
# cancel personal memberships direct from the myProfile-page # cancel personal memberships direct from the myProfile-page
def cancel_membership def cancel_membership
if params[:membership_id] if params[:membership_id]
membership = @current_user.memberships.find!(params[:membership_id]) membership = @current_user.memberships.find(params[:membership_id])
else else
membership = @current_user.memberships.find_by_group_id!(params[:group_id]) membership = @current_user.memberships.find_by_group_id!(params[:group_id])
end end

View file

@ -0,0 +1 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails

View file

@ -14,23 +14,23 @@ class AppleBar
def group_bar_state def group_bar_state
if apples >= 100 if apples >= 100
'success' 'success'
else elsif FoodsoftConfig[:stop_ordering_under].present? &&
if FoodsoftConfig[:stop_ordering_under].present? and (apples >= FoodsoftConfig[:stop_ordering_under])
apples >= FoodsoftConfig[:stop_ordering_under]
'warning' 'warning'
else else
'danger' 'danger'
end end
end end
end
# Use apples as percentage, but show at least 10 percent # Use apples as percentage, but show at least 10 percent
def group_bar_width def group_bar_width
@ordergroup.apples < 2 ? 2 : @ordergroup.apples [@ordergroup.apples, 2].max
end end
def mean_order_amount_per_job def mean_order_amount_per_job
(1 / @global_avg).round rescue 0 (1 / @global_avg).round
rescue
0
end end
def apples def apples

View file

@ -1,4 +1,4 @@
class ArticlesCsv < RenderCSV class ArticlesCsv < RenderCsv
include ApplicationHelper include ApplicationHelper
def header def header
@ -16,7 +16,7 @@ class ArticlesCsv < RenderCSV
Article.human_attribute_name(:unit_quantity), Article.human_attribute_name(:unit_quantity),
'', '',
'', '',
Article.human_attribute_name(:article_category), Article.human_attribute_name(:article_category)
] ]
end end
@ -36,7 +36,7 @@ class ArticlesCsv < RenderCSV
o.unit_quantity, o.unit_quantity,
'', '',
'', '',
o.article_category.try(:name), o.article_category.try(:name)
] ]
end end
end end

View file

@ -8,9 +8,7 @@ class BankAccountConnector
nil nil
end end
def text attr_reader :text
@text
end
end end
class TextField class TextField
@ -24,13 +22,7 @@ class BankAccountConnector
nil nil
end end
def name attr_reader :name, :value
@name
end
def value
@value
end
def label def label
@label || @name.to_s @label || @name.to_s
@ -73,17 +65,7 @@ class BankAccountConnector
@bank_account.iban @bank_account.iban
end end
def auto_submit attr_reader :auto_submit, :controls, :count
@auto_submit
end
def controls
@controls
end
def count
@count
end
def text(data) def text(data)
@controls += [TextItem.new(data)] @controls += [TextItem.new(data)]
@ -142,11 +124,9 @@ class BankAccountConnector
@bank_account.save! @bank_account.save!
end end
def load(data) def load(data); end
end
def dump def dump; end
end
def t(key, args = {}) def t(key, args = {})
return t(".fields.#{key}") unless key.is_a? String return t(".fields.#{key}") unless key.is_a? String

View file

@ -1,7 +1,7 @@
class BankTransactionReference class BankTransactionReference
# parses a string from a bank transaction field # parses a string from a bank transaction field
def self.parse(data) def self.parse(data)
m = /(^|[^\w\.])FS(?<group>\d+)(\.(?<user>\d+))?(?<parts>([A-Za-z]+\d+(\.\d+)?)+)([^\w\.]|$)/.match(data) m = /(^|[^\w.])FS(?<group>\d+)(\.(?<user>\d+))?(?<parts>([A-Za-z]+\d+(\.\d+)?)+)([^\w.]|$)/.match(data)
return unless m return unless m
parts = {} parts = {}
@ -13,7 +13,7 @@ class BankTransactionReference
ret = { group: m[:group].to_i, parts: parts } ret = { group: m[:group].to_i, parts: parts }
ret[:user] = m[:user].to_i if m[:user] ret[:user] = m[:user].to_i if m[:user]
return ret ret
end end
def self.js_code_for_user(user) def self.js_code_for_user(user)

View file

@ -1,6 +1,6 @@
require 'csv' require 'csv'
class BankTransactionsCsv < RenderCSV class BankTransactionsCsv < RenderCsv
include ApplicationHelper include ApplicationHelper
def header def header

View file

@ -27,12 +27,20 @@ module DateTimeAttributeValidate
define_method("#{attribute}_date_value=") do |val| define_method("#{attribute}_date_value=") do |val|
self.instance_variable_set("@#{attribute}_is_set", true) self.instance_variable_set("@#{attribute}_is_set", true)
self.instance_variable_set("@#{attribute}_date_value", val) self.instance_variable_set("@#{attribute}_date_value", val)
self.send("#{attribute}_date=", val) rescue nil begin
self.send("#{attribute}_date=", val)
rescue
nil
end
end end
define_method("#{attribute}_time_value=") do |val| define_method("#{attribute}_time_value=") do |val|
self.instance_variable_set("@#{attribute}_is_set", true) self.instance_variable_set("@#{attribute}_is_set", true)
self.instance_variable_set("@#{attribute}_time_value", val) self.instance_variable_set("@#{attribute}_time_value", val)
self.send("#{attribute}_time=", val) rescue nil begin
self.send("#{attribute}_time=", val)
rescue
nil
end
end end
# fallback to field when values are not set # fallback to field when values are not set
@ -48,11 +56,19 @@ module DateTimeAttributeValidate
# validate date and time # validate date and time
define_method("#{attribute}_datetime_value_valid") do define_method("#{attribute}_datetime_value_valid") do
date = self.instance_variable_get("@#{attribute}_date_value") date = self.instance_variable_get("@#{attribute}_date_value")
unless date.blank? || (Date.parse(date) rescue nil) unless date.blank? || begin
Date.parse(date)
rescue
nil
end
errors.add(attribute, "is not a valid date") # @todo I18n errors.add(attribute, "is not a valid date") # @todo I18n
end end
time = self.instance_variable_get("@#{attribute}_time_value") time = self.instance_variable_get("@#{attribute}_time_value")
unless time.blank? || (Time.parse(time) rescue nil) unless time.blank? || begin
Time.parse(time)
rescue
nil
end
errors.add(attribute, "is not a valid time") # @todo I18n errors.add(attribute, "is not a valid time") # @todo I18n
end end
end end

View file

@ -1,6 +1,6 @@
require 'csv' require 'csv'
class FinancialTransactionsCsv < RenderCSV class FinancialTransactionsCsv < RenderCsv
include ApplicationHelper include ApplicationHelper
def header def header

View file

@ -54,8 +54,8 @@ module Foodsoft
# @param options [Hash<String, String>] Extra variables to expand # @param options [Hash<String, String>] Extra variables to expand
# @return [String] Expanded string # @return [String] Expanded string
def self.expand(str, options = {}) def self.expand(str, options = {})
str.gsub /{{([._a-zA-Z0-9]+)}}/ do str.gsub(/{{([._a-zA-Z0-9]+)}}/) do
options[$1] || self.get($1) options[::Regexp.last_match(1)] || self.get(::Regexp.last_match(1))
end end
end end

View file

@ -44,6 +44,8 @@ class FoodsoftConfig
# @return [ActiveSupport::HashWithIndifferentAccess] Current configuration from configuration file. # @return [ActiveSupport::HashWithIndifferentAccess] Current configuration from configuration file.
mattr_accessor :config mattr_accessor :config
mattr_accessor :default_config
# Configuration file location. # Configuration file location.
# Taken from environment variable +FOODSOFT_APP_CONFIG+, # Taken from environment variable +FOODSOFT_APP_CONFIG+,
# or else +config/app_config.yml+. # or else +config/app_config.yml+.
@ -189,7 +191,7 @@ class FoodsoftConfig
# @return [Hash] Full configuration. # @return [Hash] Full configuration.
def to_hash def to_hash
keys.to_h { |k| [k, self[k]] } keys.index_with { |k| self[k] }
end end
# for using active_model_serializer in the api/v1/configs controller # for using active_model_serializer in the api/v1/configs controller
@ -216,7 +218,6 @@ class FoodsoftConfig
# end # end
# #
# @return [Hash] Default configuration values # @return [Hash] Default configuration values
mattr_accessor :default_config
private private
@ -229,7 +230,7 @@ class FoodsoftConfig
end end
def setup_database def setup_database
database_config = ActiveRecord::Base.configurations[Rails.env] database_config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
database_config = database_config.merge(config[:database]) if config[:database].present? database_config = database_config.merge(config[:database]) if config[:database].present?
ActiveRecord::Base.establish_connection(database_config) ActiveRecord::Base.establish_connection(database_config)
end end

View file

@ -6,7 +6,11 @@ module FoodsoftDateUtil
schedule = IceCube::Schedule.new(start) schedule = IceCube::Schedule.new(start)
schedule.add_recurrence_rule rule_from(options[:recurr]) schedule.add_recurrence_rule rule_from(options[:recurr])
# @todo handle ical parse errors # @todo handle ical parse errors
occ = (schedule.next_occurrence(from).to_time rescue nil) occ = begin
schedule.next_occurrence(from).to_time
rescue
nil
end
end end
if options && options[:time] && occ if options && options[:time] && occ
occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight) occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight)
@ -17,9 +21,10 @@ module FoodsoftDateUtil
# @param p [String, Symbol, Hash, IceCube::Rule] What to return a rule from. # @param p [String, Symbol, Hash, IceCube::Rule] What to return a rule from.
# @return [IceCube::Rule] Recurring rule # @return [IceCube::Rule] Recurring rule
def self.rule_from(p) def self.rule_from(p)
if p.is_a? String case p
when String
IceCube::Rule.from_ical(p) IceCube::Rule.from_ical(p)
elsif p.is_a? Hash when Hash
IceCube::Rule.from_hash(p) IceCube::Rule.from_hash(p)
else else
p p

View file

@ -19,7 +19,7 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
private private
def on_rcpt_to_event(ctx, rcpt_to) def on_rcpt_to_event(_ctx, rcpt_to)
recipient = rcpt_to.gsub(/^\s*<\s*(.*)\s*>\s*$/, '\1') recipient = rcpt_to.gsub(/^\s*<\s*(.*)\s*>\s*$/, '\1')
@handlers << self.class.find_handler(recipient) @handlers << self.class.find_handler(recipient)
rcpt_to rcpt_to
@ -29,7 +29,6 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
end end
def on_message_data_event(ctx) def on_message_data_event(ctx)
begin
@handlers.each do |handler| @handlers.each do |handler|
handler.call(ctx[:message][:data]) handler.call(ctx[:message][:data])
end end
@ -39,10 +38,9 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
ensure ensure
@handlers.clear @handlers.clear
end end
end
def self.find_handler(recipient) def self.find_handler(recipient)
m = /(?<foodcoop>[^@\.]+)\.(?<address>[^@]+)(@(?<hostname>[^@]+))?/.match recipient m = /(?<foodcoop>[^@.]+)\.(?<address>[^@]+)(@(?<hostname>[^@]+))?/.match recipient
raise "recipient is missing or has an invalid format" if m.nil? raise "recipient is missing or has an invalid format" if m.nil?
raise "Foodcoop '#{m[:foodcoop]}' could not be found" unless FoodsoftConfig.allowed_foodcoop? m[:foodcoop] raise "Foodcoop '#{m[:foodcoop]}' could not be found" unless FoodsoftConfig.allowed_foodcoop? m[:foodcoop]
@ -51,7 +49,7 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
@@registered_classes.each do |klass| @@registered_classes.each do |klass|
if match = klass.regexp.match(m[:address]) if match = klass.regexp.match(m[:address])
handler = klass.new match handler = klass.new match
return lambda { |data| handler.received(data) } return ->(data) { handler.received(data) }
end end
end end

View file

@ -1,6 +1,6 @@
require 'csv' require 'csv'
class InvoicesCsv < RenderCSV class InvoicesCsv < RenderCsv
include ApplicationHelper include ApplicationHelper
def header def header
@ -32,7 +32,7 @@ class InvoicesCsv < RenderCSV
t.deposit, t.deposit,
t.deposit_credit, t.deposit_credit,
t.paid_on, t.paid_on,
t.note, t.note
] ]
end end
end end

View file

@ -1,6 +1,6 @@
require 'csv' require 'csv'
class OrderCsv < RenderCSV class OrderCsv < RenderCsv
def header def header
[ [
OrderArticle.human_attribute_name(:units_to_order), OrderArticle.human_attribute_name(:units_to_order),

View file

@ -1,4 +1,4 @@
class OrderPdf < RenderPDF class OrderPdf < RenderPdf
attr_reader :order attr_reader :order
def initialize(order, options = {}) def initialize(order, options = {})

View file

@ -1,5 +1,5 @@
class OrderTxt class OrderTxt
def initialize(order, options = {}) def initialize(order, _options = {})
@order = order @order = order
end end
@ -15,10 +15,10 @@ class OrderTxt
text += "****** " + I18n.t('orders.fax.to_address') + "\n\n" text += "****** " + I18n.t('orders.fax.to_address') + "\n\n"
text += "#{FoodsoftConfig[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n" text += "#{FoodsoftConfig[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n"
text += "****** " + I18n.t('orders.fax.articles') + "\n\n" text += "****** " + I18n.t('orders.fax.articles') + "\n\n"
text += "%8s %8s %s\n" % [I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'), I18n.t('orders.fax.name')] text += format("%8s %8s %s\n", I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'), I18n.t('orders.fax.name'))
# now display all ordered articles # now display all ordered articles
@order.order_articles.ordered.includes([:article, :article_price]).each do |oa| @order.order_articles.ordered.includes([:article, :article_price]).each do |oa|
text += "%8s %8d %s\n" % [oa.article.order_number, oa.units_to_order.to_i, oa.article.name] text += format("%8s %8d %s\n", oa.article.order_number, oa.units_to_order.to_i, oa.article.name)
end end
text text
end end

View file

@ -1,4 +1,4 @@
class OrdergroupsCsv < RenderCSV class OrdergroupsCsv < RenderCsv
include ApplicationHelper include ApplicationHelper
def header def header
@ -14,9 +14,9 @@ class OrdergroupsCsv < RenderCSV
Ordergroup.human_attribute_name(:break_start), Ordergroup.human_attribute_name(:break_start),
Ordergroup.human_attribute_name(:break_end), Ordergroup.human_attribute_name(:break_end),
Ordergroup.human_attribute_name(:last_user_activity), Ordergroup.human_attribute_name(:last_user_activity),
Ordergroup.human_attribute_name(:last_order), Ordergroup.human_attribute_name(:last_order)
] ]
row + Ordergroup.custom_fields.map { |f| f[:label] } row + Ordergroup.custom_fields.pluck(:label)
end end
def data def data
@ -33,7 +33,7 @@ class OrdergroupsCsv < RenderCSV
o.break_start, o.break_start,
o.break_end, o.break_end,
o.last_user_activity, o.last_user_activity,
o.last_order.try(:starts), o.last_order.try(:starts)
] ]
yield row + Ordergroup.custom_fields.map { |f| o.settings.custom_fields[f[:name]] } yield row + Ordergroup.custom_fields.map { |f| o.settings.custom_fields[f[:name]] }
end end

View file

@ -1,6 +1,6 @@
require 'csv' require 'csv'
class RenderCSV class RenderCsv
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
def initialize(object, options = {}) def initialize(object, options = {})

View file

@ -18,7 +18,7 @@ class RotatedCell < Prawn::Table::Cell::Text
(height + (border_top_width / 2.0) + (border_bottom_width / 2.0)) / tan_rotation (height + (border_top_width / 2.0) + (border_bottom_width / 2.0)) / tan_rotation
end end
def styled_width_of(text) def styled_width_of(_text)
options = @text_options.reject { |k| k == :style } options = @text_options.reject { |k| k == :style }
with_font { (@pdf.height_of(@content, options) + padding_top + padding_bottom) / tan_rotation } with_font { (@pdf.height_of(@content, options) + padding_top + padding_bottom) / tan_rotation }
end end
@ -52,7 +52,7 @@ class RotatedCell < Prawn::Table::Cell::Text
end end
end end
class RenderPDF < Prawn::Document class RenderPdf < Prawn::Document
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
include ApplicationHelper include ApplicationHelper
@ -156,9 +156,10 @@ class RenderPDF < Prawn::Document
def pdf_add_page_breaks?(docid = nil) def pdf_add_page_breaks?(docid = nil)
docid ||= self.class.name.underscore docid ||= self.class.name.underscore
cfg = FoodsoftConfig[:pdf_add_page_breaks] cfg = FoodsoftConfig[:pdf_add_page_breaks]
if cfg.is_a? Array case cfg
when Array
cfg.index(docid.to_s).any? cfg.index(docid.to_s).any?
elsif cfg.is_a? Hash when Hash
cfg[docid.to_s] cfg[docid.to_s]
else else
cfg cfg

View file

@ -21,8 +21,6 @@ class TokenVerifier < ActiveSupport::MessageVerifier
# return original message # return original message
if r.length > 2 if r.length > 2
r[2] r[2]
else
nil
end end
end end
@ -32,8 +30,6 @@ class TokenVerifier < ActiveSupport::MessageVerifier
class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end
protected
def self.secret def self.secret
# secret_key_base for Rails 4, but Rails 3 initializer may still be used # secret_key_base for Rails 4, but Rails 3 initializer may still be used
Foodsoft::Application.config.secret_key_base || Foodsoft::Application.config.secret_token Foodsoft::Application.config.secret_key_base || Foodsoft::Application.config.secret_token

View file

@ -1,4 +1,4 @@
class UsersCsv < RenderCSV class UsersCsv < RenderCsv
include ApplicationHelper include ApplicationHelper
def header def header

View file

@ -32,8 +32,8 @@ class GroupOrder < ApplicationRecord
# Generate some data for the javascript methods in ordering view # Generate some data for the javascript methods in ordering view
def load_data def load_data
data = {} data = {}
data[:account_balance] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.account_balance data[:account_balance] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.account_balance
data[:available_funds] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.get_available_funds(self) data[:available_funds] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.get_available_funds(self)
# load prices and other stuff.... # load prices and other stuff....
data[:order_articles] = {} data[:order_articles] = {}

View file

@ -8,10 +8,10 @@
= csrf_meta_tags = csrf_meta_tags
= stylesheet_link_tag "application", :media => "all" = stylesheet_link_tag "application", :media => "all"
//%link(href="images/favicon.ico" rel="shortcut icon") //%link(href="images/favicon.ico" rel="shortcut icon")
= yield(:head) = yield(:head)
= foodcoop_css_tag = foodcoop_css_tag
%body %body
= yield = yield
@ -19,7 +19,9 @@
Javascripts Javascripts
\================================================== \==================================================
/ Placed at the end of the document so the pages load faster / Placed at the end of the document so the pages load faster
= javascript_include_tag "application" = javascript_importmap_tags
= javascript_include_tag "application_legacy"
:javascript :javascript
I18n.defaultLocale = "#{I18n.default_locale}"; I18n.defaultLocale = "#{I18n.default_locale}";
I18n.locale = "#{I18n.locale}"; I18n.locale = "#{I18n.locale}";

4
bin/importmap Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env ruby
require_relative "../config/application"
require "importmap/commands"

View file

@ -1,36 +1,33 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'fileutils' require "fileutils"
include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = File.expand_path('..', __dir__) APP_ROOT = File.expand_path("..", __dir__)
def system!(*args) def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
end end
chdir APP_ROOT do FileUtils.chdir APP_ROOT do
# This script is a starting point to setup your application. # This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file. # Add necessary setup steps to this file.
puts '== Installing dependencies ==' puts "== Installing dependencies =="
system! 'gem install bundler --conservative' system! "gem install bundler --conservative"
system('bundle check') || system!('bundle install') system("bundle check") || system!("bundle install")
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
# puts "\n== Copying sample files ==" # puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml') # unless File.exist?("config/database.yml")
# cp 'config/database.yml.sample', 'config/database.yml' # FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end # end
puts "\n== Preparing database ==" puts "\n== Preparing database =="
system! 'bin/rails db:setup' system! "bin/rails db:prepare"
puts "\n== Removing old logs and tempfiles ==" puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear' system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server ==" puts "\n== Restarting application server =="
system! 'bin/rails restart' system! "bin/rails restart"
end end

View file

@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
module Foodsoft module Foodsoft
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.0 config.load_defaults 7.0
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers # Application configuration can go into files in config/initializers
@ -36,9 +36,6 @@ module Foodsoft
# Configure the default encoding used in templates for Ruby 1.9. # Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8" config.encoding = "utf-8"
# TODO: Remove this. See CVE-2022-32224 for details.
config.active_record.yaml_column_permitted_classes = [BigDecimal, Date, Symbol, Time]
# Enable escaping HTML in JSON. # Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true config.active_support.escape_html_entities_in_json = true
@ -66,6 +63,12 @@ module Foodsoft
# Load legacy scripts from vendor # Load legacy scripts from vendor
config.assets.precompile += ['vendor/assets/javascripts/*.js'] config.assets.precompile += ['vendor/assets/javascripts/*.js']
config.active_record.yaml_column_permitted_classes = [Symbol, BigDecimal]
config.autoloader = :zeitwerk
# Ex:- :default =>''
# CORS for API # CORS for API
config.middleware.insert_before 0, Rack::Cors do config.middleware.insert_before 0, Rack::Cors do
allow do allow do

View file

@ -1,3 +1,5 @@
require "active_support/core_ext/integer/time"
# Foodsoft production configuration. # Foodsoft production configuration.
# #
# This file is in the public domain. # This file is in the public domain.
@ -27,23 +29,23 @@ Rails.application.configure do
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS. # Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier config.assets.js_compressor = :terser
config.assets.css_compressor = :sass config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed. # Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false config.assets.compile = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server. # Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com' # config.asset_host = "http://assets.example.com"
# Specifies the header that your server uses for sending files. # Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options) # Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local config.active_storage.service = :local
# Mount Action Cable outside main process or domain # Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil # config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable' # config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
@ -51,6 +53,8 @@ Rails.application.configure do
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = ENV["RAILS_FORCE_SSL"] != "false" config.force_ssl = ENV["RAILS_FORCE_SSL"] != "false"
# Include generic and useful information about system operation, but avoid logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII).
# Set to :debug to see everything in the log. # Set to :debug to see everything in the log.
config.log_level = :info config.log_level = :info
@ -63,6 +67,10 @@ Rails.application.configure do
# Use a different cache store in production. # Use a different cache store in production.
# config.cache_store = :mem_cache_store # config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "foodsoft_production"
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors. # Ignore bad email addresses and do not raise email delivery errors.
@ -98,7 +106,7 @@ Rails.application.configure do
end end
# Use default logging formatter so that PID and timestamp are not suppressed. # Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new config.log_formatter = Logger::Formatter.new
# Use a different logger for distributed setups. # Use a different logger for distributed setups.
# require 'syslog/logger' # require 'syslog/logger'

View file

@ -1,30 +1,31 @@
# Foodsoft test configuration. require "active_support/core_ext/integer/time"
#
# This file is in the public domain. # The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's # Turn false under Spring and add config.action_view.cache_template_loading = true.
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application # Eager loading loads your whole application. When running a single test locally,
# just for the purpose of running a single test. If you are using a tool that # this probably isn't necessary. It's a good idea to do in a continuous integration
# preloads Rails for running tests, you may have to set it to true. # system, or in some way before deploying your code.
config.eager_load = false config.eager_load = ENV["CI"].present?
# Configure public file server for tests with Cache-Control for performance. # Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true config.public_file_server.enabled = true
config.public_file_server.headers = { config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}" "Cache-Control" => "public, max-age=#{1.hour.to_i}"
} }
# Show full error reports and disable caching. # Show full error reports and disable caching.
config.consider_all_requests_local = true config.consider_all_requests_local = true
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates. # Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false config.action_dispatch.show_exceptions = false
@ -32,7 +33,7 @@ Rails.application.configure do
# Disable request forgery protection in test environment. # Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory # Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test config.active_storage.service = :test
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
@ -45,6 +46,15 @@ Rails.application.configure do
# Print deprecation notices to the stderr. # Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr config.active_support.deprecation = :stderr
# Raises error for missing translations # Raise exceptions for disallowed deprecations.
# config.action_view.raise_on_missing_translations = true config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
end end

2
config/importmap.rb Normal file
View file

@ -0,0 +1,2 @@
# Pin npm packages by running ./bin/importmap
pin "application", preload: true

View file

@ -5,10 +5,8 @@ Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path. # Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path # Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets. # Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets # application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added. # folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css ) Rails.application.config.assets.precompile += %w( application_legacy.js jquery.min.js )

View file

@ -1,25 +1,25 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Define an application-wide content security policy # Define an application-wide content security policy.
# For further information see the following documentation # See the Securing Rails Applications Guide for more information:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy # https://guides.rubyonrails.org/security.html#content-security-policy-header
# Rails.application.config.content_security_policy do |policy| # Rails.application.configure do
# config.content_security_policy do |policy|
# policy.default_src :self, :https # policy.default_src :self, :https
# policy.font_src :self, :https, :data # policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data # policy.img_src :self, :https, :data
# policy.object_src :none # policy.object_src :none
# policy.script_src :self, :https # policy.script_src :self, :https
# policy.style_src :self, :https # policy.style_src :self, :https
# # Specify URI for violation reports # # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint" # # policy.report_uri "/csp-violation-report-endpoint"
# end # end
#
# If you are using UJS then enable automatic nonce generation # # Generate session nonces for permitted importmap and inline scripts
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
# config.content_security_policy_nonce_directives = %w(script-src)
# Report CSP violations to a specified URI #
# For further information see the following documentation: # # Report violations without enforcing the policy.
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only # # config.content_security_policy_report_only = true
# Rails.application.config.content_security_policy_report_only = true # end

View file

@ -0,0 +1,16 @@
# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
# Read more: https://github.com/cyu/rack-cors
# Rails.application.config.middleware.insert_before 0, Rack::Cors do
# allow do
# origins "example.com"
#
# resource "*",
# headers: :any,
# methods: [:get, :post, :put, :patch, :delete, :options, :head]
# end
# end

View file

@ -1,7 +1,7 @@
# remove all currency translations, so that we can set the default language and # remove all currency translations, so that we can set the default language and
# have it shown in all other languages too # have it shown in all other languages too
::I18n.available_locales.each do |locale| I18n.available_locales.each do |locale|
unless locale == ::I18n.default_locale unless locale == I18n.default_locale
::I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } }) I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } })
end end
end end

View file

@ -3,7 +3,7 @@ class String
# remove comma from decimal inputs # remove comma from decimal inputs
def self.delocalized_decimal(string) def self.delocalized_decimal(string)
if !string.blank? and string.is_a?(String) if !string.blank? and string.is_a?(String)
BigDecimal.new(string.sub(',', '.')) BigDecimal(string.sub(',', '.'))
else else
string string
end end

View file

@ -1,4 +1,8 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file. # Configure parameters to be filtered from the log file. Use this to limit dissemination of
Rails.application.config.filter_parameters += [:password] # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
# notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
]

View file

@ -1 +1,3 @@
FoodsoftMailReceiver.register BounceMailReceiver Rails.application.config.to_prepare do
FoodsoftMailReceiver.register BounceMailReceiver
end

View file

@ -1,17 +0,0 @@
# Be sure to restart your server when you modify this file.
#
# This file contains migration options to ease your Rails 5.0 upgrade.
#
# Once upgraded flip defaults one by one to migrate to the new default.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# Enable per-form CSRF tokens. Previous versions had false.
Rails.application.config.action_controller.per_form_csrf_tokens = false
# Enable origin-checking CSRF mitigation. Previous versions had false.
Rails.application.config.action_controller.forgery_protection_origin_check = false
# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
# Previous versions had false.
ActiveSupport.to_time_preserves_timezone = false

View file

@ -1,14 +0,0 @@
# Be sure to restart your server when you modify this file.
#
# This file contains migration options to ease your Rails 5.1 upgrade.
#
# Once upgraded flip defaults one by one to migrate to the new default.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# Make `form_with` generate non-remote forms.
Rails.application.config.action_view.form_with_generates_remote_forms = false
# Unknown asset fallback will return the path passed in when the given
# asset is not present in the asset pipeline.
# Rails.application.config.assets.unknown_asset_fallback = false

View file

@ -1,38 +0,0 @@
# Be sure to restart your server when you modify this file.
#
# This file contains migration options to ease your Rails 5.2 upgrade.
#
# Once upgraded flip defaults one by one to migrate to the new default.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# Make Active Record use stable #cache_key alongside new #cache_version method.
# This is needed for recyclable cache keys.
# Rails.application.config.active_record.cache_versioning = true
# Use AES-256-GCM authenticated encryption for encrypted cookies.
# Also, embed cookie expiry in signed or encrypted cookies for increased security.
#
# This option is not backwards compatible with earlier Rails versions.
# It's best enabled when your entire app is migrated and stable on 5.2.
#
# Existing cookies will be converted on read then written with the new scheme.
# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
# Rails.application.config.active_support.use_authenticated_message_encryption = true
# Add default protection from forgery to ActionController::Base instead of in
# ApplicationController.
# Rails.application.config.action_controller.default_protect_from_forgery = true
# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
# 'f' after migrating old data.
Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
# Rails.application.config.active_support.use_sha1_digests = true
# Make `form_with` generate id attributes for any generated HTML tags.
# Rails.application.config.action_view.form_with_generates_ids = true

View file

@ -0,0 +1,11 @@
# Define an application-wide HTTP permissions policy. For further
# information see https://developers.google.com/web/updates/2018/06/feature-policy
#
# Rails.application.config.permissions_policy do |f|
# f.camera :none
# f.gyroscope :none
# f.microphone :none
# f.usb :none
# f.fullscreen :self
# f.payment :self, "https://secure.example.com"
# end

View file

@ -1,98 +0,0 @@
raise "Remove no-longer-needed #{__FILE__}!" if Rails::VERSION::MAJOR >= 6
require "weakref"
module ActiveRecord
# Backport https://github.com/rails/rails/pull/36998 and https://github.com/rails/rails/pull/36999
# to avoid `ThreadError: can't create Thread: Resource temporarily unavailable` issues
module ConnectionAdapters
class ConnectionPool
class Reaper
@mutex = Mutex.new
@pools = {}
@threads = {}
class << self
def register_pool(pool, frequency) # :nodoc:
@mutex.synchronize do
unless @threads[frequency]&.alive?
@threads[frequency] = spawn_thread(frequency)
end
@pools[frequency] ||= []
@pools[frequency] << WeakRef.new(pool)
end
end
private
def spawn_thread(frequency)
Thread.new(frequency) do |t|
running = true
while running
sleep t
@mutex.synchronize do
@pools[frequency].select!(&:weakref_alive?)
@pools[frequency].each do |p|
p.reap
p.flush
rescue WeakRef::RefError
end
if @pools[frequency].empty?
@pools.delete(frequency)
@threads.delete(frequency)
running = false
end
end
end
end
end
end
def run
return unless frequency && frequency > 0
self.class.register_pool(pool, frequency)
end
end
def reap
stale_connections = synchronize do
return unless @connections
@connections.select do |conn|
conn.in_use? && !conn.owner.alive?
end.each(&:steal!)
end
stale_connections.each do |conn|
if conn.active?
conn.reset!
checkin conn
else
remove conn
end
end
end
def flush(minimum_idle = @idle_timeout)
return if minimum_idle.nil?
idle_connections = synchronize do
return unless @connections
@connections.select do |conn|
!conn.in_use? && conn.seconds_idle >= minimum_idle
end.each do |conn|
conn.lease
@available.delete conn
@connections.delete conn
end
end
idle_connections.each(&:disconnect!)
end
end
end
end

View file

@ -0,0 +1,13 @@
Rswag::Api.configure do |c|
# Specify a root folder where Swagger JSON files are located
# This is used by the Swagger middleware to serve requests for API descriptions
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
# that it's configured to generate files in the same folder
c.swagger_root = Rails.root.to_s + '/swagger'
# Inject a lambda function to alter the returned Swagger prior to serialization
# The function will have access to the rack env for the current request
# For example, you could leverage this to dynamically assign the "host" property
#
# c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
end

View file

@ -0,0 +1,15 @@
Rswag::Ui.configure do |c|
# List the Swagger endpoints that you want to be documented through the
# swagger-ui. The first parameter is the path (absolute or relative to the UI
# host) to the corresponding endpoint and the second is a title that will be
# displayed in the document selector.
# NOTE: If you're using rspec-api to expose Swagger files
# (under swagger_root) as JSON or YAML endpoints, then the list below should
# correspond to the relative paths for those endpoints.
c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs'
# Add Basic Auth in case your API is private
# c.basic_auth_enabled = true
# c.basic_auth_credentials 'username', 'password'
end

View file

@ -0,0 +1,5 @@
# config/initializers/zeitwerk.rb
ActiveSupport::Dependencies
.autoload_paths
.delete("#{Rails.root}/app/controllers/concerns")

View file

@ -1,4 +1,6 @@
Rails.application.routes.draw do Rails.application.routes.draw do
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
get "order_comments/new" get "order_comments/new"
get "comments/new" get "comments/new"

View file

@ -0,0 +1,22 @@
# This migration comes from active_storage (originally 20190112182829)
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
def up
return unless table_exists?(:active_storage_blobs)
unless column_exists?(:active_storage_blobs, :service_name)
add_column :active_storage_blobs, :service_name, :string
if configured_service = ActiveStorage::Blob.service.name
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
end
change_column :active_storage_blobs, :service_name, :string, null: false
end
end
def down
return unless table_exists?(:active_storage_blobs)
remove_column :active_storage_blobs, :service_name
end
end

View file

@ -0,0 +1,28 @@
# This migration comes from active_storage (originally 20191206030411)
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
def change
return unless table_exists?(:active_storage_blobs)
# Use Active Record's configured type for primary key
create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t|
t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
t.string :variation_digest, null: false
t.index [:blob_id, :variation_digest], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
private
def primary_key_type
config = Rails.configuration.generators
config.options[config.orm][:primary_key_type] || :primary_key
end
def blobs_primary_key_type
pkey_name = connection.primary_key(:active_storage_blobs)
pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
pkey_column.bigint? ? :bigint : pkey_column.type
end
end

View file

@ -0,0 +1,8 @@
# This migration comes from active_storage (originally 20211119233751)
class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]
def change
return unless table_exists?(:active_storage_blobs)
change_column_null(:active_storage_blobs, :checksum, true)
end
end

View file

@ -2,54 +2,60 @@
# of editing this file, please use the migrations feature of Active Record to # of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition. # incrementally modify your database, and then regenerate this schema definition.
# #
# Note that this schema.rb definition is the authoritative source for your # This file is the source Rails uses to define your schema when running `bin/rails
# database schema. If you need to create the application database on another # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# system, you should be using db:schema:load, not running all the migrations # be faster and is potentially less error prone than running all of your
# from scratch. The latter is a flawed and unsustainable approach (the more migrations # migrations from scratch. Old migrations may fail to apply correctly if those
# you'll amass, the slower it'll run and the greater likelihood for issues). # migrations use external dependencies or application code.
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_02_05_090257) do ActiveRecord::Schema[7.0].define(version: 2023_01_06_144440) do
create_table "active_storage_attachments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
create_table "active_storage_attachments", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "record_type", null: false t.string "record_type", null: false
t.bigint "record_id", null: false t.bigint "record_id", null: false
t.bigint "blob_id", null: false t.bigint "blob_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end end
create_table "active_storage_blobs", id: :integer, force: :cascade do |t| create_table "active_storage_blobs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.string "filename", null: false t.string "filename", null: false
t.string "content_type" t.string "content_type"
t.text "metadata" t.text "metadata"
t.bigint "byte_size", null: false t.bigint "byte_size", null: false
t.string "checksum", null: false t.string "checksum"
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.string "service_name", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end end
create_table "article_categories", id: :integer, force: :cascade do |t| create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "article_categories", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.string "description" t.string "description"
t.index ["name"], name: "index_article_categories_on_name", unique: true t.index ["name"], name: "index_article_categories_on_name", unique: true
end end
create_table "article_prices", id: :integer, force: :cascade do |t| create_table "article_prices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "article_id", null: false t.integer "article_id", null: false
t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false
t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false
t.decimal "deposit", precision: 8, scale: 2, default: "0.0", null: false t.decimal "deposit", precision: 8, scale: 2, default: "0.0", null: false
t.integer "unit_quantity" t.integer "unit_quantity"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.index ["article_id"], name: "index_article_prices_on_article_id" t.index ["article_id"], name: "index_article_prices_on_article_id"
end end
create_table "articles", id: :integer, force: :cascade do |t| create_table "articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.integer "supplier_id", default: 0, null: false t.integer "supplier_id", default: 0, null: false
t.integer "article_category_id", default: 0, null: false t.integer "article_category_id", default: 0, null: false
@ -58,15 +64,15 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.boolean "availability", default: true, null: false t.boolean "availability", default: true, null: false
t.string "manufacturer" t.string "manufacturer"
t.string "origin" t.string "origin"
t.datetime "shared_updated_on" t.datetime "shared_updated_on", precision: nil
t.decimal "price", precision: 8, scale: 2 t.decimal "price", precision: 8, scale: 2
t.float "tax" t.float "tax"
t.decimal "deposit", precision: 8, scale: 2, default: "0.0" t.decimal "deposit", precision: 8, scale: 2, default: "0.0"
t.integer "unit_quantity", default: 1, null: false t.integer "unit_quantity", default: 1, null: false
t.string "order_number" t.string "order_number"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.datetime "deleted_at" t.datetime "deleted_at", precision: nil
t.string "type" t.string "type"
t.integer "quantity", default: 0 t.integer "quantity", default: 0
t.index ["article_category_id"], name: "index_articles_on_article_category_id" t.index ["article_category_id"], name: "index_articles_on_article_category_id"
@ -75,31 +81,31 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["type"], name: "index_articles_on_type" t.index ["type"], name: "index_articles_on_type"
end end
create_table "assignments", id: :integer, force: :cascade do |t| create_table "assignments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "user_id", default: 0, null: false t.integer "user_id", default: 0, null: false
t.integer "task_id", default: 0, null: false t.integer "task_id", default: 0, null: false
t.boolean "accepted", default: false t.boolean "accepted", default: false
t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true
end end
create_table "bank_accounts", id: :integer, force: :cascade do |t| create_table "bank_accounts", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "iban" t.string "iban"
t.string "description" t.string "description"
t.decimal "balance", precision: 12, scale: 2, default: "0.0", null: false t.decimal "balance", precision: 12, scale: 2, default: "0.0", null: false
t.datetime "last_import" t.datetime "last_import", precision: nil
t.string "import_continuation_point" t.string "import_continuation_point"
t.integer "bank_gateway_id" t.integer "bank_gateway_id"
end end
create_table "bank_gateways", id: :integer, force: :cascade do |t| create_table "bank_gateways", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "url", null: false t.string "url", null: false
t.string "authorization" t.string "authorization"
t.integer "unattended_user_id" t.integer "unattended_user_id"
end end
create_table "bank_transactions", id: :integer, force: :cascade do |t| create_table "bank_transactions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "bank_account_id", null: false t.integer "bank_account_id", null: false
t.string "external_id" t.string "external_id"
t.date "date" t.date "date"
@ -108,32 +114,32 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.string "reference" t.string "reference"
t.text "text" t.text "text"
t.text "receipt" t.text "receipt"
t.binary "image", limit: 16777215 t.binary "image", size: :medium
t.integer "financial_link_id" t.integer "financial_link_id"
t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id" t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id"
end end
create_table "documents", id: :integer, force: :cascade do |t| create_table "documents", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name" t.string "name"
t.string "mime" t.string "mime"
t.binary "data", limit: 4294967295 t.binary "data", size: :long
t.integer "created_by_user_id" t.integer "created_by_user_id"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.integer "parent_id" t.integer "parent_id"
t.index ["parent_id"], name: "index_documents_on_parent_id" t.index ["parent_id"], name: "index_documents_on_parent_id"
end end
create_table "financial_links", id: :integer, force: :cascade do |t| create_table "financial_links", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.text "note" t.text "note"
end end
create_table "financial_transaction_classes", id: :integer, force: :cascade do |t| create_table "financial_transaction_classes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.boolean "ignore_for_account_balance", default: false, null: false t.boolean "ignore_for_account_balance", default: false, null: false
end end
create_table "financial_transaction_types", id: :integer, force: :cascade do |t| create_table "financial_transaction_types", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.integer "financial_transaction_class_id", null: false t.integer "financial_transaction_class_id", null: false
t.string "name_short" t.string "name_short"
@ -141,12 +147,12 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["name_short"], name: "index_financial_transaction_types_on_name_short" t.index ["name_short"], name: "index_financial_transaction_types_on_name_short"
end end
create_table "financial_transactions", id: :integer, force: :cascade do |t| create_table "financial_transactions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "ordergroup_id" t.integer "ordergroup_id"
t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false
t.text "note", null: false t.text "note", null: false
t.integer "user_id", default: 0, null: false t.integer "user_id", default: 0, null: false
t.datetime "created_on", null: false t.datetime "created_on", precision: nil, null: false
t.integer "financial_transaction_type_id", null: false t.integer "financial_transaction_type_id", null: false
t.integer "financial_link_id" t.integer "financial_link_id"
t.integer "reverts_id" t.integer "reverts_id"
@ -155,20 +161,20 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true
end end
create_table "group_order_article_quantities", id: :integer, force: :cascade do |t| create_table "group_order_article_quantities", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_order_article_id", default: 0, null: false t.integer "group_order_article_id", default: 0, null: false
t.integer "quantity", default: 0 t.integer "quantity", default: 0
t.integer "tolerance", default: 0 t.integer "tolerance", default: 0
t.datetime "created_on", null: false t.datetime "created_on", precision: nil, null: false
t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id" t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id"
end end
create_table "group_order_articles", id: :integer, force: :cascade do |t| create_table "group_order_articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_order_id", default: 0, null: false t.integer "group_order_id", default: 0, null: false
t.integer "order_article_id", default: 0, null: false t.integer "order_article_id", default: 0, null: false
t.integer "quantity", default: 0, null: false t.integer "quantity", default: 0, null: false
t.integer "tolerance", default: 0, null: false t.integer "tolerance", default: 0, null: false
t.datetime "updated_on", null: false t.datetime "updated_on", precision: nil, null: false
t.decimal "result", precision: 8, scale: 3 t.decimal "result", precision: 8, scale: 3
t.decimal "result_computed", precision: 8, scale: 3 t.decimal "result_computed", precision: 8, scale: 3
t.index ["group_order_id", "order_article_id"], name: "goa_index", unique: true t.index ["group_order_id", "order_article_id"], name: "goa_index", unique: true
@ -176,12 +182,12 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id" t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id"
end end
create_table "group_orders", id: :integer, force: :cascade do |t| create_table "group_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "ordergroup_id" t.integer "ordergroup_id"
t.integer "order_id", default: 0, null: false t.integer "order_id", default: 0, null: false
t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false
t.integer "lock_version", default: 0, null: false t.integer "lock_version", default: 0, null: false
t.datetime "updated_on", null: false t.datetime "updated_on", precision: nil, null: false
t.integer "updated_by_user_id" t.integer "updated_by_user_id"
t.decimal "transport", precision: 8, scale: 2 t.decimal "transport", precision: 8, scale: 2
t.index ["order_id"], name: "index_group_orders_on_order_id" t.index ["order_id"], name: "index_group_orders_on_order_id"
@ -189,18 +195,18 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id" t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id"
end end
create_table "groups", id: :integer, force: :cascade do |t| create_table "groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "type", default: "", null: false t.string "type", default: "", null: false
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.string "description" t.string "description"
t.decimal "account_balance", precision: 12, scale: 2, default: "0.0", null: false t.decimal "account_balance", precision: 12, scale: 2, default: "0.0", null: false
t.datetime "created_on", null: false t.datetime "created_on", precision: nil, null: false
t.boolean "role_admin", default: false, null: false t.boolean "role_admin", default: false, null: false
t.boolean "role_suppliers", default: false, null: false t.boolean "role_suppliers", default: false, null: false
t.boolean "role_article_meta", default: false, null: false t.boolean "role_article_meta", default: false, null: false
t.boolean "role_finance", default: false, null: false t.boolean "role_finance", default: false, null: false
t.boolean "role_orders", default: false, null: false t.boolean "role_orders", default: false, null: false
t.datetime "deleted_at" t.datetime "deleted_at", precision: nil
t.string "contact_person" t.string "contact_person"
t.string "contact_phone" t.string "contact_phone"
t.string "contact_address" t.string "contact_address"
@ -214,16 +220,16 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["name"], name: "index_groups_on_name", unique: true t.index ["name"], name: "index_groups_on_name", unique: true
end end
create_table "invites", id: :integer, force: :cascade do |t| create_table "invites", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "token", default: "", null: false t.string "token", default: "", null: false
t.datetime "expires_at", null: false t.datetime "expires_at", precision: nil, null: false
t.integer "group_id", default: 0, null: false t.integer "group_id", default: 0, null: false
t.integer "user_id", default: 0, null: false t.integer "user_id", default: 0, null: false
t.string "email", default: "", null: false t.string "email", default: "", null: false
t.index ["token"], name: "index_invites_on_token" t.index ["token"], name: "index_invites_on_token"
end end
create_table "invoices", id: :integer, force: :cascade do |t| create_table "invoices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.string "number" t.string "number"
t.date "date" t.date "date"
@ -232,16 +238,16 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false
t.decimal "deposit", precision: 8, scale: 2, default: "0.0", null: false t.decimal "deposit", precision: 8, scale: 2, default: "0.0", null: false
t.decimal "deposit_credit", precision: 8, scale: 2, default: "0.0", null: false t.decimal "deposit_credit", precision: 8, scale: 2, default: "0.0", null: false
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.integer "created_by_user_id" t.integer "created_by_user_id"
t.string "attachment_mime" t.string "attachment_mime"
t.binary "attachment_data", limit: 16777215 t.binary "attachment_data", size: :medium
t.integer "financial_link_id" t.integer "financial_link_id"
t.index ["supplier_id"], name: "index_invoices_on_supplier_id" t.index ["supplier_id"], name: "index_invoices_on_supplier_id"
end end
create_table "links", id: :integer, force: :cascade do |t| create_table "links", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "url", null: false t.string "url", null: false
t.integer "workgroup_id" t.integer "workgroup_id"
@ -249,81 +255,81 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.string "authorization" t.string "authorization"
end end
create_table "mail_delivery_status", id: :integer, force: :cascade do |t| create_table "mail_delivery_status", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.datetime "created_at" t.datetime "created_at", precision: nil
t.string "email", null: false t.string "email", null: false
t.string "message", null: false t.string "message", null: false
t.string "attachment_mime" t.string "attachment_mime"
t.binary "attachment_data", limit: 4294967295 t.binary "attachment_data", size: :long
t.index ["email"], name: "index_mail_delivery_status_on_email" t.index ["email"], name: "index_mail_delivery_status_on_email"
end end
create_table "memberships", id: :integer, force: :cascade do |t| create_table "memberships", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_id", default: 0, null: false t.integer "group_id", default: 0, null: false
t.integer "user_id", default: 0, null: false t.integer "user_id", default: 0, null: false
t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true
end end
create_table "message_recipients", id: :integer, force: :cascade do |t| create_table "message_recipients", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "message_id", null: false t.integer "message_id", null: false
t.integer "user_id", null: false t.integer "user_id", null: false
t.integer "email_state", default: 0, null: false t.integer "email_state", default: 0, null: false
t.datetime "read_at" t.datetime "read_at", precision: nil
t.index ["message_id"], name: "index_message_recipients_on_message_id" t.index ["message_id"], name: "index_message_recipients_on_message_id"
t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at" t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at"
end end
create_table "messages", id: :integer, force: :cascade do |t| create_table "messages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "sender_id" t.integer "sender_id"
t.string "subject", null: false t.string "subject", null: false
t.text "body" t.text "body"
t.boolean "private", default: false t.boolean "private", default: false
t.datetime "created_at" t.datetime "created_at", precision: nil
t.integer "reply_to" t.integer "reply_to"
t.integer "group_id" t.integer "group_id"
t.string "salt" t.string "salt"
t.binary "received_email", limit: 16777215 t.binary "received_email", size: :medium
end end
create_table "oauth_access_grants", id: :integer, force: :cascade do |t| create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "resource_owner_id", null: false t.integer "resource_owner_id", null: false
t.integer "application_id", null: false t.integer "application_id", null: false
t.string "token", null: false t.string "token", null: false
t.integer "expires_in", null: false t.integer "expires_in", null: false
t.text "redirect_uri", null: false t.text "redirect_uri", null: false
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.datetime "revoked_at" t.datetime "revoked_at", precision: nil
t.string "scopes" t.string "scopes"
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end end
create_table "oauth_access_tokens", id: :integer, force: :cascade do |t| create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "resource_owner_id" t.integer "resource_owner_id"
t.integer "application_id" t.integer "application_id"
t.string "token", null: false t.string "token", null: false
t.string "refresh_token" t.string "refresh_token"
t.integer "expires_in" t.integer "expires_in"
t.datetime "revoked_at" t.datetime "revoked_at", precision: nil
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.string "scopes" t.string "scopes"
t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id" t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end end
create_table "oauth_applications", id: :integer, force: :cascade do |t| create_table "oauth_applications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "uid", null: false t.string "uid", null: false
t.string "secret", null: false t.string "secret", null: false
t.text "redirect_uri", null: false t.text "redirect_uri", null: false
t.string "scopes", default: "", null: false t.string "scopes", default: "", null: false
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.boolean "confidential", default: true, null: false t.boolean "confidential", default: true, null: false
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end end
create_table "order_articles", id: :integer, force: :cascade do |t| create_table "order_articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "order_id", default: 0, null: false t.integer "order_id", default: 0, null: false
t.integer "article_id", default: 0, null: false t.integer "article_id", default: 0, null: false
t.integer "quantity", default: 0, null: false t.integer "quantity", default: 0, null: false
@ -337,45 +343,45 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["order_id"], name: "index_order_articles_on_order_id" t.index ["order_id"], name: "index_order_articles_on_order_id"
end end
create_table "order_comments", id: :integer, force: :cascade do |t| create_table "order_comments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "order_id" t.integer "order_id"
t.integer "user_id" t.integer "user_id"
t.text "text" t.text "text"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.index ["order_id"], name: "index_order_comments_on_order_id" t.index ["order_id"], name: "index_order_comments_on_order_id"
end end
create_table "orders", id: :integer, force: :cascade do |t| create_table "orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.text "note" t.text "note"
t.datetime "starts" t.datetime "starts", precision: nil
t.datetime "ends" t.datetime "ends", precision: nil
t.string "state", default: "open" t.string "state", default: "open"
t.integer "lock_version", default: 0, null: false t.integer "lock_version", default: 0, null: false
t.integer "updated_by_user_id" t.integer "updated_by_user_id"
t.decimal "foodcoop_result", precision: 8, scale: 2 t.decimal "foodcoop_result", precision: 8, scale: 2
t.integer "created_by_user_id" t.integer "created_by_user_id"
t.datetime "boxfill" t.datetime "boxfill", precision: nil
t.integer "invoice_id" t.integer "invoice_id"
t.date "pickup" t.date "pickup"
t.datetime "last_sent_mail" t.datetime "last_sent_mail", precision: nil
t.integer "end_action", default: 0, null: false t.integer "end_action", default: 0, null: false
t.decimal "transport", precision: 8, scale: 2 t.decimal "transport", precision: 8, scale: 2
t.index ["state"], name: "index_orders_on_state" t.index ["state"], name: "index_orders_on_state"
end end
create_table "page_versions", id: :integer, force: :cascade do |t| create_table "page_versions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "page_id" t.integer "page_id"
t.integer "lock_version" t.integer "lock_version"
t.text "body" t.text "body"
t.integer "updated_by" t.integer "updated_by"
t.integer "redirect" t.integer "redirect"
t.integer "parent_id" t.integer "parent_id"
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.index ["page_id"], name: "index_page_versions_on_page_id" t.index ["page_id"], name: "index_page_versions_on_page_id"
end end
create_table "pages", id: :integer, force: :cascade do |t| create_table "pages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "title" t.string "title"
t.text "body" t.text "body"
t.string "permalink" t.string "permalink"
@ -383,41 +389,41 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.integer "updated_by" t.integer "updated_by"
t.integer "redirect" t.integer "redirect"
t.integer "parent_id" t.integer "parent_id"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.index ["permalink"], name: "index_pages_on_permalink" t.index ["permalink"], name: "index_pages_on_permalink"
t.index ["title"], name: "index_pages_on_title" t.index ["title"], name: "index_pages_on_title"
end end
create_table "periodic_task_groups", id: :integer, force: :cascade do |t| create_table "periodic_task_groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.date "next_task_date" t.date "next_task_date"
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", null: false t.datetime "updated_at", precision: nil, null: false
end end
create_table "poll_choices", id: :integer, force: :cascade do |t| create_table "poll_choices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "poll_vote_id", null: false t.integer "poll_vote_id", null: false
t.integer "choice", null: false t.integer "choice", null: false
t.integer "value", null: false t.integer "value", null: false
t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true
end end
create_table "poll_votes", id: :integer, force: :cascade do |t| create_table "poll_votes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "poll_id", null: false t.integer "poll_id", null: false
t.integer "user_id", null: false t.integer "user_id", null: false
t.integer "ordergroup_id" t.integer "ordergroup_id"
t.text "note" t.text "note"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true
end end
create_table "polls", id: :integer, force: :cascade do |t| create_table "polls", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "created_by_user_id", null: false t.integer "created_by_user_id", null: false
t.string "name", null: false t.string "name", null: false
t.text "description" t.text "description"
t.datetime "starts" t.datetime "starts", precision: nil
t.datetime "ends" t.datetime "ends", precision: nil
t.boolean "one_vote_per_ordergroup", default: false, null: false t.boolean "one_vote_per_ordergroup", default: false, null: false
t.text "required_ordergroup_custom_fields" t.text "required_ordergroup_custom_fields"
t.text "required_user_custom_fields" t.text "required_user_custom_fields"
@ -427,66 +433,66 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.integer "multi_select_count", default: 0, null: false t.integer "multi_select_count", default: 0, null: false
t.integer "min_points" t.integer "min_points"
t.integer "max_points" t.integer "max_points"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.datetime "updated_at" t.datetime "updated_at", precision: nil
t.index ["final_choice"], name: "index_polls_on_final_choice" t.index ["final_choice"], name: "index_polls_on_final_choice"
end end
create_table "printer_job_updates", id: :integer, force: :cascade do |t| create_table "printer_job_updates", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "printer_job_id", null: false t.integer "printer_job_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.string "state", null: false t.string "state", null: false
t.text "message" t.text "message"
t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at" t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at"
end end
create_table "printer_jobs", id: :integer, force: :cascade do |t| create_table "printer_jobs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "order_id" t.integer "order_id"
t.string "document", null: false t.string "document", null: false
t.integer "created_by_user_id", null: false t.integer "created_by_user_id", null: false
t.integer "finished_by_user_id" t.integer "finished_by_user_id"
t.datetime "finished_at" t.datetime "finished_at", precision: nil
t.index ["finished_at"], name: "index_printer_jobs_on_finished_at" t.index ["finished_at"], name: "index_printer_jobs_on_finished_at"
end end
create_table "settings", id: :integer, force: :cascade do |t| create_table "settings", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "var", null: false t.string "var", null: false
t.text "value" t.text "value"
t.integer "thing_id" t.integer "thing_id"
t.string "thing_type", limit: 30 t.string "thing_type", limit: 30
t.datetime "created_at", null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", null: false t.datetime "updated_at", precision: nil, null: false
t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true
end end
create_table "stock_changes", id: :integer, force: :cascade do |t| create_table "stock_changes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "stock_event_id" t.integer "stock_event_id"
t.integer "order_id" t.integer "order_id"
t.integer "stock_article_id" t.integer "stock_article_id"
t.integer "quantity", default: 0 t.integer "quantity", default: 0
t.datetime "created_at" t.datetime "created_at", precision: nil
t.index ["stock_article_id"], name: "index_stock_changes_on_stock_article_id" t.index ["stock_article_id"], name: "index_stock_changes_on_stock_article_id"
t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id" t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id"
end end
create_table "stock_events", id: :integer, force: :cascade do |t| create_table "stock_events", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.date "date" t.date "date"
t.datetime "created_at" t.datetime "created_at", precision: nil
t.text "note" t.text "note"
t.integer "invoice_id" t.integer "invoice_id"
t.string "type", null: false t.string "type", null: false
t.index ["supplier_id"], name: "index_stock_events_on_supplier_id" t.index ["supplier_id"], name: "index_stock_events_on_supplier_id"
end end
create_table "supplier_categories", id: :integer, force: :cascade do |t| create_table "supplier_categories", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "description" t.string "description"
t.integer "financial_transaction_class_id" t.integer "financial_transaction_class_id"
t.integer "bank_account_id" t.integer "bank_account_id"
end end
create_table "suppliers", id: :integer, force: :cascade do |t| create_table "suppliers", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.string "address", default: "", null: false t.string "address", default: "", null: false
t.string "phone", default: "", null: false t.string "phone", default: "", null: false
@ -501,21 +507,21 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.string "note" t.string "note"
t.integer "shared_supplier_id" t.integer "shared_supplier_id"
t.string "min_order_quantity" t.string "min_order_quantity"
t.datetime "deleted_at" t.datetime "deleted_at", precision: nil
t.string "shared_sync_method" t.string "shared_sync_method"
t.string "iban" t.string "iban"
t.integer "supplier_category_id" t.integer "supplier_category_id"
t.index ["name"], name: "index_suppliers_on_name", unique: true t.index ["name"], name: "index_suppliers_on_name", unique: true
end end
create_table "tasks", id: :integer, force: :cascade do |t| create_table "tasks", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.text "description" t.text "description"
t.date "due_date" t.date "due_date"
t.boolean "done", default: false t.boolean "done", default: false
t.integer "workgroup_id" t.integer "workgroup_id"
t.datetime "created_on", null: false t.datetime "created_on", precision: nil, null: false
t.datetime "updated_on", null: false t.datetime "updated_on", precision: nil, null: false
t.integer "required_users", default: 1 t.integer "required_users", default: 1
t.integer "duration", default: 1 t.integer "duration", default: 1
t.integer "periodic_task_group_id" t.integer "periodic_task_group_id"
@ -525,7 +531,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id" t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id"
end end
create_table "users", id: :integer, force: :cascade do |t| create_table "users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "nick" t.string "nick"
t.string "password_hash", default: "", null: false t.string "password_hash", default: "", null: false
t.string "password_salt", default: "", null: false t.string "password_salt", default: "", null: false
@ -533,15 +539,16 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.string "last_name", default: "", null: false t.string "last_name", default: "", null: false
t.string "email", default: "", null: false t.string "email", default: "", null: false
t.string "phone" t.string "phone"
t.datetime "created_on", null: false t.datetime "created_on", precision: nil, null: false
t.string "reset_password_token" t.string "reset_password_token"
t.datetime "reset_password_expires" t.datetime "reset_password_expires", precision: nil
t.datetime "last_login" t.datetime "last_login", precision: nil
t.datetime "last_activity" t.datetime "last_activity", precision: nil
t.datetime "deleted_at" t.datetime "deleted_at", precision: nil
t.string "iban" t.string "iban"
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
t.index ["nick"], name: "index_users_on_nick", unique: true t.index ["nick"], name: "index_users_on_nick", unique: true
end end
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end end

View file

@ -8,10 +8,10 @@ def seed_group_orders
# order 3..12 times a random article # order 3..12 times a random article
go = og.group_orders.create!(order: order, updated_by_user_id: 1) go = og.group_orders.create!(order: order, updated_by_user_id: 1)
(3 + rand(10)).times do (rand(10) + 3).times do
goa = go.group_order_articles.find_or_create_by!(order_article: order.order_articles.offset(rand(noas)).first) goa = go.group_order_articles.find_or_create_by!(order_article: order.order_articles.offset(rand(noas)).first)
unit_quantity = goa.order_article.price.unit_quantity unit_quantity = goa.order_article.price.unit_quantity
goa.update_quantities rand([4, 2 * unit_quantity + 2].max), rand(unit_quantity) goa.update_quantities rand([4, unit_quantity * 2 + 2].max), rand(unit_quantity)
end end
end end
# update totals # update totals

View file

@ -5,9 +5,11 @@ like listing open orders, updating the ordergroup's order, and listing financial
transactions. Not all Foodsoft functionality is available through the API, but transactions. Not all Foodsoft functionality is available through the API, but
we're open for new additions. we're open for new additions.
The API is documented using [Open API 2.0](https://github.com/OAI/OpenAPI-Specification) The API is documented using [Open API 3.0.1](https://github.com/OAI/OpenAPI-Specification)
/ [Swagger](https://swagger.io/) in [swagger.v1.yml](swagger.v1.yml). / [Swagger](https://swagger.io/) in [swagger.yaml](/swagger/v1/swagger.yaml).
This provides a machine-readable reference that is used to provide documentation. This provides a machine-readable reference that is used to provide documentation.
It is generated by [rswag](https://github.com/rswag) wich also provides api-tests.
It can be generated running `RAILS_ENV=test rails rswag`.
**Note:** the current OAuth scopes may be subject to change, until the next release of Foodsoft. **Note:** the current OAuth scopes may be subject to change, until the next release of Foodsoft.

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile-dev dockerfile: Dockerfile-dev
platform: linux/x86_64
command: ./proc-start worker command: ./proc-start worker
volumes: volumes:
- bundle:/usr/local/bundle - bundle:/usr/local/bundle

View file

@ -32,7 +32,7 @@ class CurrentOrders::ArticlesController < ApplicationController
else else
@order_articles = OrderArticle.where(order_id: @current_orders.all.map(&:id)) @order_articles = OrderArticle.where(order_id: @current_orders.all.map(&:id))
end end
@q = OrderArticle.search(params[:q]) @q = OrderArticle.ransack(params[:q])
@order_articles = @order_articles.ordered.merge(@q.result).includes(:article, :article_price) @order_articles = @order_articles.ordered.merge(@q.result).includes(:article, :article_price)
@order_article = @order_articles.where(id: params[:id]).first @order_article = @order_articles.where(id: params[:id]).first
end end

View file

@ -1,59 +0,0 @@
require 'spec_helper'
# Most routes are tested in the swagger_spec, this tests (non-ransack) parameters.
describe Api::V1::OrderArticlesController, type: :controller do
include ApiOAuth
let(:api_scopes) { ['orders:read'] }
let(:json_order_articles) { json_response['order_articles'] }
let(:json_order_article_ids) { json_order_articles.map { |joa| joa["id"] } }
describe "GET :index" do
context "with param q[ordered]" do
let(:order) { create(:order, article_count: 4) }
let(:order_articles) { order.order_articles }
before do
order_articles[0].update!(quantity: 0, tolerance: 0, units_to_order: 0)
order_articles[1].update!(quantity: 1, tolerance: 0, units_to_order: 0)
order_articles[2].update!(quantity: 0, tolerance: 1, units_to_order: 0)
order_articles[3].update!(quantity: 0, tolerance: 0, units_to_order: 1)
end
it "(unset)" do
get :index, params: { foodcoop: 'f' }
expect(json_order_articles.count).to eq 4
end
it "all" do
get :index, params: { foodcoop: 'f', q: { ordered: 'all' } }
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
end
it "supplier" do
get :index, params: { foodcoop: 'f', q: { ordered: 'supplier' } }
expect(json_order_article_ids).to match_array [order_articles[3].id]
end
it "member" do
get :index, params: { foodcoop: 'f', q: { ordered: 'member' } }
expect(json_order_articles.count).to eq 0
end
context "when ordered by user" do
let(:user) { create(:user, :ordergroup) }
let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
before do
create(:group_order_article, group_order: go, order_article: order_articles[1], quantity: 1)
create(:group_order_article, group_order: go, order_article: order_articles[2], tolerance: 0)
end
it "member" do
get :index, params: { foodcoop: 'f', q: { ordered: 'member' } }
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
end
end
end
end
end

View file

@ -1,284 +0,0 @@
require 'spec_helper'
require 'apivore'
# we want to load a local file in YAML-format instead of a served JSON file
class SwaggerCheckerFile < Apivore::SwaggerChecker
def fetch_swagger!
YAML.load(File.read(swagger_path))
end
end
describe 'API v1', type: :apivore, order: :defined do
include ApiHelper
subject { SwaggerCheckerFile.instance_for Rails.root.join('doc', 'swagger.v1.yml') }
context 'has valid paths' do
context 'user' do
let(:api_scopes) { ['user:read'] }
# create multiple users to make sure we're getting the authenticated user, not just any
let!(:other_user_1) { create :user }
let!(:user) { create :user }
let!(:other_user_2) { create :user }
it { is_expected.to validate(:get, '/user', 200, api_auth) }
it { is_expected.to validate(:get, '/user', 401) }
it_handles_invalid_token_and_scope(:get, '/user')
end
context 'user/financial_overview' do
let(:api_scopes) { ['finance:user'] }
let!(:user) { create :user, :ordergroup }
it { is_expected.to validate(:get, '/user/financial_overview', 200, api_auth) }
it { is_expected.to validate(:get, '/user/financial_overview', 401) }
it_handles_invalid_token_and_scope(:get, '/user/financial_overview')
end
context 'user/financial_transactions' do
let(:api_scopes) { ['finance:user'] }
let(:other_user) { create :user, :ordergroup }
let!(:other_ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup }
context 'without ordergroup' do
it { is_expected.to validate(:get, '/user/financial_transactions', 403, api_auth) }
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 403, api_auth({ 'id' => other_ft_1.id })) }
end
context 'with ordergroup' do
let(:user) { create :user, :ordergroup }
let!(:ft_1) { create :financial_transaction, ordergroup: user.ordergroup }
let!(:ft_2) { create :financial_transaction, ordergroup: user.ordergroup }
let!(:ft_3) { create :financial_transaction, ordergroup: user.ordergroup }
let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id, note: 'note' } } } }
it { is_expected.to validate(:get, '/user/financial_transactions', 200, api_auth) }
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 200, api_auth({ 'id' => ft_2.id })) }
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => other_ft_1.id })) }
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({ 'id' => FinancialTransaction.last.id + 1 })) }
context 'without using self service' do
it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) }
end
context 'with using self service' do
before { FoodsoftConfig[:use_self_service] = true }
it { is_expected.to validate(:post, '/user/financial_transactions', 200, api_auth(create_params)) }
context 'with invalid financial transaction type' do
let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: -1, note: 'note' } } } }
it { is_expected.to validate(:post, '/user/financial_transactions', 404, api_auth(create_params)) }
end
context 'without note' do
let(:create_params) { { '_data' => { financial_transaction: { amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id } } } }
it { is_expected.to validate(:post, '/user/financial_transactions', 422, api_auth(create_params)) }
end
context 'without enough balance' do
before { FoodsoftConfig[:minimum_balance] = 1000 }
it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) }
end
end
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions')
it_handles_invalid_token_and_scope(:post, '/user/financial_transactions', -> { api_auth(create_params) })
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions/{id}', -> { api_auth('id' => ft_2.id) })
end
end
context 'user/group_order_articles' do
let(:api_scopes) { ['group_orders:user'] }
let(:order) { create(:order, article_count: 2) }
let(:user_2) { create :user, :ordergroup }
let(:group_order_2) { create(:group_order, order: order, ordergroup: user_2.ordergroup) }
let!(:goa_2) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order_2 }
before { group_order_2.update_price!; user_2.ordergroup.update_stats! }
context 'without ordergroup' do
it { is_expected.to validate(:get, '/user/group_order_articles', 403, api_auth) }
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 403, api_auth({ 'id' => goa_2.id })) }
end
context 'with ordergroup' do
let(:user) { create :user, :ordergroup }
let(:update_params) { { 'id' => goa.id, '_data' => { group_order_article: { quantity: goa.quantity + 1, tolerance: 0 } } } }
let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[1].id, quantity: 1 } } } }
let(:group_order) { create(:group_order, order: order, ordergroup: user.ordergroup) }
let!(:goa) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order }
before { group_order.update_price!; user.ordergroup.update_stats! }
it { is_expected.to validate(:get, '/user/group_order_articles', 200, api_auth) }
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) }
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({ 'id' => goa_2.id })) }
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({ 'id' => GroupOrderArticle.last.id + 1 })) }
it { is_expected.to validate(:post, '/user/group_order_articles', 200, api_auth(create_params)) }
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 200, api_auth(update_params)) }
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) }
context 'with an existing group_order_article' do
let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[0].id, quantity: 1 } } } }
it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) }
end
context 'with invalid parameter values' do
let(:create_params) { { '_data' => { group_order_article: { order_article_id: order.order_articles[0].id, quantity: -1 } } } }
let(:update_params) { { 'id' => goa.id, '_data' => { group_order_article: { quantity: -1, tolerance: 0 } } } }
it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) }
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 422, api_auth(update_params)) }
end
context 'with a closed order' do
let(:order) { create(:order, article_count: 2, state: :finished) }
it { is_expected.to validate(:post, '/user/group_order_articles', 404, api_auth(create_params)) }
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 404, api_auth(update_params)) }
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 404, api_auth({ 'id' => goa.id })) }
end
context 'without enough balance' do
before { FoodsoftConfig[:minimum_balance] = 1000 }
it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) }
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) }
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) }
end
context 'without enough apple points' do
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) }
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) }
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({ 'id' => goa.id })) }
end
it_handles_invalid_token_and_scope(:get, '/user/group_order_articles')
it_handles_invalid_token_and_scope(:post, '/user/group_order_articles', -> { api_auth(create_params) })
it_handles_invalid_token_and_scope(:get, '/user/group_order_articles/{id}', -> { api_auth({ 'id' => goa.id }) })
it_handles_invalid_token_and_scope(:patch, '/user/group_order_articles/{id}', -> { api_auth(update_params) })
it_handles_invalid_token_and_scope(:delete, '/user/group_order_articles/{id}', -> { api_auth({ 'id' => goa.id }) })
end
end
context 'config' do
let(:api_scopes) { ['config:user'] }
it { is_expected.to validate(:get, '/config', 200, api_auth) }
it { is_expected.to validate(:get, '/config', 401) }
it_handles_invalid_token_and_scope(:get, '/config')
end
context 'navigation' do
it { is_expected.to validate(:get, '/navigation', 200, api_auth) }
it { is_expected.to validate(:get, '/navigation', 401) }
it_handles_invalid_token(:get, '/navigation')
end
context 'financial_transactions' do
let(:api_scopes) { ['finance:read'] }
let(:user) { create(:user, :role_finance) }
let(:other_user) { create :user, :ordergroup }
let!(:ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup }
let!(:ft_2) { create :financial_transaction, ordergroup: other_user.ordergroup }
it { is_expected.to validate(:get, '/financial_transactions', 200, api_auth) }
it { is_expected.to validate(:get, '/financial_transactions/{id}', 200, api_auth({ 'id' => ft_2.id })) }
it { is_expected.to validate(:get, '/financial_transactions/{id}', 404, api_auth({ 'id' => FinancialTransaction.last.id + 1 })) }
context 'without role_finance' do
let(:user) { create(:user) }
it { is_expected.to validate(:get, '/financial_transactions', 403, api_auth) }
it { is_expected.to validate(:get, '/financial_transactions/{id}', 403, api_auth({ 'id' => ft_2.id })) }
end
it_handles_invalid_token_and_scope(:get, '/financial_transactions')
it_handles_invalid_token_and_scope(:get, '/financial_transactions/{id}', -> { api_auth({ 'id' => ft_2.id }) })
end
context 'financial_transaction_classes' do
let!(:cla_1) { create :financial_transaction_class }
let!(:cla_2) { create :financial_transaction_class }
it { is_expected.to validate(:get, '/financial_transaction_classes', 200, api_auth) }
it { is_expected.to validate(:get, '/financial_transaction_classes/{id}', 200, api_auth({ 'id' => cla_2.id })) }
it { is_expected.to validate(:get, '/financial_transaction_classes/{id}', 404, api_auth({ 'id' => cla_2.id + 1 })) }
it_handles_invalid_token(:get, '/financial_transaction_classes')
it_handles_invalid_token(:get, '/financial_transaction_classes/{id}', -> { api_auth({ 'id' => cla_1.id }) })
end
context 'financial_transaction_types' do
let!(:tpy_1) { create :financial_transaction_type }
let!(:tpy_2) { create :financial_transaction_type }
it { is_expected.to validate(:get, '/financial_transaction_types', 200, api_auth) }
it { is_expected.to validate(:get, '/financial_transaction_types/{id}', 200, api_auth({ 'id' => tpy_2.id })) }
it { is_expected.to validate(:get, '/financial_transaction_types/{id}', 404, api_auth({ 'id' => tpy_2.id + 1 })) }
it_handles_invalid_token(:get, '/financial_transaction_types')
it_handles_invalid_token(:get, '/financial_transaction_types/{id}', -> { api_auth({ 'id' => tpy_1.id }) })
end
context 'orders' do
let(:api_scopes) { ['orders:read'] }
let!(:order) { create :order }
it { is_expected.to validate(:get, '/orders', 200, api_auth) }
it { is_expected.to validate(:get, '/orders/{id}', 200, api_auth({ 'id' => order.id })) }
it { is_expected.to validate(:get, '/orders/{id}', 404, api_auth({ 'id' => Order.last.id + 1 })) }
it_handles_invalid_token_and_scope(:get, '/orders')
it_handles_invalid_token_and_scope(:get, '/orders/{id}', -> { api_auth({ 'id' => order.id }) })
end
context 'order_articles' do
let(:api_scopes) { ['orders:read'] }
let!(:order_article) { create(:order, article_count: 1).order_articles.first }
let!(:stock_article) { create(:stock_article) }
let!(:stock_order_article) { create(:stock_order, article_ids: [stock_article.id]).order_articles.first }
it { is_expected.to validate(:get, '/order_articles', 200, api_auth) }
it { is_expected.to validate(:get, '/order_articles/{id}', 200, api_auth({ 'id' => order_article.id })) }
it { is_expected.to validate(:get, '/order_articles/{id}', 200, api_auth({ 'id' => stock_order_article.id })) }
it { is_expected.to validate(:get, '/order_articles/{id}', 404, api_auth({ 'id' => Article.last.id + 1 })) }
it_handles_invalid_token_and_scope(:get, '/order_articles')
it_handles_invalid_token_and_scope(:get, '/order_articles/{id}', -> { api_auth({ 'id' => order_article.id }) })
end
context 'article_categories' do
let!(:cat_1) { create :article_category }
let!(:cat_2) { create :article_category }
it { is_expected.to validate(:get, '/article_categories', 200, api_auth) }
it { is_expected.to validate(:get, '/article_categories/{id}', 200, api_auth({ 'id' => cat_2.id })) }
it { is_expected.to validate(:get, '/article_categories/{id}', 404, api_auth({ 'id' => cat_2.id + 1 })) }
it_handles_invalid_token(:get, '/article_categories')
it_handles_invalid_token(:get, '/article_categories/{id}', -> { api_auth({ 'id' => cat_1.id }) })
end
end
# needs to be last context so it is always run at the end
context 'and finally' do
it 'tests all documented routes' do
is_expected.to validate_all_paths
end
end
end

View file

@ -1,109 +0,0 @@
require 'spec_helper'
# Most routes are tested in the swagger_spec, this tests endpoints that change data.
describe Api::V1::User::FinancialTransactionsController, type: :controller do
include ApiOAuth
let(:user) { create(:user, :ordergroup) }
let(:api_scopes) { ['finance:user'] }
let(:ftc1) { create :financial_transaction_class }
let(:ftc2) { create :financial_transaction_class }
let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 }
let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 }
let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 }
let(:amount) { rand(-100..100) }
let(:note) { Faker::Lorem.sentence }
let(:json_ft) { json_response['financial_transaction'] }
shared_examples "financial_transactions endpoint success" do
before { request }
it "returns status 200" do
expect(response).to have_http_status :ok
end
end
shared_examples "financial_transactions create/update success" do
include_examples "financial_transactions endpoint success"
it "returns the financial_transaction" do
expect(json_ft['id']).to be_present
expect(json_ft['financial_transaction_type_id']).to eq ftt1.id
expect(json_ft['financial_transaction_type_name']).to eq ftt1.name
expect(json_ft['amount']).to eq amount
expect(json_ft['note']).to eq note
expect(json_ft['user_id']).to eq user.id
end
it "updates the financial_transaction" do
resulting_ft = FinancialTransaction.where(id: json_ft['id']).first
expect(resulting_ft).to be_present
expect(resulting_ft.financial_transaction_type).to eq ftt1
expect(resulting_ft.amount).to eq amount
expect(resulting_ft.note).to eq note
expect(resulting_ft.user).to eq user
end
end
shared_examples "financial_transactions endpoint failure" do |status|
it "returns status #{status}" do
request
expect(response.status).to eq status
end
it "does not change the ordergroup" do
expect { request }.to_not change {
user.ordergroup.attributes
}
end
it "does not change the financial_transactions of ordergroup" do
expect { request }.to_not change {
user.ordergroup.financial_transactions.count
}
end
end
describe "POST :create" do
let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id, note: note } }
let(:request) { post :create, params: { financial_transaction: ft_params, foodcoop: 'f' } }
context 'without using self service' do
include_examples "financial_transactions endpoint failure", 403
end
context 'with using self service' do
before { FoodsoftConfig[:use_self_service] = true }
context "with no existing financial transaction" do
include_examples "financial_transactions create/update success"
end
context "with existing financial transaction" do
before { user.ordergroup.add_financial_transaction! 5000, 'for ordering', user, ftt3 }
include_examples "financial_transactions create/update success"
end
context "with invalid financial transaction type" do
let(:ft_params) { { amount: amount, financial_transaction_type_id: -1, note: note } }
include_examples "financial_transactions endpoint failure", 404
end
context "without note" do
let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id } }
include_examples "financial_transactions endpoint failure", 422
end
context 'without enough balance' do
before { FoodsoftConfig[:minimum_balance] = 1000 }
include_examples "financial_transactions endpoint failure", 403
end
end
end
end

View file

@ -1,220 +0,0 @@
require 'spec_helper'
# Most routes are tested in the swagger_spec, this tests endpoints that change data.
describe Api::V1::User::GroupOrderArticlesController, type: :controller do
include ApiOAuth
let(:user) { create(:user, :ordergroup) }
let(:json_goa) { json_response['group_order_article'] }
let(:json_oa) { json_response['order_article'] }
let(:api_scopes) { ['group_orders:user'] }
let(:order) { create(:order, article_count: 1) }
let(:oa_1) { order.order_articles.first }
let(:other_quantity) { rand(1..10) }
let(:other_tolerance) { rand(1..10) }
let(:user_other) { create(:user, :ordergroup) }
let!(:go_other) { create(:group_order, order: order, ordergroup: user_other.ordergroup) }
let!(:goa_other) { create(:group_order_article, group_order: go_other, order_article: oa_1, quantity: other_quantity, tolerance: other_tolerance) }
before { go_other.update_price!; user_other.ordergroup.update_stats! }
shared_examples "group_order_articles endpoint success" do
before { request }
it "returns status 200" do
expect(response).to have_http_status :ok
end
it "returns the order_article" do
expect(json_oa['id']).to eq oa_1.id
expect(json_oa['quantity']).to eq new_quantity + other_quantity
expect(json_oa['tolerance']).to eq new_tolerance + other_tolerance
end
it "updates the group_order" do
go = nil
expect {
request
go = user.ordergroup.group_orders.where(order: order).last
}.to change { go&.updated_by }.to(user)
.and change { go&.price }
end
end
shared_examples "group_order_articles create/update success" do
include_examples "group_order_articles endpoint success"
it "returns the group_order_article" do
expect(json_goa['id']).to be_present
expect(json_goa['order_article_id']).to eq oa_1.id
expect(json_goa['quantity']).to eq new_quantity
expect(json_goa['tolerance']).to eq new_tolerance
end
it "updates the group_order_article" do
resulting_goa = GroupOrderArticle.where(id: json_goa['id']).first
expect(resulting_goa).to be_present
expect(resulting_goa.quantity).to eq new_quantity
expect(resulting_goa.tolerance).to eq new_tolerance
end
end
shared_examples "group_order_articles endpoint failure" do |status|
it "returns status #{status}" do
request
expect(response.status).to eq status
end
it "does not change the group_order" do
expect { request }.to_not change {
go = user.ordergroup.group_orders.where(order: order).last
go&.attributes
}
end
it "does not change the group_order_article" do
expect { request }.to_not change {
goa = GroupOrderArticle.joins(:group_order)
.where(order_article_id: oa_1.id, group_orders: { ordergroup: user.ordergroup }).last
goa&.attributes
}
end
end
describe "POST :create" do
let(:new_quantity) { rand(1..10) }
let(:new_tolerance) { rand(1..10) }
let(:goa_params) { { order_article_id: oa_1.id, quantity: new_quantity, tolerance: new_tolerance } }
let(:request) { post :create, params: { group_order_article: goa_params, foodcoop: 'f' } }
context "with no existing group_order" do
include_examples "group_order_articles create/update success"
end
context "with an existing group_order" do
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
include_examples "group_order_articles create/update success"
end
context "with an existing group_order_article" do
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 0, tolerance: 1) }
before { go.update_price!; user.ordergroup.update_stats! }
include_examples "group_order_articles endpoint failure", 422
end
context "with invalid parameter values" do
let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance } }
include_examples "group_order_articles endpoint failure", 422
end
context 'with a closed order' do
let(:order) { create(:order, article_count: 1, state: :finished) }
include_examples "group_order_articles endpoint failure", 404
end
context 'without enough balance' do
before { FoodsoftConfig[:minimum_balance] = 1000 }
include_examples "group_order_articles endpoint failure", 403
end
context 'without enough apple points' do
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
include_examples "group_order_articles endpoint failure", 403
end
end
describe "PATCH :update" do
let(:new_quantity) { rand(2..10) }
let(:goa_params) { { quantity: new_quantity, tolerance: new_tolerance } }
let(:request) { patch :update, params: { id: goa.id, group_order_article: goa_params, foodcoop: 'f' } }
let(:new_tolerance) { rand(2..10) }
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 1, tolerance: 0) }
before { go.update_price!; user.ordergroup.update_stats! }
context "happy flow" do
include_examples "group_order_articles create/update success"
end
context "with invalid parameter values" do
let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance } }
include_examples "group_order_articles endpoint failure", 422
end
context 'with a closed order' do
let(:order) { create(:order, article_count: 1, state: :finished) }
include_examples "group_order_articles endpoint failure", 404
end
context 'without enough balance' do
before { FoodsoftConfig[:minimum_balance] = 1000 }
include_examples "group_order_articles endpoint failure", 403
end
context 'without enough apple points' do
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
include_examples "group_order_articles endpoint failure", 403
end
end
describe "DELETE :destroy" do
let(:new_quantity) { 0 }
let(:request) { delete :destroy, params: { id: goa.id, foodcoop: 'f' } }
let(:new_tolerance) { 0 }
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1) }
before { go.update_price!; user.ordergroup.update_stats! }
shared_examples "group_order_articles destroy success" do
include_examples "group_order_articles endpoint success"
it "does not return the group_order_article" do
expect(json_goa).to be_nil
end
it "deletes the group_order_article" do
expect(GroupOrderArticle.where(id: goa.id)).to be_empty
end
end
context "happy flow" do
include_examples "group_order_articles destroy success"
end
context 'with a closed order' do
let(:order) { create(:order, article_count: 1, state: :finished) }
include_examples "group_order_articles endpoint failure", 404
end
context 'without enough balance' do
before { FoodsoftConfig[:minimum_balance] = 1000 }
include_examples "group_order_articles destroy success"
end
context 'without enough apple points' do
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
include_examples "group_order_articles destroy success"
end
end
end

View file

@ -1,55 +0,0 @@
require 'spec_helper'
describe Api::V1::User::OrdergroupController, type: :controller do
include ApiOAuth
let(:user) { create :user, :ordergroup }
let(:api_scopes) { ['finance:user'] }
let(:ftc1) { create :financial_transaction_class }
let(:ftc2) { create :financial_transaction_class }
let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 }
let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 }
let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 }
describe "GET :financial_overview" do
let(:order) { create(:order, article_count: 1) }
let(:json_financial_overview) { json_response['financial_overview'] }
let(:oa_1) { order.order_articles.first }
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 1, tolerance: 0) }
before { go.update_price!; user.ordergroup.update_stats! }
before do
og = user.ordergroup
og.add_financial_transaction!(-1, '-1', user, ftt1)
og.add_financial_transaction!(2, '2', user, ftt1)
og.add_financial_transaction!(3, '3', user, ftt1)
og.add_financial_transaction!(-10, '-10', user, ftt2)
og.add_financial_transaction!(20, '20', user, ftt2)
og.add_financial_transaction!(30, '30', user, ftt2)
og.add_financial_transaction!(-100, '-100', user, ftt3)
og.add_financial_transaction!(200, '200', user, ftt3)
og.add_financial_transaction!(300, '300', user, ftt3)
end
it "returns correct values" do
get :financial_overview, params: { foodcoop: 'f' }
expect(json_financial_overview['account_balance']).to eq 444
expect(json_financial_overview['available_funds']).to eq 444 - go.price
ftcs = Hash[json_financial_overview['financial_transaction_class_sums'].map { |x| [x['id'], x] }]
ftcs1 = ftcs[ftc1.id]
expect(ftcs1['name']).to eq ftc1.name
expect(ftcs1['amount']).to eq 4
ftcs2 = ftcs[ftc2.id]
expect(ftcs2['name']).to eq ftc2.name
expect(ftcs2['amount']).to eq 440
end
end
end

View file

@ -6,6 +6,7 @@
default: &defaults default: &defaults
multi_coop_install: false multi_coop_install: false
use_self_service: true
default_scope: 'f' default_scope: 'f'
name: FC Minimal name: FC Minimal

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
require 'spec_helper'
describe ApplicationController, type: :controller do
describe 'current' do
it 'returns current ApplicationController' do
described_class.new.send(:store_controller)
expect(described_class.current).to be_instance_of described_class
end
end
end

View file

@ -0,0 +1,348 @@
# frozen_string_literal: true
require 'spec_helper'
describe ArticlesController, type: :controller do
let(:user) { create :user, :role_article_meta }
let(:article_category_a) { create :article_category, name: "AAAA" }
let(:article_category_b) { create :article_category, name: "BBBB" }
let(:article_category_c) { create :article_category, name: "CCCC" }
let(:supplier) { create :supplier}
let(:article_a) { create :article, name: 'AAAA', note: "ZZZZ", unit: '750 g', article_category: article_category_b, availability: false, supplier_id: supplier.id }
let(:article_b) { create :article, name: 'BBBB', note: "XXXX", unit: '500 g', article_category: article_category_a, availability: true, supplier_id: supplier.id }
let(:article_c) { create :article, name: 'CCCC', note: "YYYY", unit: '250 g', article_category: article_category_c, availability: true, supplier_id: supplier.id }
let(:article_no_supplier) { create :article, name: 'no_supplier', note: "no_supplier", unit: '100 g', article_category: article_category_b, availability: true }
let(:order) { create :order }
let(:order2) { create :order }
def get_with_supplier(action, params: {}, xhr: false, format: nil)
params['supplier_id'] = supplier.id
get_with_defaults(action, params: params, xhr: xhr, format: format)
end
def post_with_supplier(action, params: {}, xhr: false, format: nil)
params['supplier_id'] = supplier.id
post_with_defaults(action, params: params, xhr: xhr, format: format)
end
before { login user }
describe 'GET index' do
before do
supplier
article_a
article_b
article_c
supplier.reload
end
it 'assigns sorting on articles' do
sortings = [
['name', [article_a, article_b, article_c]],
['name_reverse', [article_c, article_b, article_a]],
['note', [article_b, article_c, article_a]],
['note_reverse', [article_a, article_c, article_b]],
['unit', [article_c, article_b, article_a]],
['unit_reverse', [article_a, article_b, article_c]],
['article_category', [article_b, article_a, article_c]],
['article_category_reverse', [article_c, article_a, article_b]],
['availability', [article_a, article_b, article_c]],
['availability_reverse', [article_b, article_c, article_a]]
]
sortings.each do |sorting|
get_with_supplier :index, params: { sort: sorting[0] }
expect(response).to have_http_status(:success)
expect(assigns(:articles).to_a).to eq(sorting[1])
end
end
it 'triggers an article csv' do
get_with_supplier :index, format: :csv
expect(response.header['Content-Type']).to include('text/csv')
expect(response.body).to include(article_a.unit, article_b.unit)
end
end
describe 'new' do
it 'renders form for a new article' do
get_with_supplier :new, xhr: true
expect(response).to have_http_status(:success)
end
end
describe 'copy' do
it 'renders form with copy of an article' do
get_with_supplier :copy, params: { article_id: article_a.id }, xhr: true
expect(assigns(:article).attributes).to eq(article_a.dup.attributes)
expect(response).to have_http_status(:success)
end
end
describe '#create' do
it 'creates a new article' do
valid_attributes = article_a.attributes.except('id')
valid_attributes['name'] = 'ABAB'
get_with_supplier :create, params: { article: valid_attributes }, xhr: true
expect(response).to have_http_status(:success)
end
it 'fails to create a new article and renders #new' do
get_with_supplier :create, params: { article: { id: nil } }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('articles/new')
end
end
describe 'edit' do
it 'opens form to edit article attributes' do
get_with_supplier :edit, params: { id: article_a.id }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('articles/new')
end
end
describe '#edit all' do
it 'renders edit_all' do
get_with_supplier :edit_all, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('articles/edit_all')
end
end
describe '#update' do
it 'updates article attributes' do
get_with_supplier :update, params: { id: article_a.id, article: { unit: '300 g' } }, xhr: true
expect(assigns(:article).unit).to eq('300 g')
expect(response).to have_http_status(:success)
end
it 'updates article with empty name attribute' do
get_with_supplier :update, params: { id: article_a.id, article: { name: nil } }, xhr: true
expect(response).to render_template('articles/new')
end
end
describe '#update_all' do
it 'updates all articles' do
get_with_supplier :update_all, params: { articles: { "#{article_a.id}": attributes_for(:article), "#{article_b.id}": attributes_for(:article) } }
expect(response).to have_http_status(:redirect)
end
it 'fails on updating all articles' do
get_with_supplier :update_all, params: { articles: { "#{article_a.id}": attributes_for(:article, name: 'ab') } }
expect(response).to have_http_status(:success)
expect(response).to render_template('articles/edit_all')
end
end
describe '#update_selected' do
let(:order_article) { create :order_article, order: order, article: article_no_supplier }
before do
order_article
end
it 'updates selected articles' do
get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id] }
expect(response).to have_http_status(:redirect)
end
it 'destroys selected articles' do
get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id], selected_action: 'destroy' }
article_a.reload
article_b.reload
expect(article_a).to be_deleted
expect(article_b).to be_deleted
expect(response).to have_http_status(:redirect)
end
it 'sets availability false on selected articles' do
get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id], selected_action: 'setNotAvailable' }
article_a.reload
article_b.reload
expect(article_a).not_to be_availability
expect(article_b).not_to be_availability
expect(response).to have_http_status(:redirect)
end
it 'sets availability true on selected articles' do
get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_b.id], selected_action: 'setAvailable' }
article_a.reload
article_b.reload
expect(article_a).to be_availability
expect(article_b).to be_availability
expect(response).to have_http_status(:redirect)
end
it 'fails deletion if one article is in open order' do
get_with_supplier :update_selected, params: { selected_articles: [article_a.id, article_no_supplier.id], selected_action: 'destroy' }
article_a.reload
article_no_supplier.reload
expect(article_a).not_to be_deleted
expect(article_no_supplier).not_to be_deleted
expect(response).to have_http_status(:redirect)
end
end
describe '#parse_upload' do
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/upload_test.csv'), original_filename: 'upload_test.csv') }
it 'updates particles from spreadsheet' do
post_with_supplier :parse_upload, params: { articles: { file: file, outlist_absent: '1', convert_units: '1' } }
expect(response).to have_http_status(:success)
end
it 'missing file not updates particles from spreadsheet' do
post_with_supplier :parse_upload, params: { articles: { file: nil, outlist_absent: '1', convert_units: '1' } }
expect(response).to have_http_status(:redirect)
expect(flash[:alert]).to match(I18n.t('errors.general_msg', msg: "undefined method `original_filename' for \"\":String").to_s)
end
end
describe '#sync' do
# TODO: double render error in controller
it 'throws double render error' do
expect do
post :sync, params: { foodcoop: FoodsoftConfig[:default_scope], supplier_id: supplier.id }
end.to raise_error(AbstractController::DoubleRenderError)
end
xit 'updates particles from spreadsheet' do
post :sync, params: { foodcoop: FoodsoftConfig[:default_scope], supplier_id: supplier.id, articles: { '#{article_a.id}': attributes_for(:article), '#{article_b.id}': attributes_for(:article) } }
expect(response).to have_http_status(:redirect)
end
end
describe '#destroy' do
let(:order_article) { create :order_article, order: order, article: article_no_supplier }
before do
order_article
end
it 'does not delete article if order open' do
get_with_supplier :destroy, params: { id: article_no_supplier.id }, xhr: true
expect(assigns(:article)).not_to be_deleted
expect(response).to have_http_status(:success)
expect(response).to render_template('articles/destroy')
end
it 'deletes article if order closed' do
get_with_supplier :destroy, params: { id: article_b.id }, xhr: true
expect(assigns(:article)).to be_deleted
expect(response).to have_http_status(:success)
expect(response).to render_template('articles/destroy')
end
end
describe '#update_synchronized' do
let(:order_article) { create :order_article, order: order, article: article_no_supplier }
before do
order_article
article_a
article_b
article_no_supplier
end
it 'deletes articles' do
# TODO: double render error in controller
get_with_supplier :update_synchronized, params: { outlisted_articles: { article_a.id => article_a, article_b.id => article_b } }
article_a.reload
article_b.reload
expect(article_a).to be_deleted
expect(article_b).to be_deleted
expect(response).to have_http_status(:redirect)
end
it 'updates articles' do
get_with_supplier :update_synchronized, params: { articles: { article_a.id => { name: 'NewNameA' }, article_b.id => { name: 'NewNameB' } } }
expect(assigns(:updated_articles).first.name).to eq 'NewNameA'
expect(response).to have_http_status(:redirect)
end
it 'does not update articles if article with same name exists' do
get_with_supplier :update_synchronized, params: { articles: { article_a.id => { unit: '2000 g' }, article_b.id => { name: 'AAAA' } } }
error_array = [assigns(:updated_articles).first.errors.first, assigns(:updated_articles).last.errors.first]
expect(error_array).to include(ActiveModel::Error)
expect(response).to have_http_status(:success)
end
it 'does update articles if article with same name was deleted before' do
get_with_supplier :update_synchronized, params: {
outlisted_articles: { article_a.id => article_a },
articles: {
article_a.id => { name: 'NewName' },
article_b.id => { name: 'AAAA' }
}
}
error_array = [assigns(:updated_articles).first.errors.first, assigns(:updated_articles).last.errors.first]
expect(error_array).not_to be_any
expect(response).to have_http_status(:redirect)
end
it 'does not delete articles in open order' do
get_with_supplier :update_synchronized, params: { outlisted_articles: { article_no_supplier.id => article_no_supplier } }
article_no_supplier.reload
expect(article_no_supplier).not_to be_deleted
expect(response).to have_http_status(:success)
end
it 'assigns updated article_pairs on error' do
get_with_supplier :update_synchronized, params: {
articles: { article_a.id => { name: 'EEEE' } },
outlisted_articles: { article_no_supplier.id => article_no_supplier }
}
expect(assigns(:updated_article_pairs).first).to eq([article_a, { name: 'EEEE' }])
article_no_supplier.reload
expect(article_no_supplier).not_to be_deleted
expect(response).to have_http_status(:success)
end
it 'updates articles in open order' do
get_with_supplier :update_synchronized, params: { articles: { article_no_supplier.id => { name: 'EEEE' } } }
article_no_supplier.reload
expect(article_no_supplier.name).to eq 'EEEE'
expect(response).to have_http_status(:redirect)
end
end
describe '#shared' do
let(:shared_supplier) { create :shared_supplier, shared_articles: [shared_article] }
let(:shared_article) { create :shared_article, name: 'shared' }
let(:article_s) { create :article, name: 'SSSS', note: 'AAAA', unit: '250 g', article_category: article_category_a, availability: false }
let(:supplier_with_shared) { create :supplier, shared_supplier: shared_supplier }
it 'renders view with articles' do
get_with_defaults :shared, params: { supplier_id: supplier_with_shared.id, name_cont_all_joined: 'shared' }, xhr: true
expect(assigns(:supplier).shared_supplier.shared_articles).to be_any
expect(assigns(:articles)).to be_any
expect(response).to have_http_status(:success)
end
end
describe '#import' do
let(:shared_supplier) { create :shared_supplier, shared_articles: [shared_article] }
let(:shared_article) { create :shared_article, name: 'shared' }
before do
shared_article
article_category_a
end
it 'fills form with article details' do
get_with_supplier :import, params: { article_category_id: article_category_b.id, direct: 'true', shared_article_id: shared_article.id }, xhr: true
expect(assigns(:article)).not_to be_nil
expect(response).to have_http_status(:success)
expect(response).to render_template(:create)
end
it 'does redirect to :new if param :direct not set' do
get_with_supplier :import, params: { article_category_id: article_category_b.id, shared_article_id: shared_article.id }, xhr: true
expect(assigns(:article)).not_to be_nil
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
end

View file

@ -0,0 +1,212 @@
# frozen_string_literal: true
require 'spec_helper'
class DummyAuthController < ApplicationController; end
describe 'Auth concern', type: :controller do
controller DummyAuthController do
# Defining a dummy action for an anynomous controller which inherits from the described class.
def authenticate_blank
authenticate
end
def authenticate_unknown_group
authenticate('nooby')
end
def authenticate_pickups
authenticate('pickups')
head :ok unless performed?
end
def authenticate_finance_or_orders
authenticate('finance_or_orders')
head :ok unless performed?
end
def try_authenticate_membership_or_admin
authenticate_membership_or_admin
end
def try_authenticate_or_token
authenticate_or_token('xyz')
head :ok unless performed?
end
def call_deny_access
deny_access
end
def call_current_user
current_user
end
def call_login_and_redirect_to_return_to
user = User.find(params[:user_id])
login_and_redirect_to_return_to(user)
end
def call_login
user = User.find(params[:user_id])
login(user)
end
end
# unit testing protected/private methods
describe 'protected/private methods' do
let(:user) { create :user }
let(:wrong_user) { create :user }
describe '#current_user' do
before do
login user
routes.draw { get 'call_current_user' => 'dummy_auth#call_current_user' }
end
describe 'with valid session' do
it 'returns current_user' do
get_with_defaults :call_current_user, params: { user_id: user.id }, format: JSON
expect(assigns(:current_user)).to eq user
end
end
describe 'with invalid session' do
it 'not returns current_user' do
session[:user_id] = nil
get_with_defaults :call_current_user, params: { user_id: nil }, format: JSON
expect(assigns(:current_user)).to be_nil
end
end
end
describe '#deny_access' do
it 'redirects to root_url' do
login user
routes.draw { get 'deny_access' => 'dummy_auth#call_deny_access' }
get_with_defaults :call_deny_access
expect(response).to redirect_to(root_url)
end
end
describe '#login' do
before do
routes.draw { get 'call_login' => 'dummy_auth#call_login' }
end
it 'sets user in session' do
login wrong_user
get_with_defaults :call_login, params: { user_id: user.id }, format: JSON
expect(session[:user_id]).to eq user.id
expect(session[:scope]).to eq FoodsoftConfig.scope
expect(session[:locale]).to eq user.locale
end
end
describe '#login_and_redirect_to_return_to' do
it 'redirects to already set target' do
login user
session[:return_to] = my_profile_url
routes.draw { get 'call_login_and_redirect_to_return_to' => 'dummy_auth#call_login_and_redirect_to_return_to' }
get_with_defaults :call_login_and_redirect_to_return_to, params: { user_id: user.id }
expect(session[:return_to]).to be_nil
end
end
end
describe 'authenticate' do
describe 'not logged in' do
it 'does not authenticate' do
routes.draw { get 'authenticate_blank' => 'dummy_auth#authenticate_blank' }
get_with_defaults :authenticate_blank
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(login_path)
expect(flash[:alert]).to match(I18n.t('application.controller.error_authn'))
end
end
describe 'logged in' do
let(:user) { create :user }
let(:pickups_user) { create :user, :role_pickups }
let(:finance_user) { create :user, :role_finance }
let(:orders_user) { create :user, :role_orders }
it 'does not authenticate with unknown group' do
login user
routes.draw { get 'authenticate_unknown_group' => 'dummy_auth#authenticate_unknown_group' }
get_with_defaults :authenticate_unknown_group
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(root_path)
expect(flash[:alert]).to match(I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(I18n.t('application.controller.error_denied_sign_in'), login_path)))
end
it 'does not authenticate with pickups group' do
login pickups_user
routes.draw { get 'authenticate_pickups' => 'dummy_auth#authenticate_pickups' }
get_with_defaults :authenticate_pickups
expect(response).to have_http_status(:success)
end
it 'does not authenticate with finance group' do
login finance_user
routes.draw { get 'authenticate_finance_or_orders' => 'dummy_auth#authenticate_finance_or_orders' }
get_with_defaults :authenticate_finance_or_orders
expect(response).to have_http_status(:success)
end
it 'does not authenticate with orders group' do
login orders_user
routes.draw { get 'authenticate_finance_or_orders' => 'dummy_auth#authenticate_finance_or_orders' }
get_with_defaults :authenticate_finance_or_orders
expect(response).to have_http_status(:success)
end
end
end
describe 'authenticate_membership_or_admin' do
describe 'logged in' do
let(:pickups_user) { create :user, :role_pickups }
let(:workgroup) { create :workgroup }
it 'redirects with not permitted group' do
group_id = workgroup.id
login pickups_user
routes.draw { get 'try_authenticate_membership_or_admin' => 'dummy_auth#try_authenticate_membership_or_admin' }
get_with_defaults :try_authenticate_membership_or_admin, params: { id: group_id }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(root_path)
expect(flash[:alert]).to match(I18n.t('application.controller.error_members_only'))
end
end
end
describe 'authenticate_or_token' do
describe 'logged in' do
let(:token_verifier) { TokenVerifier.new('xyz') }
let(:token_msg) { token_verifier.generate }
let(:user) { create :user }
before { login user }
it 'authenticates token' do
routes.draw { get 'try_authenticate_or_token' => 'dummy_auth#try_authenticate_or_token' }
get_with_defaults :try_authenticate_or_token, params: { token: token_msg }
expect(response).not_to have_http_status(:redirect)
end
it 'redirects on faulty token' do
routes.draw { get 'try_authenticate_or_token' => 'dummy_auth#try_authenticate_or_token' }
get_with_defaults :try_authenticate_or_token, params: { token: 'abc' }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(root_path)
expect(flash[:alert]).to match(I18n.t('application.controller.error_token'))
end
it 'authenticates current user on empty token' do
routes.draw { get 'try_authenticate_or_token' => 'dummy_auth#try_authenticate_or_token' }
get_with_defaults :try_authenticate_or_token
expect(response).to have_http_status(:success)
end
end
end
end

View file

@ -0,0 +1,211 @@
# frozen_string_literal: true
require 'spec_helper'
describe Finance::BalancingController, type: :controller do
let(:user) { create :user, :role_finance, :role_orders, groups: [create(:ordergroup)] }
before { login user }
describe 'GET index' do
let(:order) { create :order }
it 'renders index page' do
get_with_defaults :index
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/index')
end
end
describe 'new balancing' do
let(:supplier) { create :supplier }
let(:article1) { create :article, name: 'AAAA', supplier: supplier, unit_quantity: 1 }
let(:article2) { create :article, name: 'AAAB', supplier: supplier, unit_quantity: 1 }
let(:order) { create :order, supplier: supplier, article_ids: [article1.id, article2.id] }
let(:go1) { create :group_order, order: order }
let(:go2) { create :group_order, order: order }
let(:oa1) { order.order_articles.find_by_article_id(article1.id) }
let(:oa2) { order.order_articles.find_by_article_id(article2.id) }
let(:oa3) { order2.order_articles.find_by_article_id(article2.id) }
let(:goa1) { create :group_order_article, group_order: go1, order_article: oa1 }
let(:goa2) { create :group_order_article, group_order: go1, order_article: oa2 }
before do
goa1.update_quantities(3, 0)
goa2.update_quantities(1, 0)
oa1.update_results!
oa2.update_results!
end
it 'renders new order page' do
get_with_defaults :new, params: { order_id: order.id }
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/new')
end
it 'assigns sorting on articles' do
sortings = [
['name', [oa1, oa2]],
['name_reverse', [oa2, oa1]],
['order_number', [oa1, oa2]],
['order_number_reverse', [oa1, oa2]] # just one order
]
sortings.each do |sorting|
get_with_defaults :new, params: { order_id: order.id, sort: sorting[0] }
expect(response).to have_http_status(:success)
expect(assigns(:articles).to_a).to eq(sorting[1])
end
end
end
describe 'update summary' do
let(:order) { create(:order) }
it 'shows the summary view' do
get_with_defaults :update_summary, params: { id: order.id }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/update_summary')
end
end
describe 'new_on_order' do
let(:order) { create(:order) }
let(:order_article) { order.order_articles.first }
it 'calls article update' do
get_with_defaults :new_on_order_article_update, params: { id: order.id, order_article_id: order_article.id }, xhr: true
expect(response).not_to render_template(layout: 'application')
expect(response).to render_template('finance/balancing/new_on_order_article_update')
end
it 'calls article create' do
get_with_defaults :new_on_order_article_create, params: { id: order.id, order_article_id: order_article.id }, xhr: true
expect(response).not_to render_template(layout: 'application')
expect(response).to render_template('finance/balancing/new_on_order_article_create')
end
end
describe 'edit_note' do
let(:order) { create(:order) }
it 'updates order note' do
get_with_defaults :edit_note, params: { id: order.id, order: { note: 'Hello' } }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/edit_note')
end
end
describe 'update_note' do
let(:order) { create(:order) }
it 'updates order note' do
get_with_defaults :update_note, params: { id: order.id, order: { note: 'Hello' } }, xhr: true
expect(response).to have_http_status(:success)
end
it 'redirects to edit note on failed update' do
get_with_defaults :update_note, params: { id: order.id, order: { article_ids: nil } }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/edit_note')
end
end
describe 'transport' do
let(:order) { create(:order) }
it 'calls the edit transport view' do
get_with_defaults :edit_transport, params: { id: order.id }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/edit_transport')
end
it 'does redirect if order valid' do
get_with_defaults :update_transport, params: { id: order.id, order: { ends: Time.now } }, xhr: true
expect(response).to have_http_status(:redirect)
expect(assigns(:order).errors.count).to eq(0)
expect(response).to redirect_to(new_finance_order_path(order_id: order.id))
end
it 'does redirect if order invalid' do
get_with_defaults :update_transport, params: { id: order.id, order: { starts: Time.now + 2, ends: Time.now } }, xhr: true
expect(assigns(:order).errors.count).to eq(1)
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(new_finance_order_path(order_id: order.id))
end
end
describe 'confirm' do
let(:order) { create(:order) }
it 'renders the confirm template' do
get_with_defaults :confirm, params: { id: order.id }, xhr: true
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/balancing/confirm')
end
end
describe 'close and update account balances' do
let(:order) { create(:order) }
let(:order1) { create(:order, ends: Time.now) }
let(:fft) { create(:financial_transaction_type) }
it 'does not close order if ends not set' do
get_with_defaults :close, params: { id: order.id, type: fft.id }
expect(assigns(:order)).not_to be_closed
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(new_finance_order_url(order_id: order.id))
end
it 'closes order' do
get_with_defaults :close, params: { id: order1.id, type: fft.id }
expect(assigns(:order)).to be_closed
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(finance_order_index_url)
end
end
describe 'close direct' do
let(:order) { create(:order) }
it 'does not close order if already closed' do
order.close_direct!(user)
get_with_defaults :close_direct, params: { id: order.id }
expect(assigns(:order)).to be_closed
end
it 'closes order directly' do
get_with_defaults :close_direct, params: { id: order.id }
expect(assigns(:order)).to be_closed
end
end
describe 'close all direct' do
let(:invoice) { create(:invoice) }
let(:invoice1) { create(:invoice) }
let(:order) { create(:order, state: 'finished', ends: Time.now + 2.hours, invoice: invoice) }
let(:order1) { create(:order, state: 'finished', ends: Time.now + 2.hours) }
before do
order
order1
end
it 'does close orders' do
get_with_defaults :close_all_direct_with_invoice
order.reload
expect(order).to be_closed
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(finance_order_index_url)
end
it 'does not close orders when invoice not set' do
get_with_defaults :close_all_direct_with_invoice
order1.reload
expect(order1).not_to be_closed
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(finance_order_index_url)
end
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'spec_helper'
describe Finance::BaseController, type: :controller do
let(:user) { create :user, :role_finance, :role_orders, :ordergroup }
before { login user }
describe 'GET index' do
let(:fin_trans) { create_list :financial_transaction, 3, user: user, ordergroup: user.ordergroup }
let(:orders) { create_list :order, 2, state: 'finished' }
let(:invoices) { create_list :invoice, 4 }
before do
fin_trans
orders
invoices
end
it 'renders index page' do
get_with_defaults :index
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/index')
expect(assigns(:financial_transactions).size).to eq(fin_trans.size)
expect(assigns(:orders).size).to eq(orders.size)
expect(assigns(:unpaid_invoices).size).to eq(invoices.size)
end
end
end

View file

@ -0,0 +1,197 @@
# frozen_string_literal: true
require 'spec_helper'
describe HomeController, type: :controller do
let(:user) { create :user }
describe 'GET index' do
describe 'NOT logged in' do
it 'redirects' do
get_with_defaults :profile
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(login_path)
end
end
describe 'logegd in' do
before { login user }
it 'assigns tasks' do
get_with_defaults :index
expect(assigns(:unaccepted_tasks)).not_to be_nil
expect(assigns(:next_tasks)).not_to be_nil
expect(assigns(:unassigned_tasks)).not_to be_nil
expect(response).to render_template('home/index')
end
end
end
describe 'GET profile' do
before { login user }
it 'renders dashboard' do
get_with_defaults :profile
expect(response).to have_http_status(:success)
expect(response).to render_template('home/profile')
end
end
describe 'GET reference_calculator' do
describe 'with simple user' do
before { login user }
it 'redirects to home' do
get_with_defaults :reference_calculator
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(root_path)
end
end
describe 'with ordergroup user' do
let(:og_user) { create :user, :ordergroup }
before { login og_user }
it 'renders reference calculator' do
get_with_defaults :reference_calculator
expect(response).to have_http_status(:success)
expect(response).to render_template('home/reference_calculator')
end
end
end
describe 'GET update_profile' do
describe 'with simple user' do
let(:unchanged_attributes) { user.attributes.slice('first_name', 'last_name', 'email') }
let(:changed_attributes) { attributes_for :user }
let(:invalid_attributes) { { email: 'e.mail.com' } }
before { login user }
it 'renders profile after update with invalid attributes' do
get_with_defaults :update_profile, params: { user: invalid_attributes }
expect(response).to have_http_status(:success)
expect(response).to render_template('home/profile')
expect(assigns(:current_user).errors.present?).to be true
end
it 'redirects to profile after update with unchanged attributes' do
get_with_defaults :update_profile, params: { user: unchanged_attributes }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(my_profile_path)
end
it 'redirects to profile after update' do
patch :update_profile, params: { foodcoop: FoodsoftConfig[:default_scope], user: changed_attributes }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(my_profile_path)
expect(flash[:notice]).to match(/#{I18n.t('home.changes_saved')}/)
expect(user.reload.attributes.slice(:first_name, :last_name, :email)).to eq(changed_attributes.slice('first_name', 'last_name', 'email'))
end
end
describe 'with ordergroup user' do
let(:og_user) { create :user, :ordergroup }
let(:unchanged_attributes) { og_user.attributes.slice('first_name', 'last_name', 'email') }
let(:changed_attributes) { unchanged_attributes.merge({ ordergroup: { contact_address: 'new Adress 7' } }) }
before { login og_user }
it 'redirects to home after update' do
get_with_defaults :update_profile, params: { user: changed_attributes }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(my_profile_path)
expect(og_user.reload.ordergroup.contact_address).to eq('new Adress 7')
end
end
end
describe 'GET ordergroup' do
describe 'with simple user' do
before { login user }
it 'redirects to home' do
get_with_defaults :ordergroup
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(root_path)
end
end
describe 'with ordergroup user' do
let(:og_user) { create :user, :ordergroup }
before { login og_user }
it 'renders ordergroup' do
get_with_defaults :ordergroup
expect(response).to have_http_status(:success)
expect(response).to render_template('home/ordergroup')
end
describe 'assigns sortings' do
let(:fin_trans1) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'A', amount: 100 }
let(:fin_trans2) { create :financial_transaction, user: og_user, ordergroup: og_user.ordergroup, note: 'B', amount: 200, created_on: Time.now + 1.minute }
before do
fin_trans1
fin_trans2
end
it 'by criteria' do
sortings = [
['date', [fin_trans1, fin_trans2]],
['note', [fin_trans1, fin_trans2]],
['amount', [fin_trans1, fin_trans2]],
['date_reverse', [fin_trans2, fin_trans1]],
['note_reverse', [fin_trans2, fin_trans1]],
['amount_reverse', [fin_trans2, fin_trans1]]
]
sortings.each do |sorting|
get_with_defaults :ordergroup, params: { sort: sorting[0] }
expect(response).to have_http_status(:success)
expect(assigns(:financial_transactions).to_a).to eq(sorting[1])
end
end
end
end
end
describe 'GET cancel_membership' do
describe 'with simple user without group' do
before { login user }
it 'fails' do
expect do
get_with_defaults :cancel_membership
end.to raise_error(ActiveRecord::RecordNotFound)
expect do
get_with_defaults :cancel_membership, params: { membership_id: 424242 }
end.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe 'with ordergroup user' do
let(:fin_user) { create :user, :role_finance }
before { login fin_user }
it 'removes user from group' do
membership = fin_user.memberships.first
get_with_defaults :cancel_membership, params: { group_id: fin_user.groups.first.id }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(my_profile_path)
expect(flash[:notice]).to match(/#{I18n.t('home.ordergroup_cancelled', :group => membership.group.name)}/)
end
it 'removes user membership' do
membership = fin_user.memberships.first
get_with_defaults :cancel_membership, params: { membership_id: membership.id }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(my_profile_path)
expect(flash[:notice]).to match(/#{I18n.t('home.ordergroup_cancelled', :group => membership.group.name)}/)
end
end
end
end

View file

@ -0,0 +1,67 @@
# frozen_string_literal: true
require 'spec_helper'
describe LoginController, type: :controller do
let(:invite) { create :invite }
describe 'GET accept_invitation' do
let(:expired_invite) { create :expired_invite }
describe 'with valid token' do
it 'accepts invitation' do
get_with_defaults :accept_invitation, params: { token: invite.token }
expect(response).to have_http_status(:success)
expect(response).to render_template('login/accept_invitation')
end
end
describe 'with invalid token' do
it 'redirects to login' do
get_with_defaults :accept_invitation, params: { token: invite.token + 'XX' }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(login_url)
expect(flash[:alert]).to match(I18n.t('login.controller.error_invite_invalid'))
end
end
describe 'with timed out token' do
it 'redirects to login' do
get_with_defaults :accept_invitation, params: { token: expired_invite.token }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(login_url)
expect(flash[:alert]).to match(I18n.t('login.controller.error_invite_invalid'))
end
end
describe 'without group' do
it 'redirects to login' do
invite.group.destroy
get_with_defaults :accept_invitation, params: { token: invite.token }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(login_url)
expect(flash[:alert]).to match(I18n.t('login.controller.error_group_invalid'))
end
end
end
describe 'POST accept_invitation' do
describe 'with invalid parameters' do
it 'renders accept_invitation view' do
post_with_defaults :accept_invitation, params: { token: invite.token, user: invite.user.slice('first_name') }
expect(response).to have_http_status(:success)
expect(response).to render_template('login/accept_invitation')
expect(assigns(:user).errors.present?).to be true
end
end
describe 'with valid parameters' do
it 'redirects to login' do
post_with_defaults :accept_invitation, params: { token: invite.token, user: invite.user.slice('first_name', 'password') }
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to(login_url)
expect(flash[:notice]).to match(I18n.t('login.controller.accept_invitation.notice'))
end
end
end
end

15
spec/factories/invite.rb Normal file
View file

@ -0,0 +1,15 @@
require 'factory_bot'
FactoryBot.define do
factory :invite do
user { create :user }
group { create :group }
email { Faker::Internet.email }
factory :expired_invite do
after :create do |invite|
invite.update_column(:expires_at, Time.now.yesterday)
end
end
end
end

View file

@ -0,0 +1,8 @@
require 'factory_bot'
FactoryBot.define do
factory :order_article do
order { create :order }
article { create :article }
end
end

3
spec/fixtures/files/upload_test.csv vendored Normal file
View file

@ -0,0 +1,3 @@
avail.;Order number;Name;Note;Manufacturer;Origin;Unit;Price (net);VAT;Deposit;Unit quantity;"";"";Category
"";;AAAA;AAAA;;;500 g;25.55;6.0;0.0;1;"";"";AAAA
"";;BBBB;BBBB;;;250 g;12.11;6.0;0.0;2;"";"";BBBB
1 avail. Order number Name Note Manufacturer Origin Unit Price (net) VAT Deposit Unit quantity Category
2 AAAA AAAA 500 g 25.55 6.0 0.0 1 AAAA
3 BBBB BBBB 250 g 12.11 6.0 0.0 2 BBBB

3
spec/fixtures/upload_test.csv vendored Normal file
View file

@ -0,0 +1,3 @@
avail.;Order number;Name;Note;Manufacturer;Origin;Unit;Price (net);VAT;Deposit;Unit quantity;"";"";Category
"";;AAAA;AAAA;;;500 g;25.55;6.0;0.0;1;"";"";AAAA
"";;BBBB;BBBB;;;250 g;12.11;6.0;0.0;2;"";"";BBBB
1 avail. Order number Name Note Manufacturer Origin Unit Price (net) VAT Deposit Unit quantity Category
2 AAAA AAAA 500 g 25.55 6.0 0.0 1 AAAA
3 BBBB BBBB 250 g 12.11 6.0 0.0 2 BBBB

View file

@ -19,9 +19,13 @@ describe Supplier do
end end
it 'return correct tolerance' do it 'return correct tolerance' do
supplier = create :supplier, articles: create_list(:article, 1, unit_quantity: 1) supplier = create :supplier
articles = create_list(:article, 1, unit_quantity: 1, supplier_id: supplier.id)
supplier.reload
expect(supplier.has_tolerance?).to be false expect(supplier.has_tolerance?).to be false
supplier2 = create :supplier, articles: create_list(:article, 1, unit_quantity: 2) supplier2 = create :supplier
articles = create_list(:article, 1, unit_quantity: 2, supplier_id: supplier2.id)
supplier.reload
expect(supplier2.has_tolerance?).to be true expect(supplier2.has_tolerance?).to be true
end end

View file

@ -0,0 +1,53 @@
require 'swagger_helper'
describe 'Article Categories', type: :request do
include ApiHelper
path '/article_categories' do
get 'article categories' do
tags 'Category'
produces 'application/json'
pagination_param
let(:order_article) { create(:order, article_count: 1).order_articles.first }
let(:stock_article) { create(:stock_article) }
let(:stock_order_article) { create(:stock_order, article_ids: [stock_article.id]).order_articles.first }
response '200', 'success' do
schema type: :object, properties: {
article_categories: {
type: :array,
items: {
'$ref': '#/components/schemas/ArticleCategory'
}
}
}
run_test!
end
it_handles_invalid_token
end
end
path '/article_categories/{id}' do
get 'find article category by id' do
tags 'Category'
produces 'application/json'
id_url_param
response '200', 'article category found' do
schema type: :object, properties: {
article_categories: {
type: :array,
items: {
'$ref': '#/components/schemas/ArticleCategory'
}
}
}
let(:id) { create(:article_category, name: 'dairy').id }
run_test!
end
it_handles_invalid_token_with_id
it_cannot_find_object
end
end
end

View file

@ -0,0 +1,20 @@
require 'swagger_helper'
describe 'Config', type: :request do
include ApiHelper
path '/config' do
get 'configuration variables' do
tags 'General'
produces 'application/json'
let(:api_scopes) { ['config:user'] }
response '200', 'success' do
schema type: :object, properties: {}
run_test!
end
it_handles_invalid_token_and_scope
end
end
end

View file

@ -0,0 +1,54 @@
require 'swagger_helper'
describe 'Financial Transaction Classes', type: :request do
include ApiHelper
path '/financial_transaction_classes' do
get 'financial transaction classes' do
tags 'Category'
produces 'application/json'
pagination_param
let(:financial_transaction_class) { create(:financial_transaction_class) }
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref' => '#/components/schemas/Meta' },
financial_transaction_class: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransactionClass'
}
}
}
run_test!
end
it_handles_invalid_token
end
end
path '/financial_transaction_classes/{id}' do
get 'Retrieves a financial transaction class' do
tags 'Category'
produces 'application/json'
id_url_param
response '200', 'financial transaction class found' do
schema type: :object, properties: {
financial_transaction_classes: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransactionClass'
}
}
}
let(:id) { create(:financial_transaction_class).id }
run_test!
end
it_handles_invalid_token_with_id
it_cannot_find_object 'financial transaction class not found'
end
end
end

View file

@ -0,0 +1,52 @@
require 'swagger_helper'
describe 'Financial Transaction types', type: :request do
include ApiHelper
path '/financial_transaction_types' do
get 'financial transaction types' do
tags 'Category'
produces 'application/json'
pagination_param
let(:financial_transaction_type) { create(:financial_transaction_type) }
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref' => '#/components/schemas/Meta' },
financial_transaction_type: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransactionType'
}
}
}
run_test!
end
it_handles_invalid_token
end
end
path '/financial_transaction_types/{id}' do
get 'find financial transaction type by id' do
tags 'Category'
produces 'application/json'
id_url_param
response '200', 'financial transaction type found' do
schema type: :object, properties: {
financial_transaction_types: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransactionType'
}
}
}
let(:id) { create(:financial_transaction_type).id }
run_test!
end
it_handles_invalid_token_with_id
it_cannot_find_object 'financial transaction type not found'
end
end
end

View file

@ -0,0 +1,56 @@
require 'swagger_helper'
describe 'Financial Transaction', type: :request do
include ApiHelper
let!(:finance_user) { create(:user, groups: [create(:workgroup, role_finance: true)]) }
let!(:api_scopes) { ['finance:read', 'finance:write'] }
let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: finance_user.id, scopes: api_scopes&.join(' ')).token }
let(:financial_transaction) { create(:financial_transaction, user: user) }
path '/financial_transactions' do
get 'financial transactions' do
tags 'Financial Transaction'
produces 'application/json'
pagination_param
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref' => '#/components/schemas/Meta' },
financial_transaction: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransaction'
}
}
}
run_test!
end
it_handles_invalid_token_and_scope
end
end
path '/financial_transactions/{id}' do
get 'Retrieves a financial transaction ' do
tags 'Financial Transaction'
produces 'application/json'
id_url_param
response '200', 'financial transaction found' do
schema type: :object, properties: {
financial_transaction: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransaction'
}
}
}
let(:id) { FinancialTransaction.create(user: user).id }
run_test!
end
it_handles_invalid_token_with_id
it_handles_invalid_scope_with_id
it_cannot_find_object 'financial transaction not found'
end
end
end

View file

@ -0,0 +1,24 @@
require 'swagger_helper'
describe 'Navigation', type: :request do
include ApiHelper
path '/navigation' do
get 'navigation' do
tags 'General'
produces 'application/json'
response '200', 'success' do
schema type: :object, properties: {
navigation: {
'$ref' => '#/components/schemas/Navigation'
}
}
run_test!
end
it_handles_invalid_token
end
end
end

View file

@ -0,0 +1,115 @@
require 'swagger_helper'
describe 'Order Articles', type: :request do
include ApiHelper
path '/order_articles' do
get 'order articles' do
tags 'Order'
produces 'application/json'
pagination_param
q_ordered_url_param
let(:api_scopes) { ['orders:read', 'orders:write'] }
let(:order) { create(:order, article_count: 4) }
let(:order_articles) { order.order_articles }
before do
order_articles[0].update! quantity: 0, tolerance: 0, units_to_order: 0
order_articles[1].update! quantity: 1, tolerance: 0, units_to_order: 0
order_articles[2].update! quantity: 0, tolerance: 1, units_to_order: 0
order_articles[3].update! quantity: 0, tolerance: 0, units_to_order: 1
end
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref' => '#/components/schemas/Meta' },
order_articles: {
type: :array,
items: {
'$ref': '#/components/schemas/OrderArticle'
}
}
}
describe '(unset)' do
run_test!
end
describe 'all' do
let(:q) { { q: { ordered: 'all' } } }
run_test! do |response|
json_order_articles = JSON.parse(response.body)['order_articles']
json_order_article_ids = json_order_articles.map { |d| d['id'].to_i }
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
end
end
describe 'when ordered by supplier' do
let(:q) { { q: { ordered: 'supplier' } } }
run_test! do |response|
json_order_articles = JSON.parse(response.body)['order_articles']
json_order_article_ids = json_order_articles.map { |d| d['id'].to_i }
expect(json_order_article_ids).to match_array [order_articles[3].id]
end
end
describe 'when ordered by member' do
let(:q) { { q: { ordered: 'member' } } }
run_test! do |response|
json_order_articles = JSON.parse(response.body)['order_articles']
expect(json_order_articles.count).to eq 0
end
end
context 'when ordered by user' do
let(:user) { create(:user, :ordergroup) }
let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
before do
create(:group_order_article, group_order: go, order_article: order_articles[1], quantity: 1)
create(:group_order_article, group_order: go, order_article: order_articles[2], tolerance: 0)
end
describe 'member' do
let(:q) { { q: { ordered: 'member' } } }
run_test! do |response|
json_order_articles = JSON.parse(response.body)['order_articles']
json_order_article_ids = json_order_articles.map { |d| d['id'].to_i }
expect(json_order_article_ids).to match_array order_articles[1..2].map(&:id)
end
end
end
end
it_handles_invalid_token_and_scope
end
end
path '/order_articles/{id}' do
get 'order articles' do
tags 'Order'
produces 'application/json'
id_url_param
let(:api_scopes) { ['orders:read', 'orders:write'] }
response '200', 'success' do
schema type: :object, properties: {
order_article: {
'$ref': '#/components/schemas/OrderArticle'
}
}
let(:order) { create(:order, article_count: 1) }
let(:id) { order.order_articles.first.id }
run_test!
end
it_handles_invalid_token_and_scope
it_cannot_find_object 'order article not found'
end
end
end

View file

@ -0,0 +1,55 @@
require 'swagger_helper'
describe 'Orders', type: :request do
include ApiHelper
let(:api_scopes) { ['orders:read'] }
path '/orders' do
get 'orders' do
tags 'Order'
produces 'application/json'
pagination_param
let(:order) { create(:order) }
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref' => '#/components/schemas/Meta' },
ordes: {
type: :array,
items: {
'$ref': '#/components/schemas/Order'
}
}
}
run_test!
end
it_handles_invalid_token_and_scope
end
end
path '/orders/{id}' do
get 'Order' do
tags 'Order'
produces 'application/json'
id_url_param
let(:order) { create(:order) }
response '200', 'success' do
schema type: :object, properties: {
order: { '$ref' => '#/components/schemas/Order' }
}
let(:id) { order.id }
run_test! do |response|
expect(JSON.parse(response.body)['order']['id']).to eq order.id
end
end
it_handles_invalid_token_and_scope
it_cannot_find_object 'order not found'
end
end
end

View file

@ -0,0 +1,109 @@
require 'swagger_helper'
describe 'User', type: :request do
include ApiHelper
let(:api_scopes) { ['finance:user'] }
let(:user) { create :user, groups: [create(:ordergroup)] }
let(:other_user2) { create :user }
let(:ft) { create(:financial_transaction, user: user, ordergroup: user.ordergroup) }
before do
ft
end
path '/user/financial_transactions' do
post 'create new financial transaction (requires enabled self service)' do
tags 'Financial Transaction'
consumes 'application/json'
produces 'application/json'
parameter name: :financial_transaction, in: :body, schema: {
type: :object,
properties: {
amount: { type: :integer },
financial_transaction_type: { type: :integer },
note: { type: :string }
}
}
let(:financial_transaction) { { amount: 3, financial_transaction_type_id: create(:financial_transaction_type).id, note: 'lirum larum' } }
response '200', 'success' do
schema type: :object, properties: {
financial_transaction: { '$ref': '#/components/schemas/FinancialTransaction' }
}
run_test!
end
it_handles_invalid_token_with_id
it_handles_invalid_scope_with_id 'user has no ordergroup, is below minimum balance, self service is disabled, or missing scope'
response '404', 'financial transaction type not found' do
schema '$ref' => '#/components/schemas/Error404'
let(:financial_transaction) { { amount: 3, financial_transaction_type_id: 'invalid', note: 'lirum larum' } }
run_test!
end
# TODO: fix controller to actually send a 422 for invalid params?
# Expected response code '200' to match '422'
# Response body: {"financial_transaction":{"id":316,"user_id":599,"user_name":"Lisbeth ","amount":-3.0,"note":"-2","created_at":"2022-12-12T13:05:32.000+01:00","financial_transaction_type_id":346,"financial_transaction_type_name":"aut est iste #9"}}
#
# response '422', 'invalid parameter value' do
# # schema '$ref' => '#/components/schemas/Error422'
# let(:financial_transaction) { { amount: -3, financial_transaction_type_id: create(:financial_transaction_type).id, note: -2 } }
# run_test!
# end
end
get "financial transactions of the member's ordergroup" do
tags 'User', 'Financial Transaction'
produces 'application/json'
pagination_param
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref': '#/components/schemas/Meta' },
financial_transaction: {
type: :array,
items: {
'$ref': '#/components/schemas/FinancialTransaction'
}
}
}
run_test! do |response|
data = JSON.parse(response.body)
expect(data['financial_transactions'].first['id']).to eq(ft.id)
end
end
it_handles_invalid_token_and_scope
end
end
path '/user/financial_transactions/{id}' do
get 'find financial transaction by id' do
tags 'User', 'Financial Transaction'
produces 'application/json'
id_url_param
response '200', 'success' do
schema type: :object, properties: {
financial_transaction: {
'$ref': '#/components/schemas/FinancialTransaction'
}
}
let(:id) { ft.id }
run_test! do |response|
data = JSON.parse(response.body)
expect(data['financial_transaction']['id']).to eq(ft.id)
end
end
it_handles_invalid_token_with_id
it_handles_invalid_scope_with_id 'user has no ordergroup or missing scope'
it_cannot_find_object 'financial transaction not found'
end
end
end

View file

@ -0,0 +1,192 @@
require 'swagger_helper'
describe 'User', type: :request do
include ApiHelper
let(:api_scopes) { ['group_orders:user'] }
let(:user) { create :user, groups: [create(:ordergroup)] }
let(:other_user2) { create :user }
let(:order) { create(:order, article_count: 4) }
let(:order_articles) { order.order_articles }
let(:group_order) { create :group_order, ordergroup: user.ordergroup, order_id: order.id }
let(:goa) { create :group_order_article, group_order: group_order, order_article: order_articles.first }
before do
goa
end
path '/user/group_order_articles' do
get 'group order articles' do
tags 'User', 'Order'
produces 'application/json'
pagination_param
q_ordered_url_param
response '200', 'success' do
schema type: :object, properties: {
meta: { '$ref': '#/components/schemas/Meta' },
group_order_article: {
type: :array,
items: {
'$ref': '#/components/schemas/GroupOrderArticle'
}
}
}
run_test! do |response|
data = JSON.parse(response.body)
expect(data['group_order_articles'].first['id']).to eq(goa.id)
end
end
it_handles_invalid_token
it_handles_invalid_scope 'user has no ordergroup or missing scope'
end
post 'create new group order article' do
tags 'User', 'Order'
consumes 'application/json'
produces 'application/json'
parameter name: :group_order_article, in: :body,
description: 'group order article to create',
required: true,
schema: { '$ref': '#/components/schemas/GroupOrderArticleForCreate' }
let(:group_order_article) { { order_article_id: order_articles.last.id, quantity: 1, tolerance: 2 } }
response '200', 'success' do
schema type: :object, properties: {
group_order_article: {
'$ref': '#/components/schemas/GroupOrderArticle'
},
order_article: {
'$ref': '#/components/schemas/OrderArticle'
}
}
run_test!
end
it_handles_invalid_token_with_id
it_handles_invalid_scope_with_id 'user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope'
response '404', 'order article not found in open orders' do
let(:group_order_article) { { order_article_id: 'invalid', quantity: 1, tolerance: 2 } }
schema '$ref' => '#/components/schemas/Error404'
run_test!
end
response '422', 'invalid parameter value or group order article already exists' do
let(:group_order_article) { { order_article_id: goa.order_article_id, quantity: 1, tolerance: 2 } }
schema '$ref' => '#/components/schemas/Error422'
run_test!
end
end
end
path '/user/group_order_articles/{id}' do
get 'find group order article by id' do
tags 'User', 'Order'
produces 'application/json'
id_url_param
response '200', 'success' do
schema type: :object, properties: {
group_order_article: {
'$ref': '#/components/schemas/GroupOrderArticle'
},
order_article: {
'$ref': '#/components/schemas/OrderArticle'
}
}
let(:id) { goa.id }
run_test! do |response|
data = JSON.parse(response.body)
expect(data['group_order_article']['id']).to eq(goa.id)
end
end
it_handles_invalid_scope_with_id
it_handles_invalid_token_with_id
it_cannot_find_object 'group order article not found'
end
patch 'update a group order article (but delete if quantity and tolerance are zero)' do
tags 'User', 'Order'
consumes 'application/json'
produces 'application/json'
id_url_param
parameter name: :group_order_article, in: :body,
description: 'group order article update',
required: true,
schema: { '$ref': '#/components/schemas/GroupOrderArticleForUpdate' }
let(:id) { goa.id }
let(:group_order_article) { { order_article_id: goa.order_article_id, quantity: 2, tolerance: 2 } }
response '200', 'success' do
schema type: :object, properties: {
group_order_article: {
'$ref': '#/components/schemas/GroupOrderArticle'
}
}
run_test!
end
response 401, 'not logged-in' do
schema '$ref' => '#/components/schemas/Error401'
let(:Authorization) { 'abc' }
run_test!
end
response 403, 'user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope' do
let(:api_scopes) { ['none'] }
schema '$ref' => '#/components/schemas/Error403'
run_test!
end
response '404', 'order article not found in open orders' do
schema type: :object, properties: {
group_order_article: {
'$ref': '#/components/schemas/GroupOrderArticle'
}
}
let(:id) { 'invalid' }
run_test!
end
response '422', 'invalid parameter value' do
let(:group_order_article) { { order_article_id: 'invalid', quantity: -5, tolerance: 'invalid' } }
schema '$ref' => '#/components/schemas/Error422'
run_test!
end
end
delete 'remove group order article' do
tags 'User', 'Order'
consumes 'application/json'
produces 'application/json'
id_url_param
let(:api_scopes) { ['group_orders:user'] }
response '200', 'success' do
schema type: :object, properties: {
group_order_article: {
'$ref': '#/components/schemas/GroupOrderArticle'
}
}
let(:id) { goa.id }
run_test!
end
it_handles_invalid_token_with_id
response 403, 'user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope' do
let(:api_scopes) { ['none'] }
schema '$ref' => '#/components/schemas/Error403'
run_test!
end
it_cannot_find_object 'order article not found in open orders'
end
end
end

View file

@ -0,0 +1,103 @@
require 'swagger_helper'
describe 'User', type: :request do
include ApiHelper
path '/user' do
get 'info about the currently logged-in user' do
tags 'User'
produces 'application/json'
let(:api_scopes) { ['user:read'] }
let(:other_user1) { create :user }
let(:user) { create :user }
let(:other_user2) { create :user }
response '200', 'success' do
schema type: :object,
properties: {
user: {
type: :object,
properties: {
id: {
type: :integer
},
name: {
type: :string,
description: 'full name'
},
email: {
type: :string,
description: 'email address'
},
locale: {
type: :string,
description: 'language code'
}
},
required: %w[id name email]
}
}
run_test! do |response|
data = JSON.parse(response.body)
expect(data['user']['id']).to eq(user.id)
end
end
it_handles_invalid_token_and_scope
end
end
path '/user/financial_overview' do
get 'financial summary about the currently logged-in user' do
tags 'User', 'Financial Transaction'
produces 'application/json'
let(:user) { create :user, :ordergroup }
let(:api_scopes) { ['finance:user'] }
FinancialTransactionClass.create(name: 'TestTransaction')
response 200, 'success' do
schema type: :object,
properties: {
financial_overview: {
type: :object,
properties: {
account_balance: {
type: :number,
description: 'booked accout balance of ordergroup'
},
available_funds: {
type: :number,
description: 'fund available to order articles'
},
financial_transaction_class_sums: {
type: :array,
properties: {
id: {
type: :integer,
description: 'id of the financial transaction class'
},
name: {
type: :string,
description: 'name of the financial transaction class'
},
amount: {
type: :number,
description: 'sum of the amounts belonging to the financial transaction class'
}
},
required: %w[id name amount]
}
},
required: %w[account_balance available_funds financial_transaction_class_sums]
}
}
run_test!
end
it_handles_invalid_token_and_scope
end
end
end

View file

@ -21,6 +21,10 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config| RSpec.configure do |config|
# We use capybara with webkit, and need database_cleaner # We use capybara with webkit, and need database_cleaner
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do config.before(:each) do
DatabaseCleaner.strategy = (RSpec.current_example.metadata[:js] ? :truncation : :transaction) DatabaseCleaner.strategy = (RSpec.current_example.metadata[:js] ? :truncation : :transaction)
DatabaseCleaner.start DatabaseCleaner.start
@ -51,8 +55,8 @@ RSpec.configure do |config|
# --seed 1234 # --seed 1234
config.order = "random" config.order = "random"
config.include SpecTestHelper, type: :controller
config.include SessionHelper, type: :feature config.include SessionHelper, type: :feature
# Automatically determine spec from directory structure, see: # Automatically determine spec from directory structure, see:
# https://www.relishapp.com/rspec/rspec-rails/v/3-0/docs/directory-structure # https://www.relishapp.com/rspec/rspec-rails/v/3-0/docs/directory-structure
config.infer_spec_type_from_file_location! config.infer_spec_type_from_file_location!

View file

@ -5,21 +5,60 @@ module ApiHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:api_scopes) { [] } # empty scopes for stricter testing (in reality this would be default_scopes) let(:api_scopes) { [] } # empty scopes for stricter testing (in reality this would be default_scopes)
let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token } let(:api_access_token) { create(:oauth2_access_token, resource_owner_id: user.id, scopes: api_scopes&.join(' ')).token }
let(:api_authorization) { "Bearer #{api_access_token}" } let(:Authorization) { "Bearer #{api_access_token}" }
def self.it_handles_invalid_token(method, path, params_block = -> { api_auth }) def self.it_handles_invalid_token
context 'with invalid access token' do context 'with invalid access token' do
let(:api_access_token) { 'abc' } let(:Authorization) { 'abc' }
it { is_expected.to validate(method, path, 401, instance_exec(&params_block)) } response 401, 'not logged-in' do
schema '$ref' => '#/components/schemas/Error401'
run_test!
end
end end
end end
def self.it_handles_invalid_scope(method, path, params_block = -> { api_auth }) def self.it_handles_invalid_token_with_id
context 'with invalid access token' do
let(:Authorization) { 'abc' }
let(:id) { 42 } # id doesn't matter here
response 401, 'not logged-in' do
schema '$ref' => '#/components/schemas/Error401'
run_test!
end
end
end
def self.it_handles_invalid_scope(description = 'missing scope')
context 'with invalid scope' do context 'with invalid scope' do
let(:api_scopes) { ['none'] } let(:api_scopes) { ['none'] }
it { is_expected.to validate(method, path, 403, instance_exec(&params_block)) } response 403, description do
schema '$ref' => '#/components/schemas/Error403'
run_test!
end
end
end
def self.it_handles_invalid_scope_with_id(description = 'missing scope')
context 'with invalid scope' do
let(:api_scopes) { ['none'] }
let(:id) { 42 } # id doesn't matter here
response 403, description do
schema '$ref' => '#/components/schemas/Error403'
run_test!
end
end
end
def self.it_cannot_find_object(description = 'not found')
let(:id) { 'invalid' }
response 404, description do
schema '$ref' => '#/components/schemas/Error404'
run_test!
end end
end end
@ -27,13 +66,25 @@ module ApiHelper
it_handles_invalid_token(*args) it_handles_invalid_token(*args)
it_handles_invalid_scope(*args) it_handles_invalid_scope(*args)
end end
def self.id_url_param
parameter name: :id, in: :path, type: :integer, required: true
end end
# Add authentication to parameters for {Swagger::RspecHelpers#validate} def self.pagination_param
# @param params [Hash] Query parameters parameter name: :per_page, in: :query, type: :integer, required: false
# @return Query parameters with authentication header parameter name: :page, in: :query, type: :integer, required: false
# @see Swagger::RspecHelpers#validate end
def api_auth(params = {})
{ '_headers' => { 'Authorization' => api_authorization } }.deep_merge(params) def self.q_ordered_url_param
parameter name: :q, in: :query, required: false,
description: "'member' show articles ordered by the user's ordergroup, 'all' by all members, and 'supplier' ordered at the supplier",
schema: {
type: :object,
properties: {
ordered: { '$ref' => '#/components/schemas/q_ordered' }
}
}
end
end end
end end

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