Compare commits

...

19 commits

Author SHA1 Message Date
e42baa1d3b finish plugin for bnn, foodsoft, odin uploads 2023-03-30 15:31:03 +02:00
9d0e2fdafc add integration/articles_spec 2023-03-30 15:31:03 +02:00
16577d7c51 add plugin specs 2023-03-30 15:31:03 +02:00
67d58e4b45 modify view of sync_table for it is not customizable with deface 2023-03-30 15:31:03 +02:00
317379ed40 add update_category as option 2023-03-30 15:31:03 +02:00
109286f0d7 add basic functionality of plugin 2023-03-30 15:30:59 +02:00
ae8d222f82 prepare for plugin import 2023-03-28 17:03:24 +02:00
bee1d68ff2 make deface work again 2023-03-23 12:02:02 +01:00
91a38bc73b move BigDecimal.new to BigDecimal() 2023-02-17 13:16:28 +01:00
782194cd08 change .search to .ransack for updated ransack gem 2023-02-17 13:16:19 +01:00
Philipp Rothmann
78da4feafe fix: assets precompile by using terser
importmaps broke precompiliation with uglifier
see: https://github.com/rails/importmap-rails/issues/5
2023-02-10 13:07:02 +01:00
Philipp Rothmann
666e7934a6 introduce importmaps
This commit introduces importmaps. They allow to use modern javacript ESM within rails without webpack, yarn etc.
see https://github.com/rails/importmap-rails for more details.

Co-authored-by: Philipp Rothmann <philipprothmann@posteo.de>
Co-authored-by: FGU <fgu@pragma-shift.net>
2023-02-10 12:24:42 +01:00
Philipp Rothmann
82d4ff0284 improve dockerfile caching 2023-02-09 17:22:38 +01:00
Philipp Rothmann
c487f0368a upgrade dockerfile to rails7 2023-02-09 17:21:05 +01:00
FGU
a7747c9e84 fix docker-compose 2023-02-02 10:14:26 +01:00
fb8ccfea4a rails up to 7.0and ruby to 2.7.2
mv lib to app/lib due to upgrade

removing concerns from autoload path

resolve zeitwerk issues

make foodsoft run for dev on rails 7 and ruby 2.7

fix mail file permission bug

fix database_config

fix articles controller test ActiveModell::Error

bump Gemfile.lock
2023-01-17 16:35:04 +01:00
Philipp Rothmann
d7591d46b9 Add controller tests
Co-authored-by: viehlieb <pf@pragma-shift.net>
Co-authored-by: Tobias Kneuker <tk@pragma-shift.net>

seperate expects

refactor login user calls

add more articles to test sorting with

fix: fix test for rails upgrade
2023-01-17 16:09:52 +01:00
Philipp Rothmann
d16aa19300 Add home controller test
Co-authored-by: viehlieb <pf@pragma-shift.net>
Co-authored-by: Tobias Kneuker <tk@pragma-shift.net>
2023-01-17 16:09:27 +01:00
Philipp Rothmann
3f114af193 replace apivore with rswag 2023-01-17 16:07:54 +01:00
145 changed files with 6421 additions and 2478 deletions

View file

@ -266,7 +266,7 @@ Metrics/AbcSize:
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, inherit_mode.
# AllowedMethods: refine
Metrics/BlockLength:
Max: 210
Max: 212
# Offense count: 6
# Configuration parameters: CountBlocks.
@ -451,6 +451,24 @@ RSpec/DescribedClass:
- "spec/models/ordergroup_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
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
@ -581,6 +599,14 @@ RSpec/ScatteredSetup:
- "spec/integration/balancing_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
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
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 && \
supercronicBin=/usr/local/bin/supercronic && \
@ -15,13 +15,16 @@ ENV PORT=3000 \
WORKDIR /usr/src/app
COPY . ./
COPY Gemfile Gemfile.lock ./
COPY plugins/ ./plugins
COPY config/ ./config
# install dependencies and generate crontab
RUN buildDeps='libmagic-dev' && \
apt-get update && \
apt-get install --no-install-recommends -y $buildDeps && \
echo 'gem: --no-document' >> ~/.gemrc && \
gem install bundler && \
bundle config build.nokogiri "--use-system-libraries" && \
bundle install --deployment --without development test -j 4 && \
apt-get purge -y --auto-remove $buildDeps && \
@ -29,6 +32,8 @@ RUN buildDeps='libmagic-dev' && \
\
bundle exec whenever >crontab
COPY . ./
# compile assets with temporary mysql server
RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
export SECRET_KEY_BASE=thisisnotimportantnow && \

View file

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

25
Gemfile
View file

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

View file

@ -18,7 +18,7 @@ class Finance::FinancialTransactionsController < ApplicationController
sort = "created_on DESC"
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 = @financial_transactions_all.visible unless params[:show_hidden]
@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 = [BankAccount.last] if @bank_accounts.empty?
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
@ -26,7 +26,7 @@ class HomeController < ApplicationController
if @current_user.update(user_params)
@current_user.ordergroup.update(ordergroup_params) if ordergroup_params
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
render :profile
end
@ -64,7 +64,7 @@ class HomeController < ApplicationController
# cancel personal memberships direct from the myProfile-page
def cancel_membership
if params[:membership_id]
membership = @current_user.memberships.find!(params[:membership_id])
membership = @current_user.memberships.find(params[:membership_id])
else
membership = @current_user.memberships.find_by_group_id!(params[:group_id])
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
if apples >= 100
'success'
elsif FoodsoftConfig[:stop_ordering_under].present? &&
(apples >= FoodsoftConfig[:stop_ordering_under])
'warning'
else
if FoodsoftConfig[:stop_ordering_under].present? and
apples >= FoodsoftConfig[:stop_ordering_under]
'warning'
else
'danger'
end
'danger'
end
end
# Use apples as percentage, but show at least 10 percent
def group_bar_width
@ordergroup.apples < 2 ? 2 : @ordergroup.apples
[@ordergroup.apples, 2].max
end
def mean_order_amount_per_job
(1 / @global_avg).round rescue 0
(1 / @global_avg).round
rescue
0
end
def apples

View file

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

View file

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

View file

@ -1,7 +1,7 @@
class BankTransactionReference
# parses a string from a bank transaction field
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
parts = {}
@ -13,7 +13,7 @@ class BankTransactionReference
ret = { group: m[:group].to_i, parts: parts }
ret[:user] = m[:user].to_i if m[:user]
return ret
ret
end
def self.js_code_for_user(user)

View file

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

View file

@ -27,12 +27,20 @@ module DateTimeAttributeValidate
define_method("#{attribute}_date_value=") do |val|
self.instance_variable_set("@#{attribute}_is_set", true)
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
define_method("#{attribute}_time_value=") do |val|
self.instance_variable_set("@#{attribute}_is_set", true)
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
# fallback to field when values are not set
@ -48,11 +56,19 @@ module DateTimeAttributeValidate
# validate date and time
define_method("#{attribute}_datetime_value_valid") do
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
end
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
end
end

View file

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

View file

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

View file

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

View file

@ -6,7 +6,11 @@ module FoodsoftDateUtil
schedule = IceCube::Schedule.new(start)
schedule.add_recurrence_rule rule_from(options[:recurr])
# @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
if options && options[:time] && occ
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.
# @return [IceCube::Rule] Recurring rule
def self.rule_from(p)
if p.is_a? String
case p
when String
IceCube::Rule.from_ical(p)
elsif p.is_a? Hash
when Hash
IceCube::Rule.from_hash(p)
else
p

View file

@ -19,7 +19,7 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
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')
@handlers << self.class.find_handler(recipient)
rcpt_to
@ -29,20 +29,18 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
end
def on_message_data_event(ctx)
begin
@handlers.each do |handler|
handler.call(ctx[:message][:data])
end
rescue => error
ExceptionNotifier.notify_exception(error, data: ctx)
raise error
ensure
@handlers.clear
@handlers.each do |handler|
handler.call(ctx[:message][:data])
end
rescue => error
ExceptionNotifier.notify_exception(error, data: ctx)
raise error
ensure
@handlers.clear
end
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 "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|
if match = klass.regexp.match(m[:address])
handler = klass.new match
return lambda { |data| handler.received(data) }
return ->(data) { handler.received(data) }
end
end

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
class OrderTxt
def initialize(order, options = {})
def initialize(order, _options = {})
@order = order
end
@ -15,10 +15,10 @@ class OrderTxt
text += "****** " + I18n.t('orders.fax.to_address') + "\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 += "%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
@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
text
end

View file

@ -1,4 +1,4 @@
class OrdergroupsCsv < RenderCSV
class OrdergroupsCsv < RenderCsv
include ApplicationHelper
def header
@ -14,9 +14,9 @@ class OrdergroupsCsv < RenderCSV
Ordergroup.human_attribute_name(:break_start),
Ordergroup.human_attribute_name(:break_end),
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
def data
@ -33,7 +33,7 @@ class OrdergroupsCsv < RenderCSV
o.break_start,
o.break_end,
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]] }
end

View file

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

View file

@ -21,8 +21,6 @@ class TokenVerifier < ActiveSupport::MessageVerifier
# return original message
if r.length > 2
r[2]
else
nil
end
end
@ -32,8 +30,6 @@ class TokenVerifier < ActiveSupport::MessageVerifier
class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end
protected
def self.secret
# 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

View file

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

View file

@ -8,7 +8,7 @@ module LocalizeInput
separator = I18n.t("separator", scope: "number.format")
delimiter = I18n.t("delimiter", scope: "number.format")
input.gsub!(delimiter, "") if input.match(/\d+#{Regexp.escape(delimiter)}+\d+#{Regexp.escape(separator)}+\d+/) # Remove delimiter
input.gsub!(separator, ".") # Replace separator with db compatible character
input.gsub!(separator, ".") or input.gsub!(",", ".") # Replace separator with db compatible character
input
rescue
Rails.logger.warn "Can't localize input: #{input}"

View file

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

View file

@ -32,8 +32,8 @@
= form.text_field 'name', :size => 0
- hidden_fields.each do |field|
= form.hidden_field field
%td{:style => highlight_new(attrs, :note)}= form.text_field 'note', class: 'input-small'
%td{:style => highlight_new(attrs, :manufacturer)}= form.text_field 'manufacturer', class: 'input-small'
%td{:style => highlight_new(attrs, :note)}= form.text_field 'note', class: 'input-medium'
%td{:style => highlight_new(attrs, :manufacturer)}= form.text_field 'manufacturer', class: 'input-medium'
%td{:style => highlight_new(attrs, :origin)}= form.text_field 'origin', class: 'input-mini'
%td{:style => highlight_new(attrs, :unit)}= form.text_field 'unit', class: 'input-mini'
%td{:style => highlight_new(attrs, :unit_quantity)}= form.text_field 'unit_quantity', class: 'input-mini'
@ -49,8 +49,8 @@
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= form.text_field 'deposit', class: 'input-mini', style: 'width: 45px'
%td= form.select :article_category_id, ArticleCategory.all.map {|a| [ a.name, a.id ] },
{include_blank: true}, class: 'input-small'
%td{:style => highlight_new(attrs, :article_category)}
= form.select :article_category_id, ArticleCategory.all.map {|a| [ a.name, a.id ] }, {include_blank: true}
- unless changed_article.errors.empty?
%tr.alert
%td(colspan=11)= changed_article.errors.full_messages.join(', ')

View file

@ -8,10 +8,10 @@
= csrf_meta_tags
= stylesheet_link_tag "application", :media => "all"
//%link(href="images/favicon.ico" rel="shortcut icon")
= yield(:head)
= foodcoop_css_tag
%body
= yield
@ -19,7 +19,9 @@
Javascripts
\==================================================
/ 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
I18n.defaultLocale = "#{I18n.default_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
require 'fileutils'
include FileUtils
require "fileutils"
# path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
chdir APP_ROOT do
# This script is a starting point to setup your application.
FileUtils.chdir APP_ROOT do
# 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.
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# cp 'config/database.yml.sample', 'config/database.yml'
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
system! 'bin/rails db:setup'
system! "bin/rails db:prepare"
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 =="
system! 'bin/rails restart'
system! "bin/rails restart"
end

View file

@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
module Foodsoft
class Application < Rails::Application
# 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.
# 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.
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.
config.active_support.escape_html_entities_in_json = true
@ -66,6 +63,12 @@ module Foodsoft
# Load legacy scripts from vendor
config.assets.precompile += ['vendor/assets/javascripts/*.js']
config.active_record.yaml_column_permitted_classes = [Symbol, BigDecimal]
config.autoloader = :zeitwerk
# Ex:- :default =>''
# CORS for API
config.middleware.insert_before 0, Rack::Cors do
allow do

View file

@ -1,3 +1,5 @@
require "active_support/core_ext/integer/time"
# Foodsoft production configuration.
#
# 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?
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
config.assets.js_compressor = :terser
config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# 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.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# 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
# 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.url = 'wss://example.com/cable'
# 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.
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.
config.log_level = :info
@ -63,6 +67,10 @@ Rails.application.configure do
# Use a different cache store in production.
# 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
# Ignore bad email addresses and do not raise email delivery errors.
@ -98,7 +106,7 @@ Rails.application.configure do
end
# 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.
# require 'syslog/logger'

View file

@ -1,30 +1,31 @@
# Foodsoft test configuration.
#
# This file is in the public domain.
require "active_support/core_ext/integer/time"
# 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
# Settings specified here will take precedence over those in config/application.rb.
# 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!
# Turn false under Spring and add config.action_view.cache_template_loading = true.
config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Eager loading loads your whole application. When running a single test locally,
# this probably isn't necessary. It's a good idea to do in a continuous integration
# system, or in some way before deploying your code.
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
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.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
@ -32,7 +33,7 @@ Rails.application.configure do
# Disable request forgery protection in test environment.
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.action_mailer.perform_caching = false
@ -45,6 +46,15 @@ Rails.application.configure do
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# Raise exceptions for disallowed deprecations.
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

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

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
# have it shown in all other languages too
::I18n.available_locales.each do |locale|
unless locale == ::I18n.default_locale
::I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } })
I18n.available_locales.each do |locale|
unless locale == I18n.default_locale
I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } })
end
end

View file

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

View file

@ -1,4 +1,8 @@
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password]
# Configure parameters to be filtered from the log file. Use this to limit dissemination of
# 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
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
get "order_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
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_02_05_090257) do
create_table "active_storage_attachments", id: :integer, force: :cascade do |t|
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|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_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 ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
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 "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.string "checksum"
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
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 "description"
t.index ["name"], name: "index_article_categories_on_name", unique: true
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.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 "deposit", precision: 8, scale: 2, default: "0.0", null: false
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"
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.integer "supplier_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.string "manufacturer"
t.string "origin"
t.datetime "shared_updated_on"
t.datetime "shared_updated_on", precision: nil
t.decimal "price", precision: 8, scale: 2
t.float "tax"
t.decimal "deposit", precision: 8, scale: 2, default: "0.0"
t.integer "unit_quantity", default: 1, null: false
t.string "order_number"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "deleted_at"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.datetime "deleted_at", precision: nil
t.string "type"
t.integer "quantity", default: 0
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"
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 "task_id", default: 0, null: false
t.boolean "accepted", default: false
t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true
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 "iban"
t.string "description"
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.integer "bank_gateway_id"
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 "url", null: false
t.string "authorization"
t.integer "unattended_user_id"
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.string "external_id"
t.date "date"
@ -108,32 +114,32 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.string "reference"
t.text "text"
t.text "receipt"
t.binary "image", limit: 16777215
t.binary "image", size: :medium
t.integer "financial_link_id"
t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id"
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 "mime"
t.binary "data", limit: 4294967295
t.binary "data", size: :long
t.integer "created_by_user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.integer "parent_id"
t.index ["parent_id"], name: "index_documents_on_parent_id"
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"
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.boolean "ignore_for_account_balance", default: false, null: false
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.integer "financial_transaction_class_id", null: false
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"
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.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false
t.text "note", 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_link_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
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 "quantity", 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"
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 "order_article_id", default: 0, null: false
t.integer "quantity", 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_computed", precision: 8, scale: 3
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"
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 "order_id", default: 0, null: false
t.decimal "price", precision: 8, scale: 2, default: "0.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.decimal "transport", precision: 8, scale: 2
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"
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 "name", default: "", null: false
t.string "description"
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_suppliers", default: false, null: false
t.boolean "role_article_meta", default: false, null: false
t.boolean "role_finance", 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_phone"
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
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.datetime "expires_at", null: false
t.datetime "expires_at", precision: nil, null: false
t.integer "group_id", default: 0, null: false
t.integer "user_id", default: 0, null: false
t.string "email", default: "", null: false
t.index ["token"], name: "index_invites_on_token"
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.string "number"
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 "deposit", 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 "updated_at"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.integer "created_by_user_id"
t.string "attachment_mime"
t.binary "attachment_data", limit: 16777215
t.binary "attachment_data", size: :medium
t.integer "financial_link_id"
t.index ["supplier_id"], name: "index_invoices_on_supplier_id"
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 "url", null: false
t.integer "workgroup_id"
@ -249,81 +255,81 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.string "authorization"
end
create_table "mail_delivery_status", id: :integer, force: :cascade do |t|
t.datetime "created_at"
create_table "mail_delivery_status", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.datetime "created_at", precision: nil
t.string "email", null: false
t.string "message", null: false
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"
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 "user_id", default: 0, null: false
t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true
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 "user_id", 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 ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at"
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.string "subject", null: false
t.text "body"
t.boolean "private", default: false
t.datetime "created_at"
t.datetime "created_at", precision: nil
t.integer "reply_to"
t.integer "group_id"
t.string "salt"
t.binary "received_email", limit: 16777215
t.binary "received_email", size: :medium
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 "application_id", null: false
t.string "token", null: false
t.integer "expires_in", null: false
t.text "redirect_uri", null: false
t.datetime "created_at", null: false
t.datetime "revoked_at"
t.datetime "created_at", precision: nil, null: false
t.datetime "revoked_at", precision: nil
t.string "scopes"
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
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 "application_id"
t.string "token", null: false
t.string "refresh_token"
t.integer "expires_in"
t.datetime "revoked_at"
t.datetime "created_at", null: false
t.datetime "revoked_at", precision: nil
t.datetime "created_at", precision: nil, null: false
t.string "scopes"
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 ["token"], name: "index_oauth_access_tokens_on_token", unique: true
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 "uid", null: false
t.string "secret", null: false
t.text "redirect_uri", null: false
t.string "scopes", default: "", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.boolean "confidential", default: true, null: false
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
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 "article_id", 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"
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 "user_id"
t.text "text"
t.datetime "created_at"
t.datetime "created_at", precision: nil
t.index ["order_id"], name: "index_order_comments_on_order_id"
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.text "note"
t.datetime "starts"
t.datetime "ends"
t.datetime "starts", precision: nil
t.datetime "ends", precision: nil
t.string "state", default: "open"
t.integer "lock_version", default: 0, null: false
t.integer "updated_by_user_id"
t.decimal "foodcoop_result", precision: 8, scale: 2
t.integer "created_by_user_id"
t.datetime "boxfill"
t.datetime "boxfill", precision: nil
t.integer "invoice_id"
t.date "pickup"
t.datetime "last_sent_mail"
t.datetime "last_sent_mail", precision: nil
t.integer "end_action", default: 0, null: false
t.decimal "transport", precision: 8, scale: 2
t.index ["state"], name: "index_orders_on_state"
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 "lock_version"
t.text "body"
t.integer "updated_by"
t.integer "redirect"
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"
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.text "body"
t.string "permalink"
@ -383,41 +389,41 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do
t.integer "updated_by"
t.integer "redirect"
t.integer "parent_id"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.index ["permalink"], name: "index_pages_on_permalink"
t.index ["title"], name: "index_pages_on_title"
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.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
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 "choice", 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
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 "user_id", null: false
t.integer "ordergroup_id"
t.text "note"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "created_at", precision: nil
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
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.string "name", null: false
t.text "description"
t.datetime "starts"
t.datetime "ends"
t.datetime "starts", precision: nil
t.datetime "ends", precision: nil
t.boolean "one_vote_per_ordergroup", default: false, null: false
t.text "required_ordergroup_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 "min_points"
t.integer "max_points"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "created_at", precision: nil
t.datetime "updated_at", precision: nil
t.index ["final_choice"], name: "index_polls_on_final_choice"
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.datetime "created_at", null: false
t.datetime "created_at", precision: nil, null: false
t.string "state", null: false
t.text "message"
t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at"
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.string "document", null: false
t.integer "created_by_user_id", null: false
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"
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.text "value"
t.integer "thing_id"
t.string "thing_type", limit: 30
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", precision: nil, 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
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 "order_id"
t.integer "stock_article_id"
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_event_id"], name: "index_stock_changes_on_stock_event_id"
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.date "date"
t.datetime "created_at"
t.datetime "created_at", precision: nil
t.text "note"
t.integer "invoice_id"
t.string "type", null: false
t.index ["supplier_id"], name: "index_stock_events_on_supplier_id"
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 "description"
t.integer "financial_transaction_class_id"
t.integer "bank_account_id"
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 "address", 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.integer "shared_supplier_id"
t.string "min_order_quantity"
t.datetime "deleted_at"
t.datetime "deleted_at", precision: nil
t.string "shared_sync_method"
t.string "iban"
t.integer "supplier_category_id"
t.index ["name"], name: "index_suppliers_on_name", unique: true
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.text "description"
t.date "due_date"
t.boolean "done", default: false
t.integer "workgroup_id"
t.datetime "created_on", null: false
t.datetime "updated_on", null: false
t.datetime "created_on", precision: nil, null: false
t.datetime "updated_on", precision: nil, null: false
t.integer "required_users", default: 1
t.integer "duration", default: 1
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"
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 "password_hash", 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 "email", default: "", null: false
t.string "phone"
t.datetime "created_on", null: false
t.datetime "created_on", precision: nil, null: false
t.string "reset_password_token"
t.datetime "reset_password_expires"
t.datetime "last_login"
t.datetime "last_activity"
t.datetime "deleted_at"
t.datetime "reset_password_expires", precision: nil
t.datetime "last_login", precision: nil
t.datetime "last_activity", precision: nil
t.datetime "deleted_at", precision: nil
t.string "iban"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["nick"], name: "index_users_on_nick", unique: true
end
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end

View file

@ -8,10 +8,10 @@ def seed_group_orders
# order 3..12 times a random article
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)
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
# 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
we're open for new additions.
The API is documented using [Open API 2.0](https://github.com/OAI/OpenAPI-Specification)
/ [Swagger](https://swagger.io/) in [swagger.v1.yml](swagger.v1.yml).
The API is documented using [Open API 3.0.1](https://github.com/OAI/OpenAPI-Specification)
/ [Swagger](https://swagger.io/) in [swagger.yaml](/swagger/v1/swagger.yaml).
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.

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,10 @@
/ insert_after 'erb:contains("file_field")'
- if FoodsoftArticleImport.enabled?
%label(for="articles_file")
%strong="select the file type you are about to upload"
=f.collection_select :type, FoodsoftArticleImport::FORMATS , :to_s, :to_s
/ insert_before 'erb:contains("articles_outlist_absent")'
-if FoodsoftArticleImport.enabled?
%label(for="articles_update_category")
= f.check_box "update_category"
= t 'Kategorien aus der Datei übernehmen und erstellen.'

View file

@ -0,0 +1,20 @@
if FoodsoftArticleImport.enabled?
ArticlesController.class_eval do
def parse_upload
uploaded_file = params[:articles]['file'] or raise I18n.t('articles.controller.parse_upload.no_file')
type = params[:articles]['type']
options = { filename: uploaded_file.original_filename }
options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
options[:convert_units] = (params[:articles]['convert_units'] == '1')
options[:update_category] = (params[:articles]['update_category'] == '1')
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, type, options
if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice')
end
@ignored_article_count = 0
rescue => error
redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
end
end
end

View file

@ -0,0 +1,39 @@
if FoodsoftArticleImport.enabled?
Article.class_eval do
def unequal_attributes(new_article, options = {})
# try to convert different units when desired
if options[:convert_units] == false
new_price = nil
new_unit_quantity = nil
else
new_price, new_unit_quantity = convert_units(new_article)
end
if new_price && new_unit_quantity
new_unit = self.unit
else
new_price = new_article.price
new_unit_quantity = new_article.unit_quantity
new_unit = new_article.unit
end
attribute_hash = {
:name => [self.name, new_article.name],
:manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
:origin => [self.origin, new_article.origin],
:unit => [self.unit, new_unit],
:price => [self.price.to_f.round(2), new_price.to_f.round(2)],
:tax => [self.tax, new_article.tax],
:deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
# take care of different num-objects.
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
:note => [self.note.to_s, new_article.note.to_s]
}
if options[:update_category] == true
new_article_category = new_article.article_category
attribute_hash[:article_category] = [self.article_category, new_article_category] unless new_article_category.blank?
end
Article.compare_attributes(attribute_hash)
end
end
end

View file

@ -0,0 +1,53 @@
if FoodsoftArticleImport.enabled?
Supplier.class_eval do
# Synchronise articles with spreadsheet.
#
# @param file [File] Spreadsheet file to parse
# @param options [Hash] Options passed to {FoodsoftArticleImport#parse} except when listed here.
# @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet.
# @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
def sync_from_file(file, type, options = {})
all_order_numbers = []
updated_article_pairs, outlisted_articles, new_articles = [], [], []
custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
opts = options.except(:convert_units, :outlist_absent)
custom_codes_file_path = custom_codes_path if File.exist?(custom_codes_path)
FoodsoftArticleImport.parse(file, custom_file_path: custom_codes_file_path, type: type, **opts) do |new_attrs, status, line|
article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
if new_attrs[:article_category].present? && options[:update_category]
new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category]) || ArticleCategory.create_or_find_by!(name: new_attrs[:article_category])
else
new_attrs[:article_category] = nil
end
new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
new_article = articles.build(new_attrs)
if status.nil?
if article.nil?
new_articles << new_article
else
unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units, :update_category))
unless unequal_attributes.empty?
article.attributes = unequal_attributes
updated_article_pairs << [article, unequal_attributes]
end
end
elsif status == :outlisted && article.present?
outlisted_articles << article
# stop when there is a parsing error
elsif status.is_a? String
# @todo move I18n key to model
raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s)
end
all_order_numbers << article.order_number if article
end
if options[:outlist_absent]
outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
end
[updated_article_pairs, outlisted_articles, new_articles]
end
end
end

View file

@ -0,0 +1,20 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "foodsoft_article_import/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "foodsoft_article_import"
s.version = FoodsoftArticleImport::VERSION
s.authors = ["viehlieb"]
s.email = ["foodsoft@local-it.org"]
s.summary = "Manages manual article import from file. File Formats supported are: foodsoft file(csv), bnn files (.bnn) and odin files (xml)"
s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"]
s.add_dependency "rails"
s.add_dependency "deface", "~> 1.0"
s.add_dependency 'roo', '~> 2.9.0'
s.add_development_dependency 'simplecov'
end

View file

@ -0,0 +1,76 @@
# frozen_string_literal: true
require "deface"
require 'foodsoft_article_import/engine'
require 'digest/sha1'
require 'tempfile'
require 'csv'
require 'yaml'
require 'active_support/core_ext/hash/keys'
require_relative 'foodsoft_article_import/bnn'
require_relative 'foodsoft_article_import/odin'
require_relative 'foodsoft_article_import/foodsoft'
module FoodsoftArticleImport
class ConversionFailedException < StandardError; end
FORMATS = %w(bnn foodsoft odin).freeze
def self.enabled?
FoodsoftConfig[:use_article_import]
end
def self.file_formats
@@file_formats ||= {
'bnn' => FoodsoftArticleImport::Bnn,
'foodsoft' => FoodsoftArticleImport::Foodsoft,
'odin' => FoodsoftArticleImport::Odin,
}.freeze
end
# Parse file by type (one of {.file_formats})
#
# @param file [File, Tempfile]
# @option opts [String] type file format (required) (see {.file_formats})
# @return [File, Roo::Spreadsheet] file with encoding set if needed
def self.parse(file, custom_file_path: nil, type: nil, **opts, &blk)
@@filename = opts[:filename] if opts[:filename]
custom_file_path ||= nil
type ||= 'bnn'
parser = file_formats[type]
if block_given?
parser.parse(file, custom_file_path: custom_file_path, &blk)
else
data = []
parser.parse(file, custom_file_path: custom_file_path) { |a| data << a }
data
end
end
# Helper method to generate an article number for suppliers that do not have one
def self.generate_number(article)
# something unique, but not too unique
s = "#{article[:name]}-#{article[:unit_quantity]}x#{article[:unit]}"
s = s.downcase.gsub(/[^a-z0-9.]/, '')
# prefix abbreviated sha1-hash with colon to indicate that it's a generated number
article[:order_number] = ":#{Digest::SHA1.hexdigest(s)[-7..]}"
article
end
# Helper method for opening a spreadsheet file
#
# @param file [File] file to open
# @param filename [String, NilClass] optional filename for guessing the file format
# @param encoding [String, NilClass] optional CSV encoding
# @param col_sep [String, NilClass] optional column separator
# @return [Roo::Spreadsheet]
def self.open_spreadsheet(file, encoding: nil, col_sep: nil, liberal_parsing: nil)
opts = { csv_options: {} }
opts[:csv_options][:encoding] = encoding if encoding
opts[:csv_options][:col_sep] = col_sep if col_sep
opts[:csv_options][:liberal_parsing] = true if liberal_parsing
opts[:extension] = File.extname(File.basename(file)) if file
begin
Roo::Spreadsheet.open(file, **opts)
rescue StandardError => e
raise "Failed to parse foodsoft file. make sure file format is correct: #{e.message}"
end
end
end

View file

@ -0,0 +1,90 @@
# frozen_string_literal: true
# Module for translation and parsing of BNN-files (www.n-bnn.de)
#
module FoodsoftArticleImport
module Bnn
@@codes = {}
@@midgard = {}
# Loads the codes_file config/bnn_codes.yml into the class variable @@codes
def self.load_codes(custom_file_path = nil)
@gem_lib = File.expand_path '..', __dir__
dir = File.join @gem_lib, 'foodsoft_article_import'
begin
@@codes = YAML.safe_load(File.open(File.join(dir, 'bnn_codes.yml'))).symbolize_keys
if custom_file_path
custom_codes = YAML.safe_load(File.open(custom_file_path)).symbolize_keys
custom_codes.each_key do |key|
custom_codes[key] = custom_codes[key].merge @@codes[key] if @@codes.keys.include?(key)
@@codes = @@codes.merge custom_codes
end
end
@@midgard = YAML.safe_load(File.open(File.join(dir, 'midgard_codes.yml'))).symbolize_keys
rescue StandardError => e
raise "Failed to load bnn_codes: #{dir}/{bnn,midgard}_codes.yml: #{e.message}"
end
end
$missing_bnn_codes = []
# translates codes from BNN to foodsoft-code
def self.translate(key, value)
if @@codes[key][value]
@@codes[key][value]
elsif @@midgard[key]
@@midgard[key][value]
elsif !value.nil?
$missing_bnn_codes << value
nil
end
end
NAME = 'BNN (CSV)'
OUTLIST = false
OPTIONS = {
encoding: 'IBM850',
col_sep: ';'
}.freeze
# parses a bnn-file
def self.parse(file, custom_file_path: nil, **opts)
custom_file_path ||= nil
encoding = opts[:encoding] || OPTIONS[:encoding]
col_sep = opts[:col_sep] || OPTIONS[:col_sep]
load_codes(custom_file_path)
CSV.foreach(file, { col_sep: col_sep, encoding: encoding, headers: true }).with_index(1) do |row, i|
# check if the line is empty
unless row[0] == '' || row[0].nil?
article = {
name: row[6],
order_number: row[0],
note: row[7],
manufacturer: translate(:manufacturer, row[10]),
origin: row[12],
article_category: translate(:category, row[16]),
unit: row[23],
price: row[37],
tax: translate(:tax, row[33]),
unit_quantity: row[22]
}
# TODO: Complete deposit list....
article.merge!(deposit: translate(:deposit, row[26])) if translate(:deposit, row[26])
if !row[62].nil?
# consider special prices
article[:note] = "Sonderpreis: #{article[:price]} von #{row[62]} bis #{row[63]}"
yield article, :special, i
# Check now for article status, we only consider outlisted articles right now
# N=neu, A=Änderung, X=ausgelistet, R=Restbestand,
# V=vorübergehend ausgelistet, W=wiedergelistet
elsif row[1] == 'X' || row[1] == 'V'
yield article, :outlisted, i
else
yield article, nil, i
end
end
end
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,129 @@
# from http://www.nieuweband.nl/producten/groepen/
indeling:
1: Verswaren
50: Kaas
62: Schapenkaas
2: Basisproducten
850: Noten
855: Noten grootverbruik
700: Peulvruchten
705: Peulvruchten grootverbruik
340: Rijst
341: Rijst grootverbruik
450: Vlokken
455: Vlokken grootverbruik
800: Zaden en pitten
805: Zaden en pitten grootverbruik
603: Melen grootverbruik
3: Ontbijt en lunch
943: Marmelade
1272: Muesli en poppies
1000: Notenpasta
1276: Ontbijtmelen
1295: Rijstwafels
1290: Roggebrood
1270: Sandwichspread
940: Vruchtenbeleg
942: Vruchtenjam
944: Vruchtenstroop
1300: Knäckebröd, toast en beschuit
4: Warme maaltijd
1820: Mosterd
1610: Olijfolie
1600: Olijven
1451: Peulvruchtenconserven
1957: Pindasaus
1960: Sambal, ketjap en pittige smaakmakers
2170: Seitan
2260: Siropen
2248: Smaakmakers
1500: Soepen en bouillon
1515: Soepstengels
2000: Sojasauzen
2250: Suiker
1452: Tafelzuren
1590: Tamme-kastanje-producten
1975: Thaise keuken
1900: Tomatenproducten
1670: Vetten
1930: Visconserven
2175: Vleesvervangers
1360: Vruchtencompote
1400: Vruchtenconserven
1350: Vruchtenmoes en -puree
2249: Zout en kruidenzout
5: Sappen en dranken
2605: Rode wijn Oostenrijk
2604: Rode wijn Portugal
2602: Rode wijn Spanje
2608: Rode wijn Zuid-Afrika
2612: Rosé Spanje
2617: Rosé Zuid-Afrika
2420: Smoothies
2455: Sojamelkproducten
2505: Speciaalbieren
2400: Vruchtensappen
2490: Waterijs
2637: Witte wijn Argentinië
2630: Witte wijn Frankrijk
2634: Witte wijn Griekenland
2631: Witte wijn Italië
2635: Witte wijn Oostenrijk
2632: Witte wijn Spanje
2638: Witte wijn Zuid-Afrika
6: Warme dranken en theekruiden
3102: Kruidenthee builtjes
3100: Kruidenthee los
3020: Kruidenthee met geneeskrachtige werking
3009: Rooibosthee
3010: Thee grootverpakking
3052: Theekruiden
3008: Witte thee
3011: Yogi spice tea
3012: Yogi tao tea
3000: Zwarte thee
7: Versnaperingen
3552: Lollies
3470: Nougat en fudge
3570: Raw Food
3360: Rozijntjes in kinderverpakking
3410: Snijkoek
3555: Snoep met suiker
3550: Snoep zonder suiker
3405: Stroopwafels
3350: Tortillachips en salsa
3358: Zoete chips
3540: Zoethoutstokjes
3365: Zoutjes, hartige bites en popcorn
3530: Laurierdrop
8: Persoonlijke verzorging en cosmetica
5036: Lavera
5037: Namaste
5040: Natracare
5042: Odylique
5049: Sonett
5055: Urtekram
5065: Weleda
9: Natuurtherapeutisch
5455: Kruidentincturen
5420: Propolis-producten
5245: Zelfzorgmiddelen
5280: Huid- en massage-olie
10: Non Food
5517: Luiers en babydoekjes
5510: Maandverband en tampons
5520: Toiletpapier e.d.
5890: Voor kinderen (en volwassenen)
5650: Was- en schoonmaakmiddelen
5515: Watten
5610: Luchtverfrissers

View file

@ -0,0 +1,12 @@
module FoodsoftArticleImport
class Engine < ::Rails::Engine
config.to_prepare do
Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
require_dependency(c)
end
end
def default_foodsoft_config(cfg)
cfg[:use_article_import] = false
end
end
end

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
# Module for Foodsoft-file import
# The Foodsoft-file is a CSV-file, with semicolon-separated columns, or ODS/XLS/XLSX
require 'roo'
require 'roo-xls'
module FoodsoftArticleImport
module Foodsoft
NAME = 'Foodsoft (CSV, ODS, XLS, XLSX)'
OUTLIST = false
OPTIONS = {
encoding: 'UTF-8',
col_sep: ';'
}.freeze
# Parses Foodsoft file
# the yielded article is a simple hash
def self.parse(file, custom_file_path: nil)
custom_file_path ||= nil
opts = OPTIONS.dup
ss = FoodsoftArticleImport.open_spreadsheet(file, **opts)
header_row = true
ss.sheet(0).each.with_index(1) do |row, i|
# skip first header row
if header_row
header_row = false
next
end
# skip empty lines
if row[2].to_s.strip.empty?
# raise no order number given
yield nil, nil, i
next
end
article = { order_number: row[1],
name: row[2],
note: row[3],
manufacturer: row[4],
origin: row[5],
unit: row[6],
price: row[7],
tax: row[8],
unit_quantity: row[10],
article_category: row[13] }
article.merge!(deposit: row[9]) unless row[9].nil?
FoodsoftArticleImport.generate_number(article) if article[:order_number].to_s.strip.empty?
if row[6].nil? || row[7].nil? || row[8].nil?
yield article, 'Error: unit, price and tax must be entered', i
else
yield article, (row[0] == 'x' ? :outlisted : nil), i
end
end
end
end
end

View file

@ -0,0 +1,294 @@
manufacturer:
"61": Maintal
AB: Agrobioservice
AD: Anita Dehnert
AH: Phyto Treasures e.K.
AO: Arganöl
AR: ARIES
AS: Abraham Schinken
Ad: Molkerei Andechs
An: Frans Andringa
Ap: Apfeltraum
Ar: Provamel über Arche
Ay: Aytem
BA: BauckHof Amelinghausn
BB: Bakenhus Biofleisch GmbH
BC: Bio-Bäckerei Bucco
BD: Biosa
BF: Bruno Fischer
BG: Bauers Garten
BH: Bauck Hof
BHA: Bauck Hof Amelinghausen
BI: Biofarben
BK: Burger Knäcke
BKO: BioKräuterei Oberhavel
BL: Beumer & Lutum
BM: Bohlsener Mühle
BN: Brochenin
BOD: Bode Naturkost
BR: Luchs Bier
BT: Beltane Naturkost GmbH
BU: Baumschule am Butzelberg
BV: BIO VITA
BZ: Biozeit
Ba: Bauck demeter Produkte
Bb: Beutelsbacher
Bd: Biosa Danmark Aps
Be: Behncken
Bf: Backforum
Bg: Butzelberg
Bh: Barnhouse
Bj: Milchschafhof Brünjes
Bk: Blank
Bm: Biomax
Bn: Bentele
Bo: Bobalis
Bt: Bretti's
Bu: Biogärtnerei Bauer
By: Byodo
CA: Care
CF: CLUB Feinkost
CI: CIDRERIE
CV: Cosmoveda
Ca: Campo
Cl: Obsthof Clostermann
Co: Obsthof Cordes (Heinrich)
Cp: Campobello
Cs: Cosmoveda
Ct: cbet GmbH
DA: Danival
DE: DEMETER-Erzeugergemeinschaft
DH: Dieter Hein Wurstwaren
DM: Dr. Martins
DN: Hof Dannwisch
DO: Donath-Mühle
DR: De Rit
DV: Davert
DW: Vovic / Evian
De: Dennree
Dk: Dinkula
EB: Erich Boden
EH: Engemann Handel
EI: Natürlich Eistert
ELM: BIONADE
EN: Provence Regime
EO: Eosta
ER: Euresis
Eb: Eisblümerl
Eh: Erhardt Meerrettichprodukte
Ei: Eiland
El: Kelterei Elm
En: Eichhorn
Er: Erdmannhauser Brezelfabrik
Es: Erntesegen
FB: Flensburger Brauerei
FE: Frucht-Express
FF: Schiffers
FI: Fromi GmbH
FL: Florian Kerzen
FR: I Frutti del Sole
FU: Future 3000
Fh: Florahof
Fq: Fläming-Quelle
Fr: Frunet
Ft: Fontaine
GA: Bio-Gärtnerei Altglobsow
GG: Naturhof Günter Gaßmann
GH: Gutshöfe
GN: Nesse Gewürze
GO: Der Georgshof
GS: Gut Schmerwitz
GT: Gut Temmen
Gb: Grabower
Gl: Glaciar
Go: Golden Temple
Gr: Grützdorfer
Gw: Gwidon Zastawa
: Gärtnerei am Bauerngut
: Stadtgut Görlitz
HA: Haaner Felsenquelle
HB: Hof Bockum
HF: Hühnerhof Falkenthal
HK: Heinz Ketchup
HM: Hof Marienhöhe
HO: Hoffmann
HS: Obstbau H. Schalkau
Ha: Hake
Hc: Hoch Oblatenfabrik
He: Hennicke
Hk: Natur Obsthof Hauke
Hl: Heidehof
Ho: Holle
Hu: Humanopolis
: Hütterman
IC: Japan Grüntee
IN: Isola della Natura
IOC: IOC
IS: Isana
Ib: Iberia
Il: Il Nuraghe
Is: ISANA
JH: Beerenobst
JS: Juers Fruchtchips
Je: Jelitta Käse
KD: Kristdyn
KG: Kräuter Gut
KK: 74271
KN: Öko-Gartenbau
KP: Kräutergarten Pommerland
Ka: Kanne
Kg: Karg Brotgenuß
: Kärrners
: Obsthof König
LB: Lammsbräu
LE: LEEB Schaf- und Ziegenmolkerei
LI: Legend Organics
LM: LeMar
LS: La Selva
La: Lahmann
Lb: Lebensbaum
Le: Leuchtenberg Sauerkrautfabrik
Lh: Lindenhof
Li: Lima Belgien
Lk: Landkrone
Ln: Land in Sicht
Ls: Lubs GmbH
Lu: Luvos Heilerde
Lw: Gärtnerei Löwenzahn
MA: Mack
MB: Mabutake
ME: Martin Evers
MH: Märkische Heide
MI: Martin Ibele
MII: Katal. Olivenöl
ML: Märkisches Landbrot
MM: Bioland Imkerei
MT: Maintal
MV: MegaVega Limited
MY: Mayka Brezel
Ma: Marschland
Mg: MIDGARD
Mh: Melchhof
Mn: Mosna
Mo: Mosaikwerkstätten
My: MAYKA, Brezelfabrik
: Hofmolkerei GmbH Münchehofe
: Märkischer Ökovertrieb
NE: Natürlich Eistert
NM:
NO: Nürnberger Bio Originale
NQ: Pineo Wasser
Na: NATURATA
Nt: Natumi
OTC: OTC
Od: ODIN Holland
PB: Peter Bentele
PG: Pilzgarten
PH: Biopilzhof
PM: Pinkus Müller
PN: Pro Natura
Pi: Piding
Pt: Port International
QB: Panettoncino
RB: Rother Bräu
RP: Rheinsberger Preussenquelle
RS: rosmarin BIOBACK
RZ: Ranch Zempow
Ra: Raab
Rb: Rabenhorst
Re: Rebgarten
Rg: Rosengarten
Rh: Rotenhäusler
Ro: Geflügelhof Robert
RoL: Robert´s LOSE
Rt: Rottstock
: Römerquelle
SB: Sabines Bauernhof
SBP: Stiftelsen Bananen
SC: Sommer & Co.
SF: Sprossen
SH: Spreewälder Hirse
SI: SINFO
SK: Spargelhof Kreienbaum
SL: St. Leonhardsquelle
SM: Seenlan Müritz
SO: Sonett
SR: Sprossenmanufaktur GbR
STN: Sonnentor
SV: SANTAVERDE ALOE VERA
Sa: Salamita
Sb: Hans Hermann Soetbeer
Sc: Schulz-Deetz
Sch: Hof Schütte
Sd: Savid
Se: Sekem, Ägypten
Sf: Sauerkonservenfabrik Schweizer
Sh: Kombucha
Si: Land in Sicht
Sk: Schock Ludwigsburg
Sm: Schramm
So: Sophienhof
Sp: Spielberger
Sr: Sanmar
St: Steck Senf
StB: Stralsunder Brauerei
Su: Sun,Backwaren aus Norwegen
Sv: Sunval demeter-Produkte
Sw: Szilleweit
Sy: Synanon
Sz: Schrozberg
: Südasien
TB: Team Blue
TF: Terra Frischdienst
TN: Tofu Nagel
TR: Teltower Rübchen
Ta: Tarpa
Te: Teutoburger Ölmühle
Ti: Tiedemann
Tm: Tillmann
Tr: tri d´Aix
Tt: Tautropfen
: Töpfer Rohrzucker
UK: Udo Kolm Bananen
UL: Gärtnerei Ulenburg
UV: Uni-Vert
Ul: Ulenburg Bioland Gemüse
VA: Kleingenossenschaft VENUSTA
VD: V & D
VE: Vega e.K.
VG: Biolog. Vollwertgetränke
VT: Vogt
VV: Vallé Käse
Vi: Viana Tofu
VlV: Vivo Lo Vin
Vo: Voelkel
WB: Weber
WD: Werder Feinkost GmbH
WH: Weide-Hardebek
WK: BioCompany Kaffee
WL: Wendland Storchenmilch
WP: Plosewasser
WR: Speickwerke
WS: Weingut Sander
Wa: Watzkendorf
We: Wendts
Wh: Molkerei Weißenhorn
Wz: Werz Heidenheim
ZA: Bio-Center Zann
ZF: Obsthof zum Felde
ZG: Zwergenwiese
ZI: Biolandhof Zielke
ZK: Ziegenkäserei Karolinenhof
ZP: Bioland Ranch Zempow
ZW: Zellertaler Wein
bF: bio Frische
bi: biosanica
dB: ÖMA-d`Beers, Kisslegg im Algäu
eu: felicia
fa: familia Müsli
ha: Hawlik
vL: v.d.Linden
öG: Öko-Gartenbau
öh: ökohum Blumenerde
ÖL: Öko-Line
ÖS: Ölmühle Solling

View file

@ -0,0 +1,85 @@
# frozen_string_literal: true
# Article import for De Nieuw Band XML file
#
# Always contains full assortment, including recently outlisted articles.
# To make sure we don't keep old articles when a number of updates was missed,
# +OUTLIST+ is set to +true+ to remove articles not present in the file.
#
require 'nokogiri'
module FoodsoftArticleImport
class Odin
NAME = 'De Nieuwe Band (XML)'
OUTLIST = true
OPTIONS = {}.freeze
# parses a string or file
def self.parse(file, custom_file_path: nil, **_opts)
custom_file_path ||= nil
xml = File.open(file)
doc = Nokogiri.XML(xml, nil, nil,
Nokogiri::XML::ParseOptions::RECOVER +
Nokogiri::XML::ParseOptions::NONET +
Nokogiri::XML::ParseOptions::COMPACT) # do not modify doc!
load_codes(custom_file_path)
doc.search('product').each.with_index(1) do |row, i|
# create a new article
unit = row.search('eenheid').text
unit = case unit.strip
when '' then 'st'
when 'stuk' then 'st'
when 'g' then 'gr' # need at least 2 chars
when 'l' then 'ltr'
else unit
end
inhoud = row.search('inhoud').text
inhoud.to_s.strip.empty? or (inhoud.to_f - 1).abs > 1e-3 and unit = inhoud.gsub(/\.0+\s*$/, '') + unit
deposit = row.search('statiegeld').text
deposit.to_s.strip.empty? and deposit = 0
category = [
@@codes[:indeling][row.search('indeling').text.to_i],
@@codes[:indeling][row.search('subindeling').text.to_i]
].compact.join(' - ')
status = row.search('status').text == 'Actief' ? nil : :outlisted
article = {}
unless row.search('bestelnummer').text == ''
article = { order_number: row.search('bestelnummer').text,
# :ean => row.search('eancode').text,
name: row.search('omschrijving').text,
note: row.search('kwaliteit').text,
manufacturer: row.search('merk').text,
origin: row.search('herkomst').text,
unit: unit,
price: row.search('prijs inkoopprijs').text,
unit_quantity: row.search('sve').text,
tax: row.search('btw').text,
deposit: deposit,
article_category: category }
end
yield article, status, i
end
end
@@codes = {}
def self.load_codes(custom_file_path = nil)
@gem_lib = File.expand_path '..', __dir__
dir = File.join @gem_lib, 'foodsoft_article_import'
begin
@@codes = YAML.safe_load(File.open(File.join(dir, 'dnb_codes.yml'))).symbolize_keys
if custom_file_path
custom_codes = YAML.safe_load(File.open(custom_file_path)).symbolize_keys
custom_codes.each_key do |key|
custom_codes[key] = custom_codes[key].merge @@codes[key] if @@codes.keys.include?(key)
@@codes = @@codes.merge custom_codes
end
end
@@codes
rescue StandardError => e
raise "Failed to load dnb_codes: #{dir}/dnb_codes.yml: #{e.message}"
end
end
end
end

View file

@ -0,0 +1,3 @@
module FoodsoftArticleImport
VERSION = "0.0.1"
end

View file

@ -0,0 +1,34 @@
# Minimal Foodsoft configuration
#
# Without those settings, Foodsoft may not even work.
# This file is used when running tests. When plugins would modify foodsoft behaviour
# and they are enabled in the sample configuration, there is stable base to test with.
default: &defaults
multi_coop_install: false
use_self_service: true
default_scope: 'f'
name: FC Minimal
# true by default to keep compat with older installations, but test with false here
use_nick: false
use_article_import: true
price_markup: 5
# do we really need the following ones?
tax_default: 6.0
email_sender: noreply@minimal.test
host: localhost
development:
<<: *defaults
test:
<<: *defaults
production:
<<: *defaults

View file

@ -0,0 +1,3 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
64721;A;;;4280001958081;4280001958203;Greek Dressing - Kräuter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-ÖKO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
;;99

View file

@ -0,0 +1,3 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
64721;X;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
;;99

View file

@ -0,0 +1,3 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;4000;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
;;99

View file

@ -0,0 +1,3 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;20230101;20230201;;Kg;28,571;;
;;99

View file

@ -0,0 +1,3 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;HDE;;GR;C%;DE-™KO-001;120;1100;10;55;;1;6 x35g;6;35g;1;N;;99260;;1,41;;;;1;;;4,49;2,89;J;;;;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
;;99

View file

@ -0,0 +1,3 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;HDE;;GR;C%;DE-™KO-001;120;1100;10;55;;1;6 x35g;6;35g;1;N;;99260;;1,41;;;;1;;;4,49;2,89;J;;;;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
;;99

View file

@ -0,0 +1,7 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
5;;;;4280001958081;4280001958203;Žpfel Elstar;erntefrisch und knackig;;;obb;;D;C%;DE-?KO-001;120;0301;10;55;;1;10 x1kg;10;1kg;1;N;;;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;1;;
6;;;;4280001958081;4280001958203;Brokkoli;gesund und lecker;;;fig;;IT;C%;DE-?KO-001;120;03;10;55;;1;6 x400g;6;400g;1;N;;;;1,41;;;;1;;;4,49;2,99;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2,5;;
7;;;;4280001958081;4280001958203;Tomaten;pomodori italiani, demeter;;;TDP;;IT;C%;DE-?KO-001;120;03;10;55;;1;20 x500g;20;500g;1;N;;;;1,41;;;;1;;;4,49;3,19;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;;
8;;;;4280001958081;4280001958203;Reis;Reis im Vorratssack, demeter;;;FIN;;D;C%;DE-?KO-001;120;05;10;55;;1;12 x3k;12;3kg;1;N;;;;1,41;;;;1;;;4,49;3,49;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;0,3;;
9;;;;4280001958081;4280001958203;Spaghetti;100% italienisches Hartweizengrie?;;;ZLN;;D;C%;DE-?KO-001;120;06;10;55;;1;4 x500g;4;500g;1;N;;;;1,41;;;;1;;;4,49;2,99;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;;
10;;;;4280001958081;4280001958203;Kartoffeln;vorwiegend festkochend;;;rsh;;D;C%;DE-?KO-001;120;0311;10;55;;1;6 x5Kg;6;5Kg;1;N;;;;1,41;;;;1;;;4,49;3,00;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;0.2;;

View file

@ -0,0 +1,8 @@
# BNN Codes
category:
"4000": "Schuhe"
additional:
"additional": "value"
indeling:
11: Test Indeling
111: Test Subindeling

View file

@ -0,0 +1,3 @@
status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
;1;product;bio;someone;eu;1 kg;1.23;6;0;10;;;coolstuff
;12;other product;bio;someone;eu;2 kg;3.45;6;0;10;;;coolstuff
1 status number name note manufacturer origin unit clear price tax deposit unit quantity scale quantity scale price category
2 1 product bio someone eu 1 kg 1.23 6 0 10 coolstuff
3 12 other product bio someone eu 2 kg 3.45 6 0 10 coolstuff

View file

@ -0,0 +1,3 @@
status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
;;product;bio;someone;eu;1 kg;1.23;6;0;10;;;coolstuff
;;other product;bio;someone;eu;2 kg;3.45;6;0;10;;;coolstuff
1 status number name note manufacturer origin unit clear price tax deposit unit quantity scale quantity scale price category
2 product bio someone eu 1 kg 1.23 6 0 10 coolstuff
3 other product bio someone eu 2 kg 3.45 6 0 10 coolstuff

View file

@ -0,0 +1,2 @@
status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
;12;product;bio;;eu;1 kg;1.23;;0;10;;;coolstuff
1 status number name note manufacturer origin unit clear price tax deposit unit quantity scale quantity scale price category
2 12 product bio eu 1 kg 1.23 0 10 coolstuff

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmlproduct>
<leverancierkop>
<leveranciersnummer>1039</leveranciersnummer>
<versienummer>1.08</versienummer>
<naam>Estafette Associatie C.V.</naam>
<plaats>Geldermalsen</plaats>
</leverancierkop>
<product>
<eancode>8719325207668</eancode>
<omschrijving>nucli rose</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>750</inhoud>
<eenheid>g</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk>NELEMAN</merk>
<kwaliteit>Biologisch</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>ES</herkomst>
<herkomstregio></herkomstregio>
<btw>21</btw>
<cblcode>1017515</cblcode>
<bestelnummer>0109</bestelnummer>
<sve>6</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>4.52</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
</xmlproduct>

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmlproduct>
<leverancierkop>
<leveranciersnummer>1039</leveranciersnummer>
<versienummer>1.08</versienummer>
<naam>Estafette Associatie C.V.</naam>
<plaats>Geldermalsen</plaats>
</leverancierkop>
<product>
<eancode>8719325207668</eancode>
<omschrijving>nucli rose</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>750</inhoud>
<eenheid>g</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk>NELEMAN</merk>
<kwaliteit>Biologisch</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>ES</herkomst>
<herkomstregio></herkomstregio>
<btw>21</btw>
<cblcode>1017515</cblcode>
<bestelnummer>0109</bestelnummer>
<indeling>11</indeling>
<subindeling>111</subindeling>
<sve>6</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>4.52</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
</xmlproduct>

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmlproduct>
<leverancierkop>
<leveranciersnummer>1039</leveranciersnummer>
<versienummer>1.08</versienummer>
<naam>Estafette Associatie C.V.</naam>
<plaats>Geldermalsen</plaats>
</leverancierkop>
<product>
<eancode>8719325207668</eancode>
<omschrijving>nucli rose</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>750</inhoud>
<eenheid></eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk></merk>
<kwaliteit>Biologisch</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>ES</herkomst>
<herkomstregio></herkomstregio>
<btw>21</btw>
<cblcode>1017515</cblcode>
<bestelnummer>0109</bestelnummer>
<sve>6</sve>
<status>Non Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>4.52</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
</xmlproduct>

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmlproduct>
<leverancierkop>
<leveranciersnummer>1039</leveranciersnummer>
<versienummer>1.08</versienummer>
<naam>Estafette Associatie C.V.</naam>
<plaats>Geldermalsen</plaats>
</leverancierkop>
<product>
<eancode>8719325207668</eancode>
<omschrijving>nucli rose</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>750</inhoud>
<eenheid>g</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk>NELEMAN</merk>
<kwaliteit>Biologisch</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>ES</herkomst>
<herkomstregio></herkomstregio>
<btw>21</btw>
<cblcode>1017515</cblcode>
<bestelnummer></bestelnummer>
<sve>6</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>4.52</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
</xmlproduct>

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