Compare commits

...

35 commits

Author SHA1 Message Date
Philipp Rothmann
eb6cf00f94 update README 2023-08-23 15:19:03 +02:00
c76c148f99 update readme
Some checks failed
continuous-integration/drone/push Build is failing
2023-06-13 11:35:13 +02:00
FGU
ce7b4d7ce4 feat: add price per base unit
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-24 18:56:21 +01:00
Philipp Rothmann
eb719057c4 use demo seeds by default 2023-02-24 18:55:12 +01:00
Philipp Rothmann
2614f095cb update drone 2023-02-24 18:55:12 +01:00
b94ca21022 demo seeds and bnn file in cp850 format 2023-02-24 18:55:12 +01:00
Philipp Rothmann
8cb86b2f88 update readme 2023-02-24 18:55:12 +01:00
3d71d266e3 add bnn for demo day 2023-02-24 18:55:12 +01:00
FGU
ee03a2a9af wip on demos seeds 2023-02-24 18:55:12 +01:00
Philipp Rothmann
237ef5d38b fix: article_spec login twice to fix flaky test 2023-02-24 18:55:12 +01:00
Philipp Rothmann
dfe8beae2c fix: article category remove option from list 2023-02-24 18:55:12 +01:00
Philipp Rothmann
75bb400d0d feat: improve usability of group order
remove group order panel close buttons

things shouldn't just disapear

order article disabled button should be gray

roup order swap plus and minus buttons

because it's more naturally intuitive like this

group order make order details collapse

group order pull search to the right

group order make 'current orders' more obvious

fix switch menu

rework group order edit form

* make switch order a menu list
* table more slim
* alert when balance negative instead of making everything red
* search to the right

wip: rework group order details

tried to reduce the amount of informations shown. but needs some user feedback, what information are actually relevant

rework group order show view

dashboard make show edit current order action more precise

group order package indication uses  more color-blind friendly color

group order fix dancing quantity buttons

group order switch menu use show view

group order show window with some explanations

group order edit title more clear

grou order edit show less infos

group order switch view next iteration

grou order index narrower tables

move order details to show again

remove unused stuff
2023-02-24 18:55:11 +01:00
6f2a3b4f5f fix behavior - when link is provided in article details not clickable due to hover property
solve hover problem for ordering articles
2023-02-24 18:54:33 +01:00
d81ae10dc8 feat(order): export order to custom csv file
add custom_csv_collection to orders helper

add rute and controller method to orders controller

add custom csv to download dropdown

add functionality to choose column headers + order for custom csv and append order.sum gross&net to custom csv
2023-02-24 18:54:33 +01:00
4b5775e107 include foodsoft-article-import
use filetypes for manual uploading bnn, odin, foodsoft file

use opts in .parse

adapt specs to include file format

add specs for odin, bnn, foodsoft files

adapt localize input to remove ',' separator and replace with '.'

remove depr foodsoftfile.rb and spreadsheet.rb

remove todo
2023-02-24 18:54:33 +01:00
Philipp Rothmann
936c1ba878 fix: give docker user storge directory permissions for fileupload 2023-02-24 18:54:33 +01:00
Philipp Rothmann
b3571515b0 fix: set RAILS_SERVE_STATIC_FILES for deployment 2023-02-24 18:54:33 +01:00
Philipp Rothmann
28c851823a fix: assets precompile by using terser
importmaps broke precompiliation with uglifier
see: https://github.com/rails/importmap-rails/issues/5
2023-02-24 18:54:33 +01:00
Philipp Rothmann
25d4efa71a fix(messages): migrate message bodys to richtext 2023-02-24 18:54:33 +01:00
Philipp Rothmann
49a04b226c feat(messages): add html formatting to messages
This commit allows users to use the trix editor to send
messages with basic formatting and attachements.

* add active storage
* add actiontext
* add richtext field to messages
* add imageprocessing for message attachements
* add html email layout and adjust translations to use html urls
2023-02-24 18:54:33 +01:00
Philipp Rothmann
69c80eba3e feat(finance): show sum of ordergroup balances 2023-02-24 18:54:32 +01:00
Philipp Rothmann
e6e2cdc2c6 add: drone ci
fix: ci

fix: .drone docker rails version

add .drone caching

fix drone ci
2023-02-24 18:54:32 +01:00
4bb724495d downgrade haml to make deface work 2023-02-24 18:54:32 +01:00
0bd04fba41 move BigDecimal.new to BigDecimal() 2023-02-24 18:54:32 +01:00
46e3794a4e change .search to .ransack for updated ransack gem 2023-02-24 18:54:32 +01:00
5c04a43f61 update article category implemented
adapt tests

add translations

adapt test

fix bug
2023-02-24 18:54:32 +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
177 changed files with 5479 additions and 2971 deletions

145
.drone.yml Normal file
View file

@ -0,0 +1,145 @@
kind: pipeline
type: docker
name: build and test
steps:
- name: rubocop
image: circleci/ruby:2.7-bullseye-node-browsers-legacy
commands:
- sudo apt install --no-install-recommends -y libmagic-dev
- sudo -E bundle install
- sudo -E bundle exec rubocop
volumes:
- name: gem-cache
path: /bundle
- name: tmp
path: /drone/src/tmp
failure: ignore
- name: build_test
image: circleci/ruby:2.7-bullseye-node-browsers-legacy
commands:
- sudo apt install --no-install-recommends -y libmagic-dev
- echo 'Wait for db container'; sleep 30
- bundle config set path '/bundle'
- bundle config set without 'production'
- sudo -E bundle install
- sudo -E bundle exec rake foodsoft:setup_development_docker || true
- sudo -E bundle exec rake rspec-rerun:spec
volumes:
- name: gem-cache
path: /bundle
- name: tmp
path: /drone/src/tmp
environment:
RAILS_LOG_TO_STDOUT: true
RAILS_ENV: test
COVERAGE: lcov
DATABASE_URL: mysql2://user:password@mariadb/test?encoding=utf8mb4
DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: true
PARALLEL_TEST_PROCESSORS: 60
services:
- name: mariadb
image: mariadb
environment:
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_DATABASE: test
MYSQL_ROOT_PASSWORD: password
volumes:
- name: gem-cache
host:
path: /tmp/cache
- name: tmp
temp: {}
---
kind: pipeline
type: docker
name: docker build and deploy
steps:
- name: build and publish docker image
image: plugins/docker
settings:
registry: git.local-it.org
repo: git.local-it.org/foodsoft/foodsoft
username: philipp
password:
from_secret: docker_registry
tags:
- latest
- ${DRONE_BRANCH}
- ${DRONE_COMMIT:0:8}
cache_from:
- "git.local-it.org/foodsoft/foodsoft:latest"
- "git.local-it.org/foodsoft/foodsoft:${DRONE_BRANCH}"
- name: deployment
image: git.local-it.org/philipp/stack-ssh-deply:latest
settings:
stack: "foodsoft_${DRONE_BRANCH}"
compose: "deployment/compose.yml"
deploy_key:
from_secret: drone_deploy_key
host: "dev.local-it.cloud"
user: "root"
port: 22
reg_user: philipp
reg_pass:
from_secret: docker_registry
reg_url: git.local-it.org
image: git.local-it.org/foodsoft/foodsoft:${DRONE_COMMIT:0:8}
generate_secrets: true
networks:
- proxy
environment:
IMAGE: git.local-it.org/foodsoft/foodsoft:${DRONE_COMMIT:0:8}
STACK_NAME: "foodsoft_${DRONE_BRANCH}"
DOMAIN: "foodsoft.dev.local-it.cloud"
LETS_ENCRYPT_ENV: production
FOODCOOP_MULTI_INSTALL: true
FOODCOOP_NAME: Einkaufskooperative Foobar
FOODCOOP_CITY: Berlin
FOODCOOP_COUNTRY: Deutschland
FOODCOOP_EMAIL: foodsoft@local-it.org
FOODCOOP_PHONE: 123456789
FOODCOOP_STREET: Einkaufsstraße 5
FOODCOOP_ZIP_CODE: 12345
FOODCOOP_HOMEPAGE: https://foodsoft.local-it.org
FOODCOOP_HELP_URL: https://git.local-it.org/foodsoft/foodsoft
FOODCOOP_TIME_ZONE: Berlin
FOODCOOP_USE_NICK: true
FOODCOOP_LANGUAGE: de
FOODCOOP_FOOTER: '<a href="https://foodsoft.local-it.org/">Foodsoft</a> hosted by <a href="https://local-it.org">local-it e,V,</a>.'
USE_APPLE_POINTS: false
STOP_ORDERING_UNDER: 75
MINIMUM_BALANCE: 0
MYSQL_DB: foodsoft
MYSQL_HOST: db
MYSQL_PORT: 3306
MYSQL_USER: foodsoft
EMAIL_SENDER: demo@local-it.org
EMAIL_ERROR: flip@yksflip.de
SMTP_ADDRESS: mail.local-it.org
SMTP_AUTHENTICATION: login
SMTP_DOMAIN: mail.local-it.org
SMTP_ENABLE_STARTTLS_AUTO: true
SMTP_PORT: 587
SMTP_USER_NAME: demo@local-it.org
EMAIL_REPLY_DOMAIN:
SMTP_SERVER_HOST: 0.0.0.0
SMTP_SERVER_PORT: 2525
SECRET_DB_PASSWORD_VERSION: v1
SECRET_DB_ROOT_PASSWORD_VERSION: v1
SECRET_SHARED_LISTS_DB_PASSWORD_VERSION: v1
SECRET_SMTP_PASSWORD_VERSION: v1
SECRET_SECRET_KEY_BASE_VERSION: v1
APP_CONFIG_VERSION: v1
DB_CONFIG_VERSION: v1
ENTRYPOINT_VERSION: v1
PRODUCTION_ENV_VERSION: v1
trigger:
branch:
- demo

View file

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

View file

@ -1 +1 @@
2.6.9 2.7.2

View file

@ -1,4 +1,4 @@
FROM ruby:2.6 FROM ruby:2.7
RUN supercronicUrl=https://github.com/aptible/supercronic/releases/download/v0.1.3/supercronic-linux-amd64 && \ RUN supercronicUrl=https://github.com/aptible/supercronic/releases/download/v0.1.3/supercronic-linux-amd64 && \
supercronicBin=/usr/local/bin/supercronic && \ supercronicBin=/usr/local/bin/supercronic && \
@ -15,13 +15,16 @@ ENV PORT=3000 \
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY . ./ COPY Gemfile Gemfile.lock ./
COPY plugins/ ./plugins
COPY config/ ./config
# install dependencies and generate crontab # install dependencies and generate crontab
RUN buildDeps='libmagic-dev' && \ RUN buildDeps='libmagic-dev' && \
apt-get update && \ apt-get update && \
apt-get install --no-install-recommends -y $buildDeps && \ apt-get install --no-install-recommends -y $buildDeps && \
echo 'gem: --no-document' >> ~/.gemrc && \ echo 'gem: --no-document' >> ~/.gemrc && \
gem install bundler && \
bundle config build.nokogiri "--use-system-libraries" && \ bundle config build.nokogiri "--use-system-libraries" && \
bundle install --deployment --without development test -j 4 && \ bundle install --deployment --without development test -j 4 && \
apt-get purge -y --auto-remove $buildDeps && \ apt-get purge -y --auto-remove $buildDeps && \
@ -29,6 +32,8 @@ RUN buildDeps='libmagic-dev' && \
\ \
bundle exec whenever >crontab bundle exec whenever >crontab
COPY . ./
# compile assets with temporary mysql server # compile assets with temporary mysql server
RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \ RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
export SECRET_KEY_BASE=thisisnotimportantnow && \ export SECRET_KEY_BASE=thisisnotimportantnow && \
@ -48,9 +53,10 @@ RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
rm -Rf /var/lib/apt/lists/* /var/cache/apt/* rm -Rf /var/lib/apt/lists/* /var/cache/apt/*
# Make relevant dirs and files writable for app user # Make relevant dirs and files writable for app user
RUN mkdir -p tmp && \ RUN mkdir -p tmp storage && \
chown nobody config/app_config.yml && \ chown nobody config/app_config.yml && \
chown nobody tmp chown nobody tmp && \
chown nobody storage
# Run app as unprivileged user # Run app as unprivileged user
USER nobody USER nobody

View file

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

25
Gemfile
View file

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

View file

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

161
README.md
View file

@ -1,65 +1,124 @@
Foodsoft Foodsoft
========= =========
[![Build Status](https://github.com/foodcoops/foodsoft/workflows/Ruby/badge.svg)](https://github.com/foodcoops/foodsoft/actions)
[![Coverage Status](https://coveralls.io/repos/foodcoops/foodsoft/badge.svg?branch=master)](https://coveralls.io/r/foodcoops/foodsoft?branch=master)
[![Docs Status](https://inch-ci.org/github/foodcoops/foodsoft.svg?branch=master)](http://inch-ci.org/github/foodcoops/foodsoft)
[![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.svg)](https://codeclimate.com/github/foodcoops/foodsoft)
[![Docker Status](https://img.shields.io/docker/cloud/build/foodcoops/foodsoft.svg)](https://hub.docker.com/r/foodcoops/foodsoft)
[![Documentation](https://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/foodcoops/foodsoft)
Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling). [Website](https://foodsoft.local-it.org)
[Prototypefund](https://prototypefund.de/project/weiterentwicklung-von-foodsoft/)
A food cooperative is a group of people that buy food from suppliers of their own choosing. A collective do-it-yourself supermarket. Members order their products online and collect them on a specified day. And all put in a bit of work to make that possible. Foodsoft facilitates the process.
If you're a food coop considering to use foodsoft, please have a look at the [wiki page for foodcoops](https://github.com/foodcoops/foodsoft/wiki/For-foodcoops). When you'd like to experiment with or develop foodsoft, you can read [how to set it up](https://github.com/foodcoops/foodsoft/blob/master/doc/SETUP_DEVELOPMENT.md) on your own computer.
More information about using this software and contributing can be found on the [wiki](https://github.com/foodcoops/foodsoft/wiki).
Developing Foodsoft ist ein Tool für [Lebensmittelkooperativen](https://de.wikipedia.org/wiki/Lebensmittelkooperative), welches selbstorganisierte gemeinsame Bestellungen in Großmengen von regionalen und ökologischen Produkten vereinfacht und transparent gestaltet.
----------
Get foodsoft [running locally](doc/SETUP_DEVELOPMENT.md), Foodsoft wurde ursprünglich entwickelt und betrieben von [foodcoops.net](https://foodcoops.net/)
then visit our [Developing Guidelines](https://github.com/foodcoops/foodsoft/wiki/Developing-Guidelines)
page on the wiki.
Get a foodsoft dev-environment running in the browser with Gitpod
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/foodcoops/foodsoft)
Follow these [instructions](doc/SETUP_DEVELOPMENT_GITPOD.md) to complete setup from within the Gitpod workspace.
Deploying
---------
Setup foodsoft to [run in production](doc/SETUP_PRODUCTION.md), or join an existing
[hosting platform](https://foodcoops.net/foodsoft-hosting/).
License #### Zielgruppe
-------
Foodsoft is licensed under the [AGPL](https://www.gnu.org/licenses/agpl-3.0.html) Unsere Zielgruppen sind Bürger:innen, Gruppen und Vereine, die eine Einkauskooperative aufbauen wollen und eine Software, die die Bestellung, Verteilung und Abrechnung erleichtert, benötigen.
license (version 3 or later). Practically this means that you are free to use,
adapt and redistribute the software, as long as you publish any changes you
make to the code.
For private use, there are no restrictions, but if you give others access to #### Vorhaben
Foodsoft (like running it open to the internet), you must also make your
changes available under the same license. This can be as easy as
[forking](https://github.com/foodcoops/foodsoft/fork) the project on Github and
pushing your changes. You are not required to integrate your changes back into
the main Foodsoft version (but if you're up for it that would be very welcome).
To make it a little easier, configuration files are exempt, so you can just * ✅ Technische Schuld reduzieren
install and configure Foodsoft without having to publish your changes. These * ✅ Ruby on Rails Upgrade
files are marked as public domain in the file header. * ✅ Artikel Import verbessern
(Großhandelschnitstelle)
* ✅ Userexperience Verbessern
#### Was ist eine Einkaufskooperative?
![Wie funktioniert eine Einkauskooperative?](./doc/foodcoop-explained.jpg)
State of this Fork
------------------
#### Increase Test Coverage
1. integration and model tests
* [x] fork
* [x] upstream [#966](https://github.com/foodcoops/foodsoft/pull/966)
1. Controller tests
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/8_increase_test_coverage_controllers)
* [ ] upstream [#970](https://github.com/foodcoops/foodsoft/pull/970)
#### Upgrade
1. Migrate to RSwag API Tests
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/28_introduce_rswag)
* [x] upstream [#969](https://github.com/foodcoops/foodsoft/pull/969)
1. Rails v7
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/9_rails_v_7)
* [x] upstream [#979](https://github.com/foodcoops/foodsoft/pull/979)
disussion [#956](https://github.com/foodcoops/foodsoft/issues/956)
1. Javascript Importmap
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/9_rails_v_7_js_importmap)
* [x] upstream
#### Article Order Import/Export
Updating Articles from large resellers and exporting orders is now much easier!
1. adds bnn fileformat that is used from large german resellers e.g. naturkost nord
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/11_bnn_import_article_update)
[gem](https://git.local-it.org/Foodsoft/foodsoft_article_import)
* [ ] upstream
1. Import category field
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/56_add_update_of_article_category_to_file_import)
* [ ] upstream
1. Export order as a custom csv file
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/12_generate_custom_csv_file)
* [ ] upstream
1. Naturkostnord Plugin
* [ ] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/12_nkn_file_plugin)
* [ ] upstream
#### Improve User Experience
1. Richtext editor for messages. Also allows sending attachements.
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/16_html_message_templates)
* [x] upstream
1. Show the sum of all order group balances
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/47_finance_ordergroup_sums)
* [x] upstream
1. UI improvements for group order view
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/uxui_group_order)
* [ ] upstream
1. Favorites
* [ ] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/20_favourites)
* [ ] upstream
1. Show the per kilo / litre price
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/11_include_kilo_litre_price)
* [ ] upstream
#### Other
1. Fix broken plugin mechanism
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/downgrade-haml)
* [x] upstream
#### Screenshots
![rswag](./doc/screenshots/rswag.png)
---
![bnn upload](./doc/screenshots/bnn_upload.png)
---
![message formatting](./doc/screenshots/message_formatting.png)
---
![balance sum](./doc/screenshots/balance_sum.png)
---
![custom csv export](./doc/screenshots/custom_csv_export.png)
csv export
---
![order](./doc/screenshots/order.png)
If you have any remaining questions, please
[open an issue](https://github.com/foodcoops/foodsoft/issues/new) or open a new
topic at the [forum](https://forum.foodcoops.net).
Please see [LICENSE](LICENSE.md) for the full and authoritative text. Some
bundled third-party components have [other licenses](vendor/README.md).
Thanks to [Icons8](http://icons8.com/) for letting us use their icons.

View file

@ -179,17 +179,13 @@ function updateBalance() {
var balance = groupBalance - total; var balance = groupBalance - total;
$('#new_balance').html(I18n.l("currency", balance)); $('#new_balance').html(I18n.l("currency", balance));
$('#total_balance').val(I18n.l("currency", balance)); $('#total_balance').val(I18n.l("currency", balance));
// determine bgcolor and submit button state according to balance
var bgcolor = '';
if (balance < minimumBalance) { if (balance < minimumBalance) {
bgcolor = '#FF0000';
$('#submit_button').attr('disabled', 'disabled') $('#submit_button').attr('disabled', 'disabled')
$('#balance-alert').css('display', 'block')
} else { } else {
$('#submit_button').removeAttr('disabled') $('#submit_button').removeAttr('disabled')
} $('#balance-alert').css('display', 'none')
// update bgcolor
for (i in itemTotal) {
$('#td_price_' + i).css('background-color', bgcolor);
} }
} }

View file

@ -0,0 +1,31 @@
/*
* Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
* the trix-editor content (whether displayed or under editing). Feel free to incorporate this
* inclusion directly in any other asset bundle and remove this file.
*
*= require trix
*/
/*
* We need to override trix.csss image gallery styles to accommodate the
* <action-text-attachment> element we wrap around attachments. Otherwise,
* images in galleries will be squished by the max-width: 33%; rule.
*/
.trix-content .attachment-gallery > action-text-attachment,
.trix-content .attachment-gallery > .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%;
}
.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
flex-basis: 50%;
max-width: 50%;
}
.trix-content action-text-attachment .attachment {
padding: 0 !important;
max-width: 100% !important;
}

View file

@ -7,4 +7,5 @@
*= require list.unlist *= require list.unlist
*= require list.missing *= require list.missing
*= require recurring_select *= require recurring_select
*= require actiontext
*/ */

View file

@ -230,7 +230,7 @@ table {
margin: .5em 0; margin: .5em 0;
input:disabled { input:disabled {
background-color: red; } background-color: gray; }
} }
} }
} }
@ -241,6 +241,9 @@ table {
tr.order-article:hover .article-info { tr.order-article:hover .article-info {
display: none; display: none;
} }
tr.order-article:focus .article-info {
display: none;
}
} }
#order-footer { #order-footer {
@ -275,10 +278,13 @@ tr.order-article .article-info {
display: none; display: none;
} }
tr.order-article:hover .article-info { tr.order-article:focus .article-info {
display: block; display: block;
} }
tr.order-article:focus {
background-color: #E9E9E9;
}
// ********* Articles // ********* Articles

View file

@ -1,11 +1,23 @@
.list .missing-many td, .list .missing-many:hover td { .missing-many td {
background-color: #ebbebe; background-color: #ffc590aa;
} }
.list .missing-few td, .list .missing-few:hover td { .missing-many:hover td, .missing-many:focus td {
background-color: #ffee75; background-color: #ffc590;
} }
.list .missing-none td, .list .missing-none:hover td { .missing-few td {
background-color: #E4EED6; background-color: #fcf488aa;
}
.missing-few:hover td, .missing-few:focus td {
background-color: #fcf488;
}
.missing-none td {
background-color: #d0f6ffaa;
}
.missing-none:hover td, .missing-none:focus td {
background-color: #d0f6ff;
} }

View file

@ -46,6 +46,11 @@ class ArticlesController < ApplicationController
render :layout => false render :layout => false
end end
def edit
@article = Article.find(params[:id])
render :action => 'new', :layout => false
end
def create def create
@article = Article.new(params[:article]) @article = Article.new(params[:article])
if @article.valid? && @article.save if @article.valid? && @article.save
@ -55,11 +60,6 @@ class ArticlesController < ApplicationController
end end
end end
def edit
@article = Article.find(params[:id])
render :action => 'new', :layout => false
end
# Updates one Article and highlights the line if succeded # Updates one Article and highlights the line if succeded
def update def update
@article = Article.find(params[:id]) @article = Article.find(params[:id])
@ -148,10 +148,12 @@ class ArticlesController < ApplicationController
# Update articles from a spreadsheet # Update articles from a spreadsheet
def parse_upload def parse_upload
uploaded_file = params[:articles]['file'] or raise I18n.t('articles.controller.parse_upload.no_file') 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 = { filename: uploaded_file.original_filename }
options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1') options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
options[:convert_units] = (params[:articles]['convert_units'] == '1') options[:convert_units] = (params[:articles]['convert_units'] == '1')
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, options 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? 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') redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice')
end end

View file

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

View file

@ -11,7 +11,10 @@ class Finance::OrdergroupsController < Finance::BaseController
@ordergroups = Ordergroup.undeleted.order(sort) @ordergroups = Ordergroup.undeleted.order(sort)
@ordergroups = @ordergroups.include_transaction_class_sum @ordergroups = @ordergroups.include_transaction_class_sum
@ordergroups = @ordergroups.where('groups.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil? @ordergroups = @ordergroups.where('groups.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil?
@ordergroups = @ordergroups.page(params[:page]).per(@per_page) @ordergroups = @ordergroups.page(params[:page]).per(@per_page)
@total_balances = FinancialTransactionClass.sorted.each_with_object({}) do |c, tmp|
tmp[c.id] = c.financial_transactions.reduce(0) { | sum, t | sum + t.amount }
end
end end
end end

View file

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

View file

@ -49,7 +49,7 @@ class OrdersController < ApplicationController
send_order_pdf @order, params[:document] send_order_pdf @order, params[:document]
end end
format.csv do format.csv do
send_data OrderCsv.new(@order).to_csv, filename: @order.name + '.csv', type: 'text/csv' send_data OrderCsv.new(@order, options= {custom_csv: params[:custom_csv]}).to_csv, filename: @order.name + '.csv', type: 'text/csv'
end end
format.text do format.text do
send_data OrderTxt.new(@order).to_txt, filename: @order.name + '.txt', type: 'text/plain' send_data OrderTxt.new(@order).to_txt, filename: @order.name + '.txt', type: 'text/plain'
@ -57,6 +57,19 @@ class OrdersController < ApplicationController
end end
end end
def custom_csv
@order = Order.find(params[:id])
@view = (params[:view] || 'default').gsub(/[^-_a-zA-Z0-9]/, '')
@partial = case @view
when 'default' then 'articles'
when 'groups' then 'shared/articles_by/groups'
when 'articles' then 'shared/articles_by/articles'
else 'articles'
end
render :layout => false
end
# Page to create a new order. # Page to create a new order.
def new def new
if params[:order_id] if params[:order_id]

View file

@ -53,4 +53,12 @@ module GroupOrdersHelper
return 'missing-many' return 'missing-many'
end end
end end
def price_per_base_unit(article:, price:)
quantity_unit = QuantityUnit.parse(article.unit)
return nil unless quantity_unit.present?
scaled_price, base_unit = quantity_unit.scale_price_to_base_unit(price)
"#{number_to_currency(scaled_price)}/#{base_unit}"
end
end end

View file

@ -155,4 +155,16 @@ module OrdersHelper
link_to t('orders.index.action_receive'), receive_order_path(order), class: "btn#{' btn-success' unless order.received?} #{options[:class]}" link_to t('orders.index.action_receive'), receive_order_path(order), class: "btn#{' btn-success' unless order.received?} #{options[:class]}"
end end
end end
def custom_csv_collection
[
OrderArticle.human_attribute_name(:units_to_order),
Article.human_attribute_name(:order_number),
Article.human_attribute_name(:name),
Article.human_attribute_name(:unit),
Article.human_attribute_name(:unit_quantity_short),
ArticlePrice.human_attribute_name(:price),
OrderArticle.human_attribute_name(:total_price)
]
end
end end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

61
app/lib/order_csv.rb Normal file
View file

@ -0,0 +1,61 @@
require 'csv'
class OrderCsv < RenderCsv
def header
params = @options[:custom_csv]
arr = if params.nil?
[
OrderArticle.human_attribute_name(:units_to_order),
Article.human_attribute_name(:order_number),
Article.human_attribute_name(:name),
Article.human_attribute_name(:unit),
Article.human_attribute_name(:unit_quantity_short),
ArticlePrice.human_attribute_name(:price),
OrderArticle.human_attribute_name(:total_price)
]
else
[
params[:first],
params[:second],
params[:third],
params[:fourth],
params[:fifth],
params[:sixth],
params[:seventh]
]
end
end
def data
@object.order_articles.ordered.includes([:article, :article_price]).all.map do |oa|
yield [
match_params(oa, header[0]),
match_params(oa, header[1]),
match_params(oa, header[2]),
match_params(oa, header[3]),
match_params(oa, header[4]),
match_params(oa, header[5]),
match_params(oa, header[6])
]
end
end
def match_params(object, attribute)
case attribute
when OrderArticle.human_attribute_name(:units_to_order)
object.units_to_order
when Article.human_attribute_name(:order_number)
object.article.order_number
when Article.human_attribute_name(:name)
object.article.name
when Article.human_attribute_name(:unit)
object.article.unit
when Article.human_attribute_name(:unit_quantity_short)
object.price.unit_quantity > 1 ? object.price.unit_quantity : nil
when ArticlePrice.human_attribute_name(:price)
number_to_currency(object.price.price * object.price.unit_quantity)
when OrderArticle.human_attribute_name(:total_price)
number_to_currency(object.total_price)
end
end
end

View file

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

View file

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

View file

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

59
app/lib/quantity_unit.rb Normal file
View file

@ -0,0 +1,59 @@
class QuantityUnit
def initialize(quantity, unit)
@quantity = quantity
@unit = unit
end
def self.parse(number_with_unit)
# remove whitespace
number_with_unit = number_with_unit.gsub(/\s+/, '')
# to lowercase
number_with_unit = number_with_unit.downcase
# remove numerical part
number = number_with_unit.gsub(/[^0-9.,]/, '')
# remove unit part
unit = number_with_unit.gsub(/[^a-zA-Z]/, '')
# convert comma to dot
number = number.gsub(',', '.')
# convert to float
number = number.to_f
return nil unless unit.in?(%w[g kg l ml])
QuantityUnit.new(number, unit)
end
def scale_price_to_base_unit(price)
return nil unless price.is_a?(Numeric)
factor = if @unit == 'kg' || @unit == 'l'
1
elsif @unit == 'g' || @unit == 'ml'
1000
end
scaled_price = price / @quantity * factor
scaled_price.round(2)
base_unit = if @unit == 'kg' || @unit == 'g'
'kg'
elsif @unit == 'l' || @unit == 'ml'
'L'
end
[scaled_price, base_unit]
end
def to_s
"#{@quantity} #{@unit}"
end
def quantity
@quantity
end
def unit
@unit
end
end

View file

@ -1,6 +1,6 @@
require 'csv' require 'csv'
class RenderCSV class RenderCsv
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
def initialize(object, options = {}) def initialize(object, options = {})
@ -20,6 +20,7 @@ class RenderCSV
end end
data { |d| csv << d } data { |d| csv << d }
end end
ret << I18n.t('.orders.articles.prices_sum') << ";" << "#{number_to_currency(@object.sum(:gross))}/#{number_to_currency(@object.sum(:net))}" if @options[:custom_csv]
ret.encode(@options[:encoding], invalid: :replace, undef: :replace) ret.encode(@options[:encoding], invalid: :replace, undef: :replace)
end end

View file

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

View file

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

View file

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

View file

@ -143,20 +143,24 @@ class Article < ApplicationRecord
new_unit = new_article.unit new_unit = new_article.unit
end end
return Article.compare_attributes( attribute_hash = {
{ :name => [self.name, new_article.name],
:name => [self.name, new_article.name], :manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
:manufacturer => [self.manufacturer, new_article.manufacturer.to_s], :origin => [self.origin, new_article.origin],
:origin => [self.origin, new_article.origin], :unit => [self.unit, new_unit],
:unit => [self.unit, new_unit], :price => [self.price.to_f.round(2), new_price.to_f.round(2)],
:price => [self.price.to_f.round(2), new_price.to_f.round(2)], :tax => [self.tax, new_article.tax],
:tax => [self.tax, new_article.tax], :deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
:deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)], # take care of different num-objects.
# take care of different num-objects. :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
: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]
: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
# Compare attributes from two different articles. # Compare attributes from two different articles.

View file

@ -8,7 +8,7 @@ module LocalizeInput
separator = I18n.t("separator", scope: "number.format") separator = I18n.t("separator", scope: "number.format")
delimiter = I18n.t("delimiter", 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!(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 input
rescue rescue
Rails.logger.warn "Can't localize input: #{input}" 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 # Generate some data for the javascript methods in ordering view
def load_data def load_data
data = {} data = {}
data[:account_balance] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.account_balance data[:account_balance] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.account_balance
data[:available_funds] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.get_available_funds(self) data[:available_funds] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.get_available_funds(self)
# load prices and other stuff.... # load prices and other stuff....
data[:order_articles] = {} data[:order_articles] = {}

View file

@ -1,3 +1,4 @@
require 'foodsoft_article_import'
class Supplier < ApplicationRecord class Supplier < ApplicationRecord
include MarkAsDeletedWithName include MarkAsDeletedWithName
include CustomFields include CustomFields
@ -73,15 +74,24 @@ class Supplier < ApplicationRecord
# Synchronise articles with spreadsheet. # Synchronise articles with spreadsheet.
# #
# @param file [File] Spreadsheet file to parse # @param file [File] Spreadsheet file to parse
# @param options [Hash] Options passed to {FoodsoftFile#parse} except when listed here. # @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] :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. # @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
def sync_from_file(file, options = {}) def sync_from_file(file, type, options = {})
all_order_numbers = [] all_order_numbers = []
updated_article_pairs, outlisted_articles, new_articles = [], [], [] updated_article_pairs, outlisted_articles, new_articles = [], [], []
FoodsoftFile::parse file, options do |status, new_attrs, line| custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
opts = options.except(:convert_units, :outlist_absent, :update_category)
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 article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category])
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_attrs[:tax] ||= FoodsoftConfig[:tax_default]
new_article = articles.build(new_attrs) new_article = articles.build(new_attrs)
@ -89,7 +99,7 @@ class Supplier < ApplicationRecord
if article.nil? if article.nil?
new_articles << new_article new_articles << new_article
else else
unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units)) unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units, :update_category))
unless unequal_attributes.empty? unless unequal_attributes.empty?
article.attributes = unequal_attributes article.attributes = unequal_attributes
updated_article_pairs << [article, unequal_attributes] updated_article_pairs << [article, unequal_attributes]

View file

@ -0,0 +1,14 @@
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
<% if blob.representable? %>
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
<% end %>
<figcaption class="attachment__caption">
<% if caption = blob.try(:caption) %>
<%= caption %>
<% else %>
<span class="attachment__name"><%= blob.filename %></span>
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
<% end %>
</figcaption>
</figure>

View file

@ -49,7 +49,8 @@
.input-prepend .input-prepend
%span.add-on= t 'number.currency.format.unit' %span.add-on= t 'number.currency.format.unit'
= form.text_field 'deposit', class: 'input-mini', style: 'width: 45px' = form.text_field 'deposit', class: 'input-mini', style: 'width: 45px'
%td= form.select :article_category_id, ArticleCategory.all.map {|a| [ a.name, a.id ] }, %td{:style => highlight_new(attrs, :article_category)}
= form.select :article_category_id, ArticleCategory.all.map {|a| [ a.name, a.id ] },
{include_blank: true}, class: 'input-small' {include_blank: true}, class: 'input-small'
- unless changed_article.errors.empty? - unless changed_article.errors.empty?
%tr.alert %tr.alert

View file

@ -71,11 +71,19 @@
= form_for :articles, :url => parse_upload_supplier_articles_path(@supplier), = form_for :articles, :url => parse_upload_supplier_articles_path(@supplier),
:html => { multipart: true, class: "form-horizontal" } do |f| :html => { multipart: true, class: "form-horizontal" } do |f|
.control-group
%label(for="articles_file")= t '.file_label'
= f.file_field "file"
.control-group .control-group
%label(for="articles_file")
%strong= t '.file_label'
= f.file_field "file"
%label(for="articles_file")
%strong="select the file type you are about to upload"
=f.collection_select :type, ["bnn","foodsoft","odin"], :to_s, :to_s
.control-group
%label(for="articles_update_category")
= f.check_box "update_category"
= t '.options.update_category'
%label(for="articles_outlist_absent") %label(for="articles_outlist_absent")
= f.check_box "outlist_absent" = f.check_box "outlist_absent"
= t '.options.outlist_absent' = t '.options.outlist_absent'

View file

@ -22,3 +22,12 @@
%td %td
= link_to t('.new_transaction'), new_finance_ordergroup_transaction_path(ordergroup), class: 'btn btn-mini' = link_to t('.new_transaction'), new_finance_ordergroup_transaction_path(ordergroup), class: 'btn btn-mini'
= link_to t('.account_statement'), finance_ordergroup_transactions_path(ordergroup), class: 'btn btn-mini' = link_to t('.account_statement'), finance_ordergroup_transactions_path(ordergroup), class: 'btn btn-mini'
%thead
%tr
%th= t 'Total'
%th
- FinancialTransactionClass.sorted.each do |c|
- name = FinancialTransactionClass.has_multiple_classes ? c.display : heading_helper(Ordergroup, :account_balance)
%th.numeric= format_currency @total_balances[c.id]
%th.numeric
= format_currency @total_balances.values.reduce(:+)

View file

@ -0,0 +1,15 @@
%h4= t '.title'
%hr
%table.table-condensed
%thead
%th= t '.package_fill_level'
%tbody
%tr{class: "missing-none"}
%td= t '.missing_none'
%tr{class: "missing-few"}
%td= t '.missing_few'
%tr{class: "missing-many"}
%td= t '.missing_many'
%hr
%b= t('.tolerance') + ':'
= t '.tolerance_explained'

View file

@ -11,170 +11,142 @@
var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}]; var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}];
var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}]; var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}];
new List(document.body, { new List(document.body, {
valueNames: ['name'], valueNames: ['name'],
engine: 'unlist', engine: 'unlist',
plugins: [listjsResetPlugin, listjsDelayPlugin], plugins: [listjsResetPlugin, listjsDelayPlugin],
// make large pages work too (as we don't have paging - articles may disappear!) // make large pages work too (as we don't have paging - articles may disappear!)
page: 10000, page: 10000,
indexAsync: true indexAsync: true
}); });
}); });
- title t('.title'), false - title t('.title'), false
.alert.alert-error#balance-alert{style: ('display:none')}
=t 'group_orders.errors.balance_alert'
.row-fluid .row-fluid
.well.pull-left .span2
= close_button :alert .well
%h2= @order.name = render 'switch_order', current_order: @order
%dl.dl-horizontal .well
- unless @order.note.blank? = render 'explanations'
%dt= heading_helper Order, :note .well.span9
%dd= @order.note %h2.span9= t '.sub_title', order_name: @order.name
%dt= heading_helper Order, :created_by .span3
%dd= show_user_link(@order.created_by) %table.table-condensed
%dt= heading_helper Order, :ends -if @order.ends
%dd= format_time(@order.ends) %tr
%dt= heading_helper Order, :pickup %td= heading_helper(Order, :ends) + ': '
%dd= format_date(@order.pickup) %td= format_time(@order.ends)
- unless @order.stockit? or @order.supplier.min_order_quantity.blank? - unless @order.stockit? or @order.supplier.min_order_quantity.blank?
%dt= heading_helper Supplier, :min_order_quantity, short: true %tr
%dd= @order.supplier.min_order_quantity %td= heading_helper(Supplier, :min_order_quantity)
%dt= t '.sum_amount' %td= number_to_currency(@order.supplier.min_order_quantity)
%dd= number_to_currency @order.sum %tr
- unless @group_order.new_record? %td= t('group_orders.form.sum_amount') + ':'
%dt= heading_helper GroupOrder, :updated_by %td= number_to_currency(@order.sum)
%dd %hr
= show_user(@group_order.updated_by) .form-search.pull-right
(#{format_time(@group_order.updated_on)})
%dt= heading_helper Ordergroup, :account_balance
%dd= number_to_currency(@ordering_data[:account_balance])
- unless FoodsoftConfig[:charge_members_manually]
%dt= heading_helper Ordergroup, :available_funds
%dd= number_to_currency(@ordering_data[:available_funds])
.well.pull-right
= close_button :alert
= render 'switch_order', current_order: @order
.row-fluid
.well.clear
.form-search
.input-append .input-append
= text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search resettable' = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search resettable'
%button.add-on.btn.reset-search{:type => :button, :title => t('.reset_article_search')} %button.add-on.btn.reset-search{:type => :button, :title => t('.reset_article_search')}
%i.icon.icon-remove %i.icon.icon-remove
= form_for @group_order do |f|
= form_for @group_order do |f| = f.hidden_field :lock_version
= f.hidden_field :lock_version = f.hidden_field :order_id
= f.hidden_field :order_id = f.hidden_field :updated_by_user_id
= f.hidden_field :updated_by_user_id = f.hidden_field :ordergroup_id
= f.hidden_field :ordergroup_id %table.table
%table.table.table-hover %thead
%thead %tr
%tr %th= heading_helper Article, :name
%th= heading_helper Article, :name
- if @order.stockit?
%th{style: 'width:120px'}= heading_helper StockArticle, :supplier
%th{style: "width:13px;"}
%th{style: "width:4.5em;"}= t '.price'
%th{style: "width:4.5em;"}= heading_helper Article, :unit
- unless @order.stockit?
%th{style: "width:70px;"}= heading_helper OrderArticle, :missing_units, short: true
%th#col_required= heading_helper GroupOrderArticle, :quantity
%th#col_tolerance= heading_helper GroupOrderArticle, :tolerance
- else
%th(style="width:20px")= heading_helper StockArticle, :available
%th#col_required= heading_helper GroupOrderArticle, :quantity
%th{style: "width:15px;"}= heading_helper GroupOrderArticle, :total_price
%tbody.list
- @order.articles_grouped_by_category.each do |category, order_articles|
%tr.list-heading.article-category
%td
= category
%i.icon-tag
%td{colspan: "9"}
- order_articles.each do |order_article|
%tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top"}
%td.name= order_article.article.name
- if @order.stockit? - if @order.stockit?
%td= truncate order_article.article.supplier.name, length: 15 %th{style: 'width:120px'}= heading_helper StockArticle, :supplier
%td= h order_article.article.origin %th{style: "width:13px;"}
%td= number_to_currency(@ordering_data[:order_articles][order_article.id][:price]) %th{style: "width:4.5em;"}= t '.price'
%td= order_article.article.unit %th{style: "width:4.5em;"}= t '.price_per_base_unit'
%td %th{style: "width:4.5em;"}= heading_helper Article, :unit
- if @order.stockit? - unless @order.stockit?
= @ordering_data[:order_articles][order_article.id][:quantity_available] %th{style: "width:70px;"}= heading_helper OrderArticle, :missing_units, short: true
- else %th#col_required= heading_helper GroupOrderArticle, :quantity
%span{id: "missing_units_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:missing_units] %th#col_tolerance= heading_helper GroupOrderArticle, :tolerance
- else
%th(style="width:20px")= heading_helper StockArticle, :available
%th#col_required= heading_helper GroupOrderArticle, :quantity
%th{style: "width:15px;"}= heading_helper GroupOrderArticle, :total_price
%tbody.list
- @order.articles_grouped_by_category.each do | category, order_articles|
%tr.list-heading.article-category
%td
= category
%i.icon-tag
%td{colspan: "9"}
- order_articles.each do |order_article|
%tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top", tabindex: "0"}
%td.name= order_article.article.name
- if @order.stockit?
%td= truncate order_article.article.supplier.name, length: 15
%td= h order_article.article.origin
%td= number_to_currency(@ordering_data[:order_articles][order_article.id][:price])
%td= price_per_base_unit(article: order_article.article, price: @ordering_data[:order_articles][order_article.id][:price])
%td= order_article.article.unit
%td
- if @order.stockit?
= @ordering_data[:order_articles][order_article.id][:quantity_available]
- else
%span{id: "missing_units_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:missing_units]
%td.quantity %td.quantity
%input{id: "q_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][quantity]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:quantity], 'data-min' => (@ordering_data[:order_articles][order_article.id][:quantity] if @order.boxfill?), 'data-max' => (@ordering_data[:order_articles][order_article.id][:quantity]+@ordering_data[:order_articles][order_article.id][:missing_units] if @order.boxfill?)}/ .outer{style: "diyplay: inline-block; float: left; width: 50px;"}
%span.used{id: "q_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_quantity] %input{id: "q_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][quantity]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:quantity], 'data-min' => (@ordering_data[:order_articles][order_article.id][:quantity] if @order.boxfill?), 'data-max' => (@ordering_data[:order_articles][order_article.id][:quantity]+@ordering_data[:order_articles][order_article.id][:missing_units] if @order.boxfill?)}/
+ %span.used{id: "q_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_quantity]
%span.unused{id: "q_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:quantity] - @ordering_data[:order_articles][order_article.id][:used_quantity] +
.btn-group %span.unused{id: "q_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:quantity] - @ordering_data[:order_articles][order_article.id][:used_quantity]
%a.btn.btn-ordering{'data-increase_quantity' => order_article.id} .btn-group
%i.icon-plus %a.btn.btn-ordering{'data-decrease_quantity' => order_article.id}
%a.btn.btn-ordering{'data-decrease_quantity' => order_article.id} %i.icon-minus
%i.icon-minus %a.btn.btn-ordering{'data-increase_quantity' => order_article.id}
%i.icon-plus
%td.tolerance{style: ('display:none' if @order.stockit?)} %td.tolerance{style: ('display:none' if @order.stockit?)}
%input{id: "t_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][tolerance]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:tolerance], 'data-min' => (@ordering_data[:order_articles][order_article.id][:tolerance] if @order.boxfill?)}/ %input{id: "t_#{order_article.id}", name: "group_order[group_order_articles_attributes][#{order_article.id}][tolerance]", type: "hidden", value: @ordering_data[:order_articles][order_article.id][:tolerance], 'data-min' => (@ordering_data[:order_articles][order_article.id][:tolerance] if @order.boxfill?)}/
- if (@ordering_data[:order_articles][order_article.id][:unit] > 1) - if (@ordering_data[:order_articles][order_article.id][:unit] > 1)
%span.used{id: "t_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_tolerance] %span.used{id: "t_used_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:used_tolerance]
+ +
%span.unused{id: "t_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] - @ordering_data[:order_articles][order_article.id][:used_tolerance] %span.unused{id: "t_unused_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] - @ordering_data[:order_articles][order_article.id][:used_tolerance]
.btn-group .btn-group
%a.btn.btn-ordering{'data-increase_tolerance' => order_article.id} %a.btn.btn-ordering{'data-decrease_tolerance' => order_article.id}
%i.icon-plus %i.icon-minus
%a.btn.btn-ordering{'data-decrease_tolerance' => order_article.id} %a.btn.btn-ordering{'data-increase_tolerance' => order_article.id}
%i.icon-minus %i.icon-plus
%td{id: "td_price_#{order_article.id}", style: "text-align:right; padding-right:10px; width:4em"} %td{id: "td_price_#{order_article.id}", style: "text-align:right; padding-right:10px; width:4em"}
%span{id: "price_#{order_article.id}_display"}= number_to_currency(@ordering_data[:order_articles][order_article.id][:total_price]) %span{id: "price_#{order_article.id}_display"}= number_to_currency(@ordering_data[:order_articles][order_article.id][:total_price])
.article-info .article-info
.article-name= order_article.article.name .article-name= order_article.article.name
.pull-right .pull-right
= t('.units_full') + ':' = t('.units_full') + ':'
%span{id: "units_#{order_article.id}"}= order_article.units_to_order %span{id: "units_#{order_article.id}"}= order_article.units_to_order
%br/ %br/
= t('.units_total') + ':' = t('.units_total') + ':'
%span{id: "q_total_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:quantity] + @ordering_data[:order_articles][order_article.id][:others_quantity] %span{id: "q_total_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:quantity] + @ordering_data[:order_articles][order_article.id][:others_quantity]
%br/ %br/
= t('.total_tolerance') + ':' = t('.total_tolerance') + ':'
%span{id: "t_total_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] + @ordering_data[:order_articles][order_article.id][:others_tolerance] %span{id: "t_total_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:tolerance] + @ordering_data[:order_articles][order_article.id][:others_tolerance]
%br/ %br/
.pull-left .pull-left
#{heading_helper Article, :manufacturer}: #{order_article.article.manufacturer} #{heading_helper Article, :manufacturer}: #{order_article.article.manufacturer}
%br/ %br/
#{heading_helper Article, :units}: #{@order.stockit? ? order_article.article.quantity_available : @ordering_data[:order_articles][order_article.id][:unit]} * #{h order_article.article.unit} #{heading_helper Article, :units}: #{@order.stockit? ? order_article.article.quantity_available : @ordering_data[:order_articles][order_article.id][:unit]} * #{h order_article.article.unit}
%br/ %br/
#{heading_helper Article, :note}: #{order_article.article.note} #{heading_helper Article, :note}: #{order_article.article.note}
%br/ %br/
#order-footer #order-footer
#info-box #info-box
#total-sum #total-sum
%table = render 'total_sum'
%tr #order-button
%td= t('.total_sum_amount') + ':' = submit_tag( t('.action_save'), id: 'submit_button', class: 'btn btn-primary' )
%td.currency #{link_to t('ui.or_cancel'), group_orders_path}
%span#total_price= number_to_currency(@group_order.price) %input#total_balance{name: "total_balance", type: "hidden", value: @ordergroup.account_balance - @group_order.price}/
%tr %input{name: "version", type: "hidden", value: @version}/
- if FoodsoftConfig[:charge_members_manually]
- old_balance = @ordering_data[:account_balance]
%td= heading_helper(Ordergroup, :account_balance) + ':'
%td.currency= number_to_currency(@ordering_data[:account_balance])
- else
- old_balance = @ordering_data[:available_funds]
%td= heading_helper(Ordergroup, :available_funds) + ':'
%td.currency= number_to_currency(@ordering_data[:available_funds])
%tr
%td= t('.new_funds') + ':'
%td.currency
%strong
%span#new_balance= number_to_currency(old_balance - @group_order.price)
#order-button
= submit_tag( t('.action_save'), id: 'submit_button', class: 'btn btn-primary' )
#{link_to t('ui.or_cancel'), group_orders_path}
%input#total_balance{name: "total_balance", type: "hidden", value: @ordergroup.account_balance - @group_order.price}/
%input{name: "version", type: "hidden", value: @version}/

View file

@ -1,9 +1,10 @@
- orders = Order.open.started.reject{ |order| order == current_order } - orders = Order.open.started
- unless orders.empty? - unless orders.empty?
%h2= t '.title' %ul.nav.nav-pills.nav-stacked
%ul.unstyled .nav-header= t '.title'
%li= link_to t('ui.overview'), :group_orders
- orders.each do |order| - orders.each do |order|
%li .btn-small.pull-right
= link_to_ordering(order, 'data-confirm_switch_order' => true) =link_to_ordering(order, style: (order == current_order ? 'color: white' : '' ), 'data-confirm_switch_order' => true){ t 'ui.edit' }
- if order.ends %li( class="#{ order == current_order ? 'active' : ''}")
= t '.remaining', remaining: time_ago_in_words(order.ends) =link_to_ordering(order, show: true, 'data-confirm_switch_order' => true)

View file

@ -0,0 +1,19 @@
%table
%tr
%td= t('group_orders.form.total_sum_amount') + ':'
%td.currency
%span#total_price= number_to_currency(@group_order.price)
%tr
- if FoodsoftConfig[:charge_members_manually]
- old_balance = @ordering_data[:account_balance]
%td= heading_helper(Ordergroup, :account_balance) + ':'
%td.currency= number_to_currency(@ordering_data[:account_balance])
- else
- old_balance = @ordering_data[:available_funds]
%td= heading_helper(Ordergroup, :available_funds) + ':'
%td.currency= number_to_currency(@ordering_data[:available_funds])
%tr
%td= t('group_orders.form.new_funds') + ':'
%td.currency
%strong
%span#new_balance= number_to_currency(old_balance - @group_order.price)

View file

@ -18,22 +18,27 @@
%th= heading_helper Ordergroup, :available_funds %th= heading_helper Ordergroup, :available_funds
%th.numeric= number_to_currency(@ordergroup.get_available_funds) %th.numeric= number_to_currency(@ordergroup.get_available_funds)
= render :partial => "shared/open_orders", :locals => {:ordergroup => @ordergroup} .row-fluid
.span9
// finished orders = render :partial => "shared/open_orders", :locals => {:ordergroup => @ordergroup}
// finished orders
- unless @finished_not_closed_orders_including_group_order.empty? - unless @finished_not_closed_orders_including_group_order.empty?
%section .row-fluid
%h2= t '.finished_orders.title' .span9
= render partial: 'orders', locals: {orders: @finished_not_closed_orders_including_group_order, pagination: false} %section
- if @ordergroup.value_of_finished_orders > 0 %h2= t '.finished_orders.title'
%p = render partial: 'orders', locals: {orders: @finished_not_closed_orders_including_group_order, pagination: false}
= t('.finished_orders.total_sum') + ':' - if @ordergroup.value_of_finished_orders > 0
%b= number_to_currency(@ordergroup.value_of_finished_orders) %p
= t('.finished_orders.total_sum') + ':'
%b= number_to_currency(@ordergroup.value_of_finished_orders)
// closed orders // closed orders
- unless @closed_orders_including_group_order.empty? - unless @closed_orders_including_group_order.empty?
%section .row-fluid
%h2= t '.closed_orders.title' .span9
= render partial: 'orders', locals: {orders: @closed_orders_including_group_order, pagination: false} %section
%br/ %h2= t '.closed_orders.title'
= link_to t('.closed_orders.more'), archive_group_orders_path = render partial: 'orders', locals: {orders: @closed_orders_including_group_order, pagination: false}
%br/
= link_to t('.closed_orders.more'), archive_group_orders_path

View file

@ -7,107 +7,115 @@
- title t('.title', order: @order.name) - title t('.title', order: @order.name)
.row-fluid .row-fluid
.well.pull-left
// Order summary .well.span2
= render 'switch_order', current_order: @order
.well.span9
%h2= t '.articles.title'
%dl.dl-horizontal %dl.dl-horizontal
// Name
%dt= heading_helper Order, :name %dt= heading_helper Order, :name
%dd= @order.name %dd= @order.name
%dt= heading_helper Order, :note // Order Ends
%dd= @order.note
%dt= heading_helper Order, :ends %dt= heading_helper Order, :ends
%dd= format_time(@order.ends) %dd= format_time(@order.ends)
%dt= heading_helper Order, :pickup // Pickup
%dd= format_date(@order.pickup) - unless @order.pickup.blank?
%dt= heading_helper GroupOrder, :price %dt= heading_helper Order, :pickup
%dd %dd= format_date(@order.pickup)
- if @group_order // Min Order Quantity
= number_to_currency(@group_order.price) - unless @order.stockit? or @order.supplier.min_order_quantity.blank?
- else %dt= heading_helper Supplier, :min_order_quantity, short: true
= t '.not_ordered' %dd= @order.supplier.min_order_quantity
- if @group_order && @group_order.transport // Group Order Sum Amount
%dt= heading_helper GroupOrder, :transport %dt= t 'group_orders.form.sum_amount'
%dd= number_to_currency(@group_order.transport) %dd= number_to_currency @order.sum
%dt= heading_helper GroupOrder, :total // Created By
%dd= number_to_currency(@group_order.total) %dt= heading_helper Order, :created_by
%dd= show_user_link(@order.created_by)
// Updated By
- unless @group_order.new_record?
%dt= heading_helper GroupOrder, :updated_by
%dd
= show_user(@group_order.updated_by)
(#{format_time(@group_order.updated_on)})
// Closed By
- if @order.closed? - if @order.closed?
%dt= heading_helper Order, :closed_by %dt= heading_helper Order, :closed_by
%dd= show_user_link @order.updated_by %dd= show_user_link @order.updated_by
%p= link_to t('.comment'), "#comments" // Note
- unless @order.note.blank?
%dt= heading_helper Order, :note
%dd= @order.note
.well.pull-right // Article box
= close_button :alert %section
= render 'switch_order', current_order: @order .column_content#result
- if @group_order
// Article box %p= link_to t('.articles.show_hide'), '#', 'data-toggle-this' => 'tr.ignored'
%section %table.table.table-hover
%h2= t '.articles.title' %thead
.column_content#result %tr
- if @group_order %th{style: "width:40%"}= heading_helper Article, :name
%p.pull-right= link_to t('.articles.show_hide'), '#', 'data-toggle-this' => 'tr.ignored' %th= heading_helper Article, :units
%p= link_to(t('.articles.edit_order'), edit_group_order_path(@group_order, order_id: @order.id), class: 'btn btn-primary') if @order.open? %th= t '.articles.unit_price'
%table.table.table-hover %th
%thead %abbr{title: t('.articles.ordered_title')}= t '.articles.ordered'
%tr %th
%th{style: "width:40%"}= heading_helper Article, :name %abbr{title: t('.articles.order_nopen_title')}
%th= heading_helper Article, :units - if @order.open?
%th= t '.articles.unit_price' = t '.articles.order_open'
%th - else
%abbr{title: t('.articles.ordered_title')}= t '.articles.ordered' = t '.articles.order_not_open'
%th %th= heading_helper GroupOrderArticle, :total_price
%abbr{title: t('.articles.order_nopen_title')} %tbody
- if @order.open? - for category_name, order_articles in @order.articles_grouped_by_category
= t '.articles.order_open' %tr.article-category
- else %td
= t '.articles.order_not_open' = category_name
%th= heading_helper GroupOrderArticle, :total_price %i.icon-tag
%tbody %td{colspan: "9"}
- for category_name, order_articles in @order.articles_grouped_by_category - order_articles.each do |oa|
%tr.article-category - # get the order-results for the ordergroup
%td - r = get_order_results(oa, @group_order.id)
= category_name %tr{class: cycle('even', 'odd', name: 'articles') + " " + order_article_class_name(r[:quantity], r[:tolerance], r[:result])}
%i.icon-tag %td{style: "width:40%"}
%td{colspan: "9"} = oa.article.name
- order_articles.each do |oa| - unless oa.article.note.blank?
- # get the order-results for the ordergroup = image_tag("lamp_grey.png", {alt: t('.articles.show_note'), size: "15x16", border: "0", onmouseover: "$('#note_#{oa.id}').show();", onmouseout: "$('#note_#{oa.id}').hide();"})
- r = get_order_results(oa, @group_order.id) %td= "#{oa.price.unit_quantity} x #{oa.article.unit}"
%tr{class: cycle('even', 'odd', name: 'articles') + " " + order_article_class_name(r[:quantity], r[:tolerance], r[:result])} %td= number_to_currency(oa.price.fc_price)
%td{style: "width:40%"} %td
= oa.article.name = r[:quantity]
= "+ #{r[:tolerance]}" if oa.price.unit_quantity > 1
%td= r[:result] > 0 ? r[:result] : "0"
%td= number_to_currency(r[:sub_total])
- unless oa.article.note.blank? - unless oa.article.note.blank?
= image_tag("lamp_grey.png", {alt: t('.articles.show_note'), size: "15x16", border: "0", onmouseover: "$('#note_#{oa.id}').show();", onmouseout: "$('#note_#{oa.id}').hide();"}) %tr{id: "note_#{oa.id}", class: "note even", style: "display:none"}
%td= "#{oa.price.unit_quantity} x #{oa.article.unit}" %td{colspan: "6"}=h oa.article.note
%td= number_to_currency(oa.price.fc_price) %tr{class: cycle('even', 'odd', name: 'articles')}
%td %th{colspan: "5"}= heading_helper GroupOrder, :price
= r[:quantity] %th= number_to_currency(@group_order.price)
= "+ #{r[:tolerance]}" if oa.price.unit_quantity > 1 - if @group_order.transport
%td= r[:result] > 0 ? r[:result] : "0" %tr{class: cycle('even', 'odd', name: 'articles')}
%td= number_to_currency(r[:sub_total]) %td{colspan: "5"}= heading_helper GroupOrder, :transport
- unless oa.article.note.blank? %td= number_to_currency(@group_order.transport)
%tr{id: "note_#{oa.id}", class: "note even", style: "display:none"} %tr{class: cycle('even', 'odd', name: 'articles')}
%td{colspan: "6"}=h oa.article.note %th{colspan: "5"}= heading_helper GroupOrder, :total
%tr{class: cycle('even', 'odd', name: 'articles')} %th= number_to_currency(@group_order.total)
%th{colspan: "5"}= heading_helper GroupOrder, :price %br/
%th= number_to_currency(@group_order.price) = link_to_top
- if @group_order.transport %p.pull-right= link_to(t('.articles.edit_order'), edit_group_order_path(@group_order, order_id: @order.id), class: 'btn btn-primary') if @order.open?
%tr{class: cycle('even', 'odd', name: 'articles')} - else
%td{colspan: "5"}= heading_helper GroupOrder, :transport - if @order.open?
%td= number_to_currency(@group_order.transport) = t '.articles.not_ordered_msg'
%tr{class: cycle('even', 'odd', name: 'articles')} = link_to t('.articles.order_now'), action: "order", id: @order
%th{colspan: "5"}= heading_helper GroupOrder, :total - else
%th= number_to_currency(@group_order.total) = t '.articles.order_closed_msg'
%br/
= link_to_top
- else
- if @order.open?
= t '.articles.not_ordered_msg'
= link_to t('.articles.order_now'), action: "order", id: @order
- else
= t '.articles.order_closed_msg'
// Comments box // Comments box
%section %hr
%h2= t '.comments.title' %h2= t '.comments.title'
#comments #comments
= render 'shared/comments', comments: @order.comments = render 'shared/comments', comments: @order.comments
#new_comment= render 'order_comments/form', order_comment: @order.comments.build(user: current_user) #new_comment= render 'order_comments/form', order_comment: @order.comments.build(user: current_user)
= link_to_top = link_to_top

View file

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

View file

@ -0,0 +1,3 @@
<div class="trix-content">
<%= yield -%>
</div>

View file

@ -0,0 +1,12 @@
= yield
\
%hr
%ul
%li
%a{href: root_url} Foodsoft
- if FoodsoftConfig[:homepage]
%li
%a{href: FoodsoftConfig[:homepage]} Foodcoop
- if FoodsoftConfig[:help_url]
%li
%a{href: FoodsoftConfig[:help_url]}= t '.help'

View file

@ -0,0 +1,15 @@
= simple_form_for :custom_csv,format: :csv, :url => order_path(@order, view: @view, format: :csv), method: :get do |f|
.modal-header
= close_button :modal
.h3=I18n.t('.orders.custom_csv.description')
.modal-body
= f.input :first, as: :select, collection: custom_csv_collection, label: "1. " + I18n.t('.orders.custom_csv.column')
= f.input :second, as: :select, collection: custom_csv_collection, required: false, label: "2. " + I18n.t('.orders.custom_csv.column')
= f.input :third, as: :select, collection: custom_csv_collection, required: false, label: "3. " + I18n.t('.orders.custom_csv.column')
= f.input :fourth, as: :select, collection: custom_csv_collection, required: false, label: "4. " + I18n.t('.orders.custom_csv.column')
= f.input :fifth, as: :select, collection: custom_csv_collection, required: false, label: "5. " + I18n.t('.orders.custom_csv.column')
= f.input :sixth, as: :select, collection: custom_csv_collection, required: false, label: "6. " + I18n.t('.orders.custom_csv.column')
= f.input :seventh, as: :select, collection: custom_csv_collection, required: false, label: "7. " + I18n.t('.orders.custom_csv.column')
.modal-footer
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
= f.submit class: 'btn btn-primary'

View file

@ -0,0 +1,3 @@
$('#modalContainer').html('#{j(render("custom_csv_form"))}');
$('#modalContainer').modal();
$('#modalContainer').submit(function() {$('#modalContainer').modal('hide');});

View file

@ -9,6 +9,7 @@
%thead %thead
%tr %tr
%th= heading_helper Order, :name %th= heading_helper Order, :name
%th
%th= heading_helper Order, :pickup %th= heading_helper Order, :pickup
%th= heading_helper Order, :ends %th= heading_helper Order, :ends
%th= t '.who_ordered' %th= t '.who_ordered'
@ -17,21 +18,23 @@
- total = 0 - total = 0
- orders.each do |order| - orders.each do |order|
%tr %tr
%td= link_to_ordering(order) %td
= link_to_ordering(order, show: true)
%td
.btn-small= link_to_ordering(order){ t 'ui.edit' }
%td= format_date(order.pickup) unless order.pickup.nil? %td= format_date(order.pickup) unless order.pickup.nil?
%td= format_time(order.ends) unless order.ends.nil? %td= format_time(order.ends) unless order.ends.nil?
- if group_order = order.group_order(ordergroup) - if group_order = order.group_order(ordergroup)
- total += group_order.price - total += group_order.price
%td= "#{show_user group_order.updated_by} (#{format_time(group_order.updated_on)})" %td= "#{show_user group_order.updated_by} (#{format_time(group_order.updated_on)})"
%td.numeric %td.numeric
= link_to_ordering(order, show: true) do = number_to_currency(group_order.price)
= number_to_currency(group_order.price)
- else - else
%td{:colspan => 2} %td{:colspan => 2}
- if total > 0 - if total > 0
%tfooter %tfooter
%tr %tr
%th(colspan="3") %th(colspan="4")
%th= t('.total_sum') + ':' %th= t('.total_sum') + ':'
%th.numeric= number_to_currency(total) %th.numeric= number_to_currency(total)
- else - else

View file

@ -10,3 +10,4 @@
- unless order.stockit? - unless order.stockit?
%li= link_to t('.fax_txt'), order_path(order, format: :txt), {title: t('.download_file')} %li= link_to t('.fax_txt'), order_path(order, format: :txt), {title: t('.download_file')}
%li= link_to t('.fax_csv'), order_path(order, format: :csv), {title: t('.download_file')} %li= link_to t('.fax_csv'), order_path(order, format: :csv), {title: t('.download_file')}
%li= link_to t('.custom_csv'), custom_csv_order_path(order), remote: true

4
bin/importmap Executable file
View file

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

View file

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

View file

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

View file

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

View file

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

4
config/importmap.rb Normal file
View file

@ -0,0 +1,4 @@
# Pin npm packages by running ./bin/importmap
pin "application", preload: true
pin "trix"
pin "@rails/actiontext", to: "actiontext.js"

View file

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

View file

@ -1,25 +1,25 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Define an application-wide content security policy # Define an application-wide content security policy.
# For further information see the following documentation # See the Securing Rails Applications Guide for more information:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy # https://guides.rubyonrails.org/security.html#content-security-policy-header
# Rails.application.config.content_security_policy do |policy| # Rails.application.configure do
# policy.default_src :self, :https # config.content_security_policy do |policy|
# policy.font_src :self, :https, :data # policy.default_src :self, :https
# policy.img_src :self, :https, :data # policy.font_src :self, :https, :data
# policy.object_src :none # policy.img_src :self, :https, :data
# policy.script_src :self, :https # policy.object_src :none
# policy.style_src :self, :https # policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports # # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint" # # policy.report_uri "/csp-violation-report-endpoint"
# end
#
# # 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 # 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 # remove all currency translations, so that we can set the default language and
# have it shown in all other languages too # have it shown in all other languages too
::I18n.available_locales.each do |locale| I18n.available_locales.each do |locale|
unless locale == ::I18n.default_locale unless locale == I18n.default_locale
::I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } }) I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } })
end end
end end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -568,6 +568,7 @@ de:
options: options:
convert_units: Derzeitige Einheiten beibehalten, berechne Mengeneinheit und Preis (wie Synchronisieren). convert_units: Derzeitige Einheiten beibehalten, berechne Mengeneinheit und Preis (wie Synchronisieren).
outlist_absent: Artikel löschen, die nicht in der hochgeladenen Datei sind. outlist_absent: Artikel löschen, die nicht in der hochgeladenen Datei sind.
update_category: Kategorien aus der Datei übernehmen und erstellen.
sample: sample:
juices: Säfte juices: Säfte
nuts: Nüsse nuts: Nüsse
@ -1045,17 +1046,33 @@ de:
error_stale: In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden. error_stale: In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.
notice: Die Bestellung wurde gespeichert. notice: Die Bestellung wurde gespeichert.
errors: errors:
balance_alert: Kontostand im Minus
closed: Diese Bestellung ist bereits abgeschlossen. closed: Diese Bestellung ist bereits abgeschlossen.
no_member: Du bist kein Mitglieder einer Bestellgruppe. no_member: Du bist kein Mitglieder einer Bestellgruppe.
notfound: Fehlerhafte URL, das ist nicht Deine Bestellung. notfound: Fehlerhafte URL, das ist nicht Deine Bestellung.
explanations:
package_fill_level: |
Gebindefüllstand
missing_none: |
Voll
missing_few: |
Wenig fehlt
missing_many: |
Viel fehlt
title: Erklärungen
tolerance_explained: |
Zusätzliche Menge die du bestellen würdest, damit das Gebinde voll wird.
tolerance: Toleranz
form: form:
action_save: Bestellung speichern action_save: Bestellung speichern
new_funds: Neuer Kontostand new_funds: Neuer Kontostand
price: Preis price: Preis
price_per_base_unit: Grundpreis
reset_article_search: Suche zurücksetzen reset_article_search: Suche zurücksetzen
search_article: Artikel suchen... search_article: Artikel suchen...
sum_amount: Gesamtbestellmenge bisher sum_amount: Gesamtbestellmenge bisher
title: Bestellen title: Bestellen
sub_title: Bestellung für %{order_name} aufgeben
total_sum_amount: Gesamtbetrag total_sum_amount: Gesamtbetrag
total_tolerance: Gesamt-Toleranz total_tolerance: Gesamt-Toleranz
units: Gebinde units: Gebinde
@ -1099,7 +1116,6 @@ de:
sum: Summe sum: Summe
title: Dein Bestellergebnis für %{order} title: Dein Bestellergebnis für %{order}
switch_order: switch_order:
remaining: "noch %{remaining}"
title: Laufende Bestellungen title: Laufende Bestellungen
update: update:
error_general: Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat. error_general: Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.
@ -1221,6 +1237,7 @@ de:
footer_2_foodsoft: 'Foodsoft: %{url}' footer_2_foodsoft: 'Foodsoft: %{url}'
footer_3_homepage: 'Foodcoop: %{url}' footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Hilfe: %{url}' footer_4_help: 'Hilfe: %{url}'
help: 'Hilfe'
foodsoft: Foodsoft foodsoft: Foodsoft
footer: footer:
revision: Revision %{revision} revision: Revision %{revision}
@ -1463,6 +1480,9 @@ de:
units_ordered: Bestellte Einheiten units_ordered: Bestellte Einheiten
create: create:
notice: Die Bestellung wurde erstellt. notice: Die Bestellung wurde erstellt.
custom_csv:
description: Wähle die Attribute und deren Reihenfolge für die zu erzeugende CSV Datei
column: Spalte
edit: edit:
title: 'Bestellung bearbeiten: %{name}' title: 'Bestellung bearbeiten: %{name}'
edit_amount: edit_amount:

View file

@ -569,6 +569,7 @@ en:
options: options:
convert_units: Keep current units, recompute unit quantity and price (like synchronize). convert_units: Keep current units, recompute unit quantity and price (like synchronize).
outlist_absent: Delete articles not in uploaded file. outlist_absent: Delete articles not in uploaded file.
update_category: Create and replace categories from uploaded file.
sample: sample:
juices: Juices juices: Juices
nuts: Nuts nuts: Nuts
@ -1047,17 +1048,33 @@ en:
error_stale: Someone else has ordered in the meantime, couldn't update the order. error_stale: Someone else has ordered in the meantime, couldn't update the order.
notice: The order was saved. notice: The order was saved.
errors: errors:
balance_alert: Negative account balance
closed: This order is already closed. closed: This order is already closed.
no_member: You are not a member of an ordergroup. no_member: You are not a member of an ordergroup.
notfound: Incorrect URL, this is not your order. notfound: Incorrect URL, this is not your order.
explanations:
title: Explanations
tolerance: Tolerance
package_fill_level: |
Package Fill Level
missing_none: |
No more missing
missing_few: |
Few missing
missing_many: |
Many missing
tolerance_explained: |
Additional amount you would buy to fill a wholesale package
form: form:
action_save: Save order action_save: Save order
new_funds: New account balance new_funds: New account balance
price: Price price: Price
price_per_base_unit: Base price
reset_article_search: Reset search reset_article_search: Reset search
search_article: Search for articles... search_article: Search for articles...
sum_amount: Current amount sum_amount: Current amount
title: Orders title: Orders
sub_title: Place order for %{order_name}
total_sum_amount: Total amount total_sum_amount: Total amount
total_tolerance: Total tolerance total_tolerance: Total tolerance
units: Units units: Units
@ -1101,7 +1118,6 @@ en:
sum: Sum sum: Sum
title: Your order result for %{order} title: Your order result for %{order}
switch_order: switch_order:
remaining: "%{remaining} remaining"
title: Current orders title: Current orders
update: update:
error_general: The order couldnt be updated due to a bug. error_general: The order couldnt be updated due to a bug.
@ -1224,6 +1240,7 @@ en:
footer_2_foodsoft: 'Foodsoft: %{url}' footer_2_foodsoft: 'Foodsoft: %{url}'
footer_3_homepage: 'Foodcoop: %{url}' footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Help: %{url}' footer_4_help: 'Help: %{url}'
help: 'Help'
foodsoft: Foodsoft foodsoft: Foodsoft
footer: footer:
revision: revision %{revision} revision: revision %{revision}
@ -1473,6 +1490,9 @@ en:
units_ordered: Units ordered units_ordered: Units ordered
create: create:
notice: The order was created. notice: The order was created.
custom_csv:
description: Please choose the order as well as the attributes for the csv file
column: column
edit: edit:
title: 'Edit order: %{name}' title: 'Edit order: %{name}'
edit_amount: edit_amount:
@ -1626,6 +1646,7 @@ en:
who_ordered: Who ordered? who_ordered: Who ordered?
order_download_button: order_download_button:
article_pdf: Article PDF article_pdf: Article PDF
custom_csv: Custom CSV
download_file: Download file download_file: Download file
fax_csv: Fax CSV fax_csv: Fax CSV
fax_pdf: Fax PDF fax_pdf: Fax PDF

View file

@ -515,6 +515,7 @@ es:
options: options:
convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar). convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar).
outlist_absent: Borrar artículos que no están en el archivo subido. outlist_absent: Borrar artículos que no están en el archivo subido.
update_category: Toma las categorías del archivo subido.
sample: sample:
juices: Jugos juices: Jugos
nuts: Nueces nuts: Nueces
@ -929,6 +930,7 @@ es:
action_save: Guardar pedido action_save: Guardar pedido
new_funds: Nuevo balance de cuenta new_funds: Nuevo balance de cuenta
price: Precio price: Precio
price_per_base_unit: Precio de base
reset_article_search: Reinicia la búsqueda reset_article_search: Reinicia la búsqueda
search_article: Busca artículos... search_article: Busca artículos...
sum_amount: Cantidad actual sum_amount: Cantidad actual
@ -1082,6 +1084,7 @@ es:
layouts: layouts:
email: email:
footer_4_help: 'Ayuda: %{url}' footer_4_help: 'Ayuda: %{url}'
help: 'Ayuda'
footer: footer:
revision: revisión %{revision} revision: revisión %{revision}
header: header:
@ -1259,6 +1262,9 @@ es:
units_ordered: Unidades pedidas units_ordered: Unidades pedidas
create: create:
notice: Se ha creado el pedido notice: Se ha creado el pedido
custom_csv:
description: Por favor elija el orden de los atributos así como los atributos para el archivo csv
column: columna
edit: edit:
title: 'Edita pedido: %{name}' title: 'Edita pedido: %{name}'
edit_amount: edit_amount:

View file

@ -678,6 +678,7 @@ fr:
action_save: Enregistrer ta commande action_save: Enregistrer ta commande
new_funds: Nouveau solde new_funds: Nouveau solde
price: Prix price: Prix
price_per_base_unit: Prix de base
reset_article_search: Réinitialiser la recherche reset_article_search: Réinitialiser la recherche
search_article: Rechercher des produits... search_article: Rechercher des produits...
sum_amount: Quantité déjà commandée sum_amount: Quantité déjà commandée
@ -834,6 +835,7 @@ fr:
email: email:
footer_3_homepage: 'Boufcoop: %{url}' footer_3_homepage: 'Boufcoop: %{url}'
footer_4_help: 'Aide: %{url}' footer_4_help: 'Aide: %{url}'
help: 'Aide'
footer: footer:
revision: révision %{revision} revision: révision %{revision}
header: header:
@ -1010,6 +1012,9 @@ fr:
units_ordered: Unités commandées units_ordered: Unités commandées
create: create:
notice: La commande a bien été définie. notice: La commande a bien été définie.
custom_csv:
description: Veuillez choisir l'ordre des attributs ainsi que les attributs pour le fichier csv
column: colonne
edit: edit:
title: 'Modifier la commande: %{name}' title: 'Modifier la commande: %{name}'
edit_amount: edit_amount:

View file

@ -539,6 +539,7 @@ nl:
options: options:
convert_units: Bestaande eenheden behouden, herbereken groothandelseenheid en prijs (net als synchronizeren). convert_units: Bestaande eenheden behouden, herbereken groothandelseenheid en prijs (net als synchronizeren).
outlist_absent: Artikelen die niet in het bestand voorkomen, verwijderen. outlist_absent: Artikelen die niet in het bestand voorkomen, verwijderen.
upload_category: Categorieën overnemen uit bestand.
sample: sample:
juices: Sappen juices: Sappen
nuts: Noten nuts: Noten
@ -1017,6 +1018,7 @@ nl:
error_stale: In de tussentijd heeft iemand anders ook bestelt, daarom kon de bestelling niet bijgewerkt worden. error_stale: In de tussentijd heeft iemand anders ook bestelt, daarom kon de bestelling niet bijgewerkt worden.
notice: Bestelling opgeslagen. notice: Bestelling opgeslagen.
errors: errors:
balance_alert: Accountsaldo in het rood
closed: Deze bestelling is al gesloten. closed: Deze bestelling is al gesloten.
no_member: Je bent geen lid van dit huishouden. no_member: Je bent geen lid van dit huishouden.
notfound: Foute URL, dit is niet jouw bestelling. notfound: Foute URL, dit is niet jouw bestelling.
@ -1024,10 +1026,12 @@ nl:
action_save: Bestelling opslaan action_save: Bestelling opslaan
new_funds: Nieuw tegoed new_funds: Nieuw tegoed
price: Prijs price: Prijs
price_per_base_unit: Basisprjis
reset_article_search: Alles tonen reset_article_search: Alles tonen
search_article: Artikelen zoeken... search_article: Artikelen zoeken...
sum_amount: Huidig totaalbedrag sum_amount: Huidig totaalbedrag
title: Bestellen title: Bestellen
sub_title: Plaats bestelling voor %{order_name}
total_sum_amount: Totalbedrag total_sum_amount: Totalbedrag
total_tolerance: Totale tolerantie total_tolerance: Totale tolerantie
units: Eenheden units: Eenheden
@ -1071,7 +1075,6 @@ nl:
sum: Som sum: Som
title: Jouw bestelling voor %{order} title: Jouw bestelling voor %{order}
switch_order: switch_order:
remaining: "nog %{remaining}"
title: Lopende bestellingen title: Lopende bestellingen
update: update:
error_general: Er is een probleem opgetreden, de bestelling kon niet bijgewerkt worden. error_general: Er is een probleem opgetreden, de bestelling kon niet bijgewerkt worden.
@ -1194,6 +1197,7 @@ nl:
footer_2_foodsoft: 'Foodsoft: %{url}' footer_2_foodsoft: 'Foodsoft: %{url}'
footer_3_homepage: 'Foodcoop: %{url}' footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Help: %{url}' footer_4_help: 'Help: %{url}'
help: 'Help'
foodsoft: Foodsoft foodsoft: Foodsoft
footer: footer:
revision: revisie %{revision} revision: revisie %{revision}
@ -1438,6 +1442,9 @@ nl:
units_ordered: Bestelde eenheden units_ordered: Bestelde eenheden
create: create:
notice: De bestelling is aangemaakt. notice: De bestelling is aangemaakt.
custom_csv:
description: Kies de volgorde van de attributen en de attributen voor het csv-bestand
column: kolom
edit: edit:
title: 'Bestelling aanpassen: %{name}' title: 'Bestelling aanpassen: %{name}'
edit_amount: edit_amount:

View file

@ -1,4 +1,6 @@
Rails.application.routes.draw do Rails.application.routes.draw do
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
get "order_comments/new" get "order_comments/new"
get "comments/new" get "comments/new"
@ -45,6 +47,7 @@ Rails.application.routes.draw do
get :receive get :receive
post :receive post :receive
get :custom_csv
get :receive_on_order_article_create get :receive_on_order_article_create
get :receive_on_order_article_update get :receive_on_order_article_update
end end

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

@ -0,0 +1,26 @@
# This migration comes from action_text (originally 20180528164100)
class CreateActionTextTables < ActiveRecord::Migration[6.0]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :action_text_rich_texts, id: primary_key_type do |t|
t.string :name, null: false
t.text :body, size: :long
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.timestamps
t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
end
end
private
def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[primary_key_type, foreign_key_type]
end
end

View file

@ -0,0 +1,10 @@
class MigrateMessageBodyToActionText < ActiveRecord::Migration[7.0]
include ActionView::Helpers::TextHelper
def change
rename_column :messages, :body, :body_old
Message.all.each do |message|
message.update_attribute(:body, simple_format(message.body_old))
end
remove_column :messages, :body_old
end
end

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