Compare commits

..

73 commits

Author SHA1 Message Date
e78d1ad072 move automatic invoices to plugin
changes on deposit calculation

tiny changes on group order invoice pdf
2023-10-16 09:06:05 +02:00
42a1773a87 Merge branch 'master' of https://git.local-it.org/Foodsoft/foodsoft 2023-10-10 16:42:44 +02:00
Philipp Rothmann
4df78def01 fix: documents sort sql needs Arel.sql 2023-10-02 22:49:02 +02:00
Philipp Rothmann
37fb489125 continue development after release 2023-10-02 22:49:02 +02:00
Philipp Rothmann
6abf998b56 fix: documents sort sql needs Arel.sql 2023-10-02 22:48:24 +02:00
kidhab
bca4576b0f
Suppress net protocol errors (#1015) 2023-09-19 10:54:04 +02:00
kidhab
86db9ef96b Unify select field (remove dots) 2023-09-18 21:32:58 +02:00
kidhab
52942f5846 Update Rails version to fix security vulnerabilities 2023-09-18 20:48:46 +02:00
kidhab
8dbb888f0f Remove sd_notify gem
Functionality is included in in puma since 6.1.0
2023-09-18 18:50:41 +02:00
dependabot[bot]
d9e4af29d8
Bump puma from 6.0.2 to 6.3.1 (#1020)
Bumps [puma](https://github.com/puma/puma) from 6.0.2 to 6.3.1.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v6.0.2...v6.3.1)

---
updated-dependencies:
- dependency-name: puma
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 18:47:27 +02:00
kidhab
2e72e1f73f Fix: Server error on logout if redirect to other host 2023-09-18 18:05:57 +02:00
Philipp Rothmann
55234b4e27 continue development after release 2023-09-09 17:01:48 +02:00
Philipp Rothmann
e194c68397 chore: bump version to 4.8.0 2023-09-09 10:52:39 +02:00
Philipp Rothmann
e1b5824830 update changelog v4.8 2023-09-09 10:52:39 +02:00
Philipp Rothmann
91f27a0a48 chore: update chrowdin translations 2023-09-04 13:01:04 +02:00
Philipp Rothmann
caa32de30c fix: rubocop violation 2023-08-23 12:47:58 +02:00
Philipp Rothmann
1e63c59a8a fix: loading trix editor overwrite in production 2023-08-23 12:17:32 +02:00
Philipp Rothmann
a96f21134e feat(messages): attachment retention task 2023-08-04 12:48:15 +02:00
Philipp Rothmann
bcf47ec92b feat(messages): add max file size for attachments 2023-08-04 12:48:15 +02:00
Philipp Rothmann
ef6d6aa368 feat(messages): use trix editor in messages 2023-08-04 12:48:15 +02:00
Philipp Rothmann
c4a53caf52 feat: add actiontext and trix editor 2023-08-04 12:48:15 +02:00
Philipp Rothmann
9282590c06 fix: update setup-chromedriver github action 2023-08-04 12:35:58 +02:00
Philipp Rothmann
817e409a2b fix test 2023-07-14 10:27:20 +02:00
Philipp Rothmann
e80ec9c1ce change tests to use assert_select 2023-07-14 10:27:20 +02:00
Philipp Rothmann
7f23b4784c feat(finance): show sum of ordergroup balances 2023-07-14 10:27:20 +02:00
Harald Reingruber
b07653b34f Add explanation comment to .gitattributes 2023-07-03 16:12:12 +02:00
Harald Reingruber
c442327275 Fix line endings for Windows docker environment 2023-07-03 16:12:12 +02:00
Philipp Rothmann
33034e66b8 fix: add null checks for articles convert_units
Prevents division by zero exception because of a unit beeing 0.
A Unit becomes also zero e.g. when a comma symbol is used Unit.new("0,9kg") == 0

fixes #1014
2023-06-22 22:49:22 +02:00
kidhab
45e2668cea Update mail gem to .8.1 which fixes the permission error
Revert libv8 version
2023-06-17 14:03:58 +02:00
Philipp Rothmann
5f2130ca44 fix: rubocop todo EmptyExampleGroup wildcard 2023-06-17 13:44:21 +02:00
Philipp Rothmann
913136bb72 fix: invalid params request test
fixes #999
2023-06-17 13:31:43 +02:00
kidhab
4ac5bcae06 Update Ruby version and add info about dev packages 2023-06-17 10:33:15 +02:00
Philipp Rothmann
37b3b4523a fix: github action mysqladmin -> mariadb-admin ping 2023-06-16 13:33:21 +02:00
Philipp Rothmann
a1682932ac fix: price_markup with value nil gives exception
fixes #1011
2023-06-16 13:20:33 +02:00
Philipp Rothmann
026c3a6285
introduce importmaps (#983)
* 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>

* fix: rubocop violations

---------

Co-authored-by: FGU <fgu@pragma-shift.net>
2023-06-14 13:29:31 +02:00
dependabot[bot]
a8b2f387db
Bump doorkeeper from 5.6.2 to 5.6.6 (#1010)
Bumps [doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) from 5.6.2 to 5.6.6.
- [Release notes](https://github.com/doorkeeper-gem/doorkeeper/releases)
- [Changelog](https://github.com/doorkeeper-gem/doorkeeper/blob/main/CHANGELOG.md)
- [Commits](https://github.com/doorkeeper-gem/doorkeeper/compare/v5.6.2...v5.6.6)

---
updated-dependencies:
- dependency-name: doorkeeper
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 08:18:33 +02:00
Philipp Rothmann
2151835afb fix: rubocop violation 2023-06-12 13:08:36 +02:00
kidhab
20dc8b8b82 Bump Ruby version to latest in 2.7 series 2023-06-10 10:54:03 +02:00
kidhab
e4f91ef67a
Fill availability column at article export
closes #884
2023-06-10 10:47:47 +02:00
kidhab
c50ba6eda5
feat: Disable member list via configuration (#990) 2023-06-10 10:32:16 +02:00
kidhab
075f3cfa1a
Make date configurable via locales (#997) 2023-06-10 10:31:22 +02:00
dependabot[bot]
64b99038e6
Bump nokogiri from 1.13.10 to 1.15.2 (#1005)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.10 to 1.15.2.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.10...v1.15.2)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-09 20:06:14 +02:00
dependabot[bot]
7fe5fb4592
Bump rack from 2.2.5 to 2.2.7 (#1004)
Bumps [rack](https://github.com/rack/rack) from 2.2.5 to 2.2.7.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v2.2.5...v2.2.7)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-09 19:39:32 +02:00
8b0e03ff60 downgrade haml to make deface work 2023-06-09 19:02:41 +02:00
Philipp Rothmann
4bfa87d258 move CORS setup to initializer 2023-06-09 17:35:05 +02:00
Philipp Rothmann
20a67becf5 fix: assets precompile by using terser 2023-06-09 17:35:05 +02:00
Philipp Rothmann
91e07ab660 fix external link allow_other_host 2023-06-09 17:35:05 +02:00
Philipp Rothmann
285441cb4b fix group order matrix pdf 2023-06-09 17:35:05 +02:00
Philipp Rothmann
fb2b4d8a8a chore: rubocop
chore: fix api test conventions

chore: rubocop -A spec/

chore: more rubocop -A

fix failing test

rubocop fixes

removes helper methods that are in my opinion dead code

more rubocop fixes

rubocop -a --auto-gen-config
2023-06-09 17:35:05 +02:00
Philipp Rothmann
f6fb804bbe chore: update Gemfile.lock 2023-06-09 17:35:05 +02:00
Philipp Rothmann
a7775f5a98 add setup_storage to stock_config 2023-06-09 17:35:05 +02:00
FGU
b06656ba80 fix docker-compose 2023-06-09 17:35:05 +02:00
Philipp Rothmann
6e721db654 upgrade dockerfile to rails7 2023-06-09 17:35:05 +02:00
45ae192891 move BigDecimal.new to BigDecimal() 2023-06-09 17:35:05 +02:00
808baa5a98 change .search to .ransack for updated ransack gem 2023-06-09 17:35:05 +02:00
Philipp Rothmann
5cbe8dd968 fix database_config 2023-06-09 17:35:05 +02:00
Philipp Rothmann
34e238466f fix mail file permission bug 2023-06-09 17:35:05 +02:00
5fb10ec686 make foodsoft run for dev on rails 7 and ruby 2.7 2023-06-09 17:35:05 +02:00
50bf879fbf resolve zeitwerk issues 2023-06-09 17:35:05 +02:00
ea248a5f28 removing concerns from autoload path 2023-06-09 17:35:05 +02:00
4ff44aed4c mv lib to app/lib due to upgrade 2023-06-09 17:35:05 +02:00
3d81dd6b57 rails up to 7.0and ruby to 2.7.2 2023-06-09 17:35:05 +02:00
Philipp Rothmann
c67e9b5be8
Replace apivore with rswag for api tests (#969)
* Replace apivore api tests with rswag
* move to OpenAPI Spec 3.0.1
* a swagger UI is now reachable at http://localhost:3000/api-docs/index.html
*  swagger file is generated by running  `RAILS_ENV=test rails rswag`
    and it was moved from /docs/swagger.v1.yml to /swagger/v1/swagger.yml

---------

Co-authored-by: viehlieb <pf@pragma-shift.net>
2023-05-12 11:11:48 +02:00
hamaryns
8604e27fe9 Spelfouten, maar ook verbeteringen in Nederlands (#954)
* Spelfouten, maar ook verbeteringen in Nederlands

Correct spelling errors and improvements of Dutch

* Update nl.yml

* Update nl.yml

some more Dutch improvements
2023-04-21 18:58:06 +02:00
nurp
f2d5936cf0
Turkish language support added (#995)
* Added Turkish translation with help of ChatGPT

* Changed 'article' and 'item' to 'ürün' and addedtranslations for messages plugin

* added translation for the rest of plugins

* merge conflicts

* fix tr.yml in messages plugin

* Corrected more translations

---------

Co-authored-by: Nurp <>
2023-04-12 21:42:03 +02:00
kidhab
c01c16ecdb
Specify an URL to redirect after logout via settings (#989) 2023-03-30 10:05:47 +02:00
dependabot[bot]
67d0492ac4
Bump rack from 2.2.4 to 2.2.6.4 (#986)
Bumps [rack](https://github.com/rack/rack) from 2.2.4 to 2.2.6.4.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/2.2.4...v2.2.6.4)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-29 17:37:04 +02:00
dependabot[bot]
5f00a39841
Bump globalid from 1.0.0 to 1.0.1 (#978)
Bumps [globalid](https://github.com/rails/globalid) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/rails/globalid/releases)
- [Commits](https://github.com/rails/globalid/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: globalid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-29 17:34:38 +02:00
kidhab
8420323c92
Show a foodcoop's name as subtitle at login screen (#957) 2023-03-29 16:01:00 +02:00
kidhab
e0f63eebdc
Open external websites in new browser window (#981)
Usually the Foodcoop's website and the help pages are external
resources. If they load in the same window one could forget to logout
from the Foodsoft.
2023-03-29 16:00:18 +02:00
kidhab
a7a0830d43
Show order note as tooltip (#965) 2023-03-29 15:15:59 +02:00
Philipp Rothmann
503ed6c379
Add home controller test (#972)
Co-authored-by: viehlieb <pf@pragma-shift.net>
Co-authored-by: Tobias Kneuker <tk@pragma-shift.net>
2023-03-25 18:20:13 +01:00
decentral1se
debce2a635
docs: roadmap & call (#984)
Co-authored-by: decentral1se <cellarspoon@riseup.net>
2023-03-05 14:07:49 +01:00
495 changed files with 13596 additions and 7798 deletions

View file

@ -1,145 +0,0 @@
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

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
# Fixes line endings for Windows (Docker) environment, which are by default converted to crlf
* text=auto
*.sh text eol=lf
proc-start text eol=lf
Rakefile text eol=lf

View file

@ -15,7 +15,7 @@ jobs:
MYSQL_DATABASE: test
MYSQL_ROOT_PASSWORD: password
options: >-
--health-cmd "mysqladmin ping"
--health-cmd "mariadb-admin ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
@ -35,7 +35,9 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v2
- name: Setup chromedriver
uses: nanasess/setup-chromedriver@v1.0.1
uses: nanasess/setup-chromedriver@v2
with:
chromedriver-version: '115.0.5790.170' # https://github.com/nanasess/setup-chromedriver/issues/200
- name: Setup ruby
uses: ruby/setup-ruby@v1
with:

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
2.7.2
2.7.8

View file

@ -1,3 +1,36 @@
# Foodsoft 4.8.0
* feat: Show total sums for ordergroup finances [#1017](https://github.com/foodcoops/foodsoft/pull/1017)
* feat: Richtext Messages and Attachments with Actiontext [#918](https://github.com/foodcoops/foodsoft/issues/918)
* feat: Make date configurable via locales [#997](https://github.com/foodcoops/foodsoft/pull/997)
* feat: Turkish language support added [#995](https://github.com/foodcoops/foodsoft/pull/995)
* feat: Disable member list via configuration [#990](https://github.com/foodcoops/foodsoft/pull/990)
* feat: Specify an URL to redirect after logout via settings #989
* feat: introduce importmaps [#983](https://github.com/foodcoops/foodsoft/pull/983)
* feat: ruby 2.7.2 and rails 7 upgrade [#979](https://github.com/foodcoops/foodsoft/pull/979)
* feat: Add home controller test [#972](https://github.com/foodcoops/foodsoft/pull/972)
* feat: Replace apivore with rswag for api tests [#969](https://github.com/foodcoops/foodsoft/pull/969)
* feat: increase test coverage [#966](https://github.com/foodcoops/foodsoft/pull/966)
* feat: Show order note as tooltip [#965](https://github.com/foodcoops/foodsoft/pull/965)
* feat: Add sd_notify [#961](https://github.com/foodcoops/foodsoft/pull/961)
* feat: Show instance name at login screen [#957](https://github.com/foodcoops/foodsoft/pull/957)
* feat: Enabled systemd socket activation [#942](https://github.com/foodcoops/foodsoft/pull/942)
* feat: Add table_print gem for debugging ActiveRecord queries in the console [#935](https://github.com/foodcoops/foodsoft/pull/935)
* feat: Add admin UI for SupplierCategories (supplier_categories) [#930](https://github.com/foodcoops/foodsoft/pull/930)
* fix: add null checks for articles convert_units [33034e6](https://github.com/foodcoops/foodsoft/commit/33034e66b88968dedc5289425e1eff847ee67e12)
* fix: downgrade haml to make deface work [#1003](https://github.com/foodcoops/foodsoft/pull/1003)
* fix: dutch translation errors [#954](https://github.com/foodcoops/foodsoft/pull/954)
* fix: Fixe filtering of active ordergroups [#934](https://github.com/foodcoops/foodsoft/pull/934)
* fix: Change password validation to allow longer passwords [#923](https://github.com/foodcoops/foodsoft/pull/923)
* fix: Invoice: change label "delivery" to "stock delivery" [#922](https://github.com/foodcoops/foodsoft/pull/922)
* fix: Allow decimal numbers in transaction collections [#921](https://github.com/foodcoops/foodsoft/pull/921)
* fix: Add validation of more article fields [#917](https://github.com/foodcoops/foodsoft/pull/917/files)
* fix: Add default time_zone [#912](https://github.com/foodcoops/foodsoft/pull/912)
* fix: Rename Piwik to Matomo [#911](https://github.com/foodcoops/foodsoft/pull/911/files)
* fix: Change instructions to rbenv [#910](https://github.com/foodcoops/foodsoft/pull/910/files)
# Foodsoft 4.7.1
(31 December 2020)

View file

@ -15,9 +15,7 @@ ENV PORT=3000 \
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock ./
COPY plugins/ ./plugins
COPY config/ ./config
COPY . ./
# install dependencies and generate crontab
RUN buildDeps='libmagic-dev' && \
@ -32,8 +30,6 @@ RUN buildDeps='libmagic-dev' && \
\
bundle exec whenever >crontab
COPY . ./
# compile assets with temporary mysql server
RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
export SECRET_KEY_BASE=thisisnotimportantnow && \

View file

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

105
Gemfile
View file

@ -1,74 +1,77 @@
# A sample Gemfile
source "https://rubygems.org"
source 'https://rubygems.org'
gem "rails", '~> 7.0'
gem 'mail', '~> 2.7.1' # bug with mail 2.8.0 https://github.com/mikel/mail/issues/1489
gem 'rails', '~> 7.0', '>=7.0.4.1'
gem 'sassc-rails'
gem 'less-rails'
gem 'sassc-rails'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', platforms: :ruby
gem 'jquery-rails'
gem 'select2-rails'
gem 'rails_tokeninput'
gem 'bootsnap', require: false
gem 'bootstrap-datepicker-rails'
gem 'date_time_attribute'
gem 'rails-assets-listjs', '0.2.0.beta.4' # remember to maintain list.*.js plugins and template engines on update
gem 'i18n-js', '~> 3.0.0.rc8'
gem 'jquery-rails'
gem 'rails-assets-listjs', '0.2.0.beta.4' # remember to maintain list.*.js plugins and template engines on update
gem 'rails-i18n'
gem 'bootsnap', require: false
gem 'rails_tokeninput'
gem 'select2-rails'
gem 'mysql2'
gem 'prawn'
gem 'prawn-table'
gem 'haml', '~> 5.0'
gem 'haml-rails'
gem 'kaminari'
gem 'simple_form'
gem 'inherited_resources'
gem 'active_model_serializers', '~> 0.10.0'
gem 'acts_as_tree'
gem 'attribute_normalizer'
gem 'daemons'
gem 'doorkeeper'
gem 'doorkeeper-i18n'
gem 'haml', '~> 5.0'
gem 'haml-rails'
gem 'ice_cube'
gem 'inherited_resources'
gem 'kaminari'
gem 'mysql2'
gem 'net-ftp'
gem 'net-http'
gem 'prawn'
gem 'prawn-table'
gem 'puma'
gem 'rack-cors', require: 'rack/cors'
gem 'active_model_serializers', '~> 0.10.0'
gem 'twitter-bootstrap-rails', '~> 2.2.8'
gem 'rails-settings-cached', '= 0.4.3' # caching breaks tests until Rails 5 https://github.com/huacnlee/rails-settings-cached/issues/73
gem 'ransack'
gem 'resque'
gem 'ruby-units'
gem 'simple_form'
gem 'simple-navigation', '~> 3.14.0' # 3.x for simple_navigation_bootstrap
gem 'simple-navigation-bootstrap'
gem 'sprockets', '< 4'
gem 'ransack'
gem 'acts_as_tree'
gem 'rails-settings-cached', '= 0.4.3' # caching breaks tests until Rails 5 https://github.com/huacnlee/rails-settings-cached/issues/73
gem 'resque'
gem 'puma'
gem 'sd_notify'
gem 'twitter-bootstrap-rails', '~> 2.2.8'
gem 'whenever', require: false # For defining cronjobs, see config/schedule.rb
gem 'ruby-units'
gem 'attribute_normalizer'
gem 'ice_cube'
# 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-xls'
gem 'spreadsheet'
gem 'exception_notification'
gem 'gaffe'
gem 'ruby-filemagic'
gem 'mime-types'
gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114
gem "image_processing", "~> 1.12"
gem "importmap-rails", "~> 1.1"
gem 'midi-smtp-server'
gem 'mime-types'
gem 'recurring_select', git: 'https://github.com/gregschmit/recurring_select'
gem 'roo'
gem 'roo-xls'
gem 'rswag-api'
gem 'rswag-ui'
gem 'ruby-filemagic'
gem 'spreadsheet'
gem "terser", "~> 1.1"
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
gem 'foodsoft_wiki', path: 'plugins/wiki'
gem 'foodsoft_messages', path: 'plugins/messages'
gem 'foodsoft_documents', path: 'plugins/documents'
gem 'foodsoft_discourse', path: 'plugins/discourse'
gem 'foodsoft_documents', path: 'plugins/documents'
gem 'foodsoft_links', path: 'plugins/links'
gem 'foodsoft_messages', path: 'plugins/messages'
gem 'foodsoft_polls', path: 'plugins/polls'
gem 'foodsoft_wiki', path: 'plugins/wiki'
gem 'foodsoft_automatic_invoices', path: 'plugins/automatic_invoices'
# plugins not enabled by default
# gem 'foodsoft_current_orders', path: 'plugins/current_orders'
@ -76,10 +79,10 @@ gem 'foodsoft_polls', path: 'plugins/polls'
# gem 'foodsoft_uservoice', path: 'plugins/uservoice'
group :development do
gem 'sqlite3', '~> 1.3.6'
gem 'mailcatcher'
gem 'web-console'
gem 'listen'
gem 'mailcatcher'
gem 'sqlite3', '~> 1.3.6'
gem 'web-console'
# Better error output
gem 'better_errors'
@ -107,26 +110,20 @@ group :development, :test do
end
group :test do
gem 'rspec-rails'
gem 'apparition' # Capybara javascript driver
gem 'capybara'
gem 'connection_pool'
gem 'database_cleaner'
gem 'factory_bot_rails'
gem 'faker'
gem 'capybara'
gem 'apparition' # Capybara javascript driver
gem 'database_cleaner'
gem 'connection_pool'
gem 'rspec-rails'
# need to include rspec components before i18n-spec or rake fails in test environment
gem 'i18n-spec'
gem 'rspec-core'
gem 'rspec-rerun'
gem 'i18n-spec'
gem 'rails-controller-testing'
# code coverage
gem 'simplecov', require: false
gem 'simplecov-lcov', require: false
# api
gem 'rswag-specs'
gem 'hashie', '~> 3.4.6', require: false # https://github.com/westfieldlabs/apivore/issues/114
end
gem "importmap-rails", "~> 1.1"
gem "image_processing", "~> 1.12"
gem "terser", "~> 1.1"

View file

@ -1,11 +1,3 @@
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
@ -24,6 +16,13 @@ GIT
acts_as_versioned (0.6.0)
activerecord (>= 3.0.9)
PATH
remote: plugins/automatic_invoices
specs:
foodsoft_automatic_invoices (0.0.1)
deface (~> 1.9)
rails
PATH
remote: plugins/discourse
specs:
@ -78,47 +77,47 @@ PATH
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
actioncable (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
actionmailbox (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
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)
actionmailer (7.0.8)
actionpack (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activesupport (= 7.0.8)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.4)
actionview (= 7.0.4)
activesupport (= 7.0.4)
rack (~> 2.0, >= 2.2.0)
actionpack (7.0.8)
actionview (= 7.0.8)
activesupport (= 7.0.8)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.4)
actionpack (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
actiontext (7.0.8)
actionpack (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.4)
activesupport (= 7.0.4)
actionview (7.0.8)
activesupport (= 7.0.8)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -128,22 +127,22 @@ GEM
activemodel (>= 4.1, < 7.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.4)
activesupport (= 7.0.4)
activejob (7.0.8)
activesupport (= 7.0.8)
globalid (>= 0.3.6)
activemodel (7.0.4)
activesupport (= 7.0.4)
activerecord (7.0.4)
activemodel (= 7.0.4)
activesupport (= 7.0.4)
activestorage (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activesupport (= 7.0.4)
activemodel (7.0.8)
activesupport (= 7.0.8)
activerecord (7.0.8)
activemodel (= 7.0.8)
activesupport (= 7.0.8)
activestorage (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activesupport (= 7.0.8)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.4)
activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -194,7 +193,7 @@ GEM
execjs
coffee-script-source (1.12.2)
commonjs (0.2.7)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.2)
connection_pool (2.3.0)
content_for_in_controllers (0.0.2)
crass (1.0.6)
@ -218,7 +217,7 @@ GEM
diff-lcs (1.5.0)
diffy (3.4.2)
docile (1.4.0)
doorkeeper (5.6.2)
doorkeeper (5.6.6)
railties (>= 5)
doorkeeper-i18n (5.2.6)
doorkeeper (>= 5.2)
@ -240,7 +239,7 @@ GEM
ffi (1.15.5)
gaffe (1.2.0)
rails (>= 4.0.0)
globalid (1.0.0)
globalid (1.0.1)
activesupport (>= 5.0)
haml (5.2.2)
temple (>= 0.8.0)
@ -255,7 +254,7 @@ GEM
activesupport (>= 5.2)
hashie (3.4.6)
htmlentities (4.3.4)
i18n (1.12.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
i18n-js (3.0.11)
i18n (>= 0.6.6, < 2)
@ -306,11 +305,14 @@ GEM
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.19.1)
loofah (2.21.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
nokogiri (>= 1.12.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
mailcatcher (0.2.4)
eventmachine
haml
@ -330,13 +332,18 @@ GEM
mime-types-data (3.2022.0105)
mini_magick (4.12.0)
mini_mime (1.1.2)
minitest (5.17.0)
minitest (5.18.0)
mono_logger (1.1.1)
msgpack (1.6.0)
multi_json (1.15.0)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
mysql2 (0.5.4)
net-ftp (0.2.0)
net-protocol
time
net-http (0.3.2)
uri
net-imap (0.3.4)
date
net-protocol
@ -346,11 +353,11 @@ GEM
timeout
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.10-x86_64-linux)
nio4r (2.5.9)
nokogiri (1.15.2-x86_64-linux)
racc (~> 1.4)
parallel (1.22.1)
parser (3.2.0.0)
parallel (1.23.0)
parser (3.2.2.1)
ast (~> 2.4.1)
pdf-core (0.9.0)
polyglot (0.3.5)
@ -369,41 +376,38 @@ GEM
binding_of_caller (~> 1.0)
pry (~> 0.13)
public_suffix (5.0.1)
puma (6.0.2)
puma (6.3.1)
nio4r (~> 2.0)
racc (1.6.2)
rack (2.2.5)
racc (1.7.0)
rack (2.2.7)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-protection (3.0.5)
rack
rack-test (2.0.2)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.4)
actioncable (= 7.0.4)
actionmailbox (= 7.0.4)
actionmailer (= 7.0.4)
actionpack (= 7.0.4)
actiontext (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activemodel (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
rails (7.0.8)
actioncable (= 7.0.8)
actionmailbox (= 7.0.8)
actionmailer (= 7.0.8)
actionpack (= 7.0.8)
actiontext (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activemodel (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
bundler (>= 1.15.0)
railties (= 7.0.4)
railties (= 7.0.8)
rails-assets-listjs (0.2.0.beta.4)
railties (>= 3.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails-i18n (7.0.6)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
@ -411,9 +415,9 @@ GEM
rails (>= 4.2.0)
rails_tokeninput (1.7.0)
railties (>= 3.1.0)
railties (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
railties (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -434,7 +438,7 @@ GEM
redis-namespace (1.10.0)
redis (>= 4)
ref (2.0.0)
regexp_parser (2.6.1)
regexp_parser (2.8.0)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
@ -476,7 +480,7 @@ GEM
rspec-support (3.12.0)
rswag-api (2.7.0)
railties (>= 3.1, < 7.1)
rswag-specs (2.7.0)
rswag-specs (2.9.0)
activesupport (>= 3.1, < 7.1)
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
@ -484,18 +488,18 @@ GEM
rswag-ui (2.7.0)
actionpack (>= 3.1, < 7.1)
railties (>= 3.1, < 7.1)
rubocop (1.43.0)
rubocop (1.50.2)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-ast (1.28.1)
parser (>= 3.2.1.0)
rubocop-rails (2.17.4)
activesupport (>= 4.2.0)
rack (>= 1.1)
@ -505,7 +509,7 @@ GEM
ruby-filemagic (0.7.3)
ruby-ole (1.2.12.2)
ruby-prof (1.4.5)
ruby-progressbar (1.11.0)
ruby-progressbar (1.13.0)
ruby-units (3.0.0)
ruby-vips (2.1.4)
ffi (~> 1.12)
@ -521,7 +525,6 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
sd_notify (0.1.1)
select2-rails (4.0.13)
simple-navigation (3.14.0)
activesupport (>= 2.3.2)
@ -569,8 +572,10 @@ GEM
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thor (1.2.1)
thor (1.2.2)
tilt (2.0.11)
time (0.2.2)
date
timeout (0.3.1)
ttfunk (1.7.0)
twitter-bootstrap-rails (2.2.8)
@ -580,13 +585,14 @@ GEM
railties (>= 3.1)
twitter-text (1.14.7)
unf (~> 0.1.0)
tzinfo (2.0.5)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
uri (0.10.0.2)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
@ -605,7 +611,7 @@ GEM
twitter-text
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.6)
zeitwerk (2.6.8)
PLATFORMS
x86_64-linux
@ -631,7 +637,7 @@ DEPENDENCIES
exception_notification
factory_bot_rails
faker
foodsoft_article_import!
foodsoft_automatic_invoices!
foodsoft_discourse!
foodsoft_documents!
foodsoft_links!
@ -652,20 +658,20 @@ DEPENDENCIES
kaminari
less-rails
listen
mail (~> 2.7.1)
mailcatcher
midi-smtp-server
mime-types
mysql2
net-ftp
net-http
prawn
prawn-table
pry-rescue
pry-stack_explorer
puma
rack-cors
rails (~> 7.0)
rails (~> 7.0, >= 7.0.4.1)
rails-assets-listjs (= 0.2.0.beta.4)
rails-controller-testing
rails-i18n
rails-settings-cached (= 0.4.3)
rails_tokeninput
@ -687,7 +693,6 @@ DEPENDENCIES
ruby-prof
ruby-units
sassc-rails
sd_notify
select2-rails
simple-navigation (~> 3.14.0)
simple-navigation-bootstrap

147
README.md
View file

@ -1,124 +1,71 @@
Foodsoft
=========
[Website](https://foodsoft.local-it.org)
[Prototypefund](https://prototypefund.de/project/weiterentwicklung-von-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).
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.
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.
Foodsoft wurde ursprünglich entwickelt und betrieben von [foodcoops.net](https://foodcoops.net/)
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).
#### Zielgruppe
Roadmap
-------
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.
If you'd like to see what is currently bring prioritised for development, check [our roadmap](https://github.com/orgs/foodcoops/projects/1). If you'd like to influence the roadmap, please join our [monthly community call](https://forum.foodcoops.net/t/foodsoft-monthly-community-call/573/6). As of March 2023, Foodsoft has limited development capacity but we are trying to build this up once more. For now, we try to prioritise what we work on, in order to focus our efforts. If your proposed changes are waiting for some time without review, please join the community call to discuss.
#### Vorhaben
Developing
----------
* ✅ Technische Schuld reduzieren
* ✅ Ruby on Rails Upgrade
* ✅ Artikel Import verbessern
(Großhandelschnitstelle)
* ✅ Userexperience Verbessern
> Foodsoft development needs your help! If you want to hack/triage/organise to improve the software, please consider joining our monthly community calls which are announced on [this forum thread](https://forum.foodcoops.net/t/foodsoft-monthly-community-call/573/6). In these calls, we check in with each other, discuss what to prioritise and try to make progress with development and community issues together.
#### Was ist eine Einkaufskooperative?
Get foodsoft [running locally](doc/SETUP_DEVELOPMENT.md),
then visit our [Developing Guidelines](https://github.com/foodcoops/foodsoft/wiki/Developing-Guidelines)
page on the wiki.
![Wie funktioniert eine Einkauskooperative?](./doc/foodcoop-explained.jpg)
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.
State of this Fork
------------------
Deploying
---------
#### Increase Test Coverage
Setup foodsoft to [run in production](doc/SETUP_PRODUCTION.md), or join an existing
[hosting platform](https://foodcoops.net/foodsoft-hosting/).
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)
License
-------
#### Upgrade
Foodsoft is licensed under the [AGPL](https://www.gnu.org/licenses/agpl-3.0.html)
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.
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
For private use, there are no restrictions, but if you give others access to
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).
#### 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)
To make it a little easier, configuration files are exempt, so you can just
install and configure Foodsoft without having to publish your changes. These
files are marked as public domain in the file header.
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

@ -1,7 +1,7 @@
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
require File.expand_path('../config/application', __FILE__)
require File.expand_path('config/application', __dir__)
require 'rake'
require 'rspec-rerun/tasks' if defined?(RSpec) # http://stackoverflow.com/a/16853615/2866660

View file

@ -1 +1 @@
4.7.99
4.8.99

View file

@ -8,6 +8,7 @@
//= require bootstrap-datepicker/locales/bootstrap-datepicker.es
//= require bootstrap-datepicker/locales/bootstrap-datepicker.nl
//= require bootstrap-datepicker/locales/bootstrap-datepicker.fr
//= require bootstrap-datepicker/locales/bootstrap-datepicker.tr
//= require list
//= require list.unlist
//= require list.delay

View file

@ -179,13 +179,17 @@ function updateBalance() {
var balance = groupBalance - total;
$('#new_balance').html(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) {
bgcolor = '#FF0000';
$('#submit_button').attr('disabled', 'disabled')
$('#balance-alert').css('display', 'block')
} else {
$('#submit_button').removeAttr('disabled')
$('#balance-alert').css('display', 'none')
}
// update bgcolor
for (i in itemTotal) {
$('#td_price_' + i).css('background-color', bgcolor);
}
}

View file

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

View file

@ -1,23 +1,11 @@
.missing-many td {
background-color: #ffc590aa;
.list .missing-many td, .list .missing-many:hover td {
background-color: #ebbebe;
}
.missing-many:hover td, .missing-many:focus td {
background-color: #ffc590;
.list .missing-few td, .list .missing-few:hover td {
background-color: #ffee75;
}
.missing-few td {
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;
.list .missing-none td, .list .missing-none:hover td {
background-color: #E4EED6;
}

View file

@ -3,39 +3,39 @@ class Admin::BankAccountsController < Admin::BaseController
def new
@bank_account = BankAccount.new(params[:bank_account])
render :layout => false
render layout: false
end
def edit
@bank_account = BankAccount.find(params[:id])
render action: 'new', layout: false
end
def create
@bank_account = BankAccount.new(params[:bank_account])
if @bank_account.valid? && @bank_account.save
redirect_to update_bank_accounts_admin_finances_url, :status => 303
redirect_to update_bank_accounts_admin_finances_url, status: :see_other
else
render :action => 'new', :layout => false
render action: 'new', layout: false
end
end
def edit
@bank_account = BankAccount.find(params[:id])
render :action => 'new', :layout => false
end
def update
@bank_account = BankAccount.find(params[:id])
if @bank_account.update(params[:bank_account])
redirect_to update_bank_accounts_admin_finances_url, :status => 303
redirect_to update_bank_accounts_admin_finances_url, status: :see_other
else
render :action => 'new', :layout => false
render action: 'new', layout: false
end
end
def destroy
@bank_account = BankAccount.find(params[:id])
@bank_account.destroy
redirect_to update_bank_accounts_admin_finances_url, :status => 303
rescue => error
flash.now[:alert] = error.message
redirect_to update_bank_accounts_admin_finances_url, status: :see_other
rescue StandardError => e
flash.now[:alert] = e.message
render template: 'shared/alert'
end
end

View file

@ -6,6 +6,11 @@ class Admin::BankGatewaysController < Admin::BaseController
render layout: false
end
def edit
@bank_gateway = BankGateway.find(params[:id])
render action: 'new', layout: false
end
def create
@bank_gateway = BankGateway.new(params[:bank_gateway])
if @bank_gateway.valid? && @bank_gateway.save
@ -15,11 +20,6 @@ class Admin::BankGatewaysController < Admin::BaseController
end
end
def edit
@bank_gateway = BankGateway.find(params[:id])
render action: 'new', layout: false
end
def update
@bank_gateway = BankGateway.find(params[:id])

View file

@ -1,5 +1,5 @@
class Admin::ConfigsController < Admin::BaseController
before_action :get_tabs, only: [:show, :list]
before_action :get_tabs, only: %i[show list]
def show
@current_tab = @tabs.include?(params[:tab]) ? params[:tab] : @tabs.first
@ -16,7 +16,7 @@ class Admin::ConfigsController < Admin::BaseController
def update
parse_recurring_selects! params[:config][:order_schedule]
ActiveRecord::Base.transaction do
# TODO support nested configuration keys
# TODO: support nested configuration keys
params[:config].each do |key, val|
FoodsoftConfig[key] = convert_config_value val
end
@ -29,7 +29,7 @@ class Admin::ConfigsController < Admin::BaseController
# Set configuration tab names as `@tabs`
def get_tabs
@tabs = %w(foodcoop payment tasks messages layout language security others)
@tabs = %w[foodcoop payment tasks messages layout language security others]
# allow engines to modify this list
engines = Rails::Engine.subclasses.map(&:instance).select { |e| e.respond_to?(:configuration) }
engines.each { |e| e.configuration(@tabs, self) }
@ -38,16 +38,16 @@ class Admin::ConfigsController < Admin::BaseController
# turn recurring rules into something palatable
def parse_recurring_selects!(config)
if config
for k in [:pickup, :boxfill, :ends] do
if config[k]
# allow clearing it using dummy value '{}' ('' would break recurring_select)
if config[k][:recurr].present? && config[k][:recurr] != '{}'
config[k][:recurr] = ActiveSupport::JSON.decode(config[k][:recurr])
config[k][:recurr] = FoodsoftDateUtil.rule_from(config[k][:recurr]).to_ical if config[k][:recurr]
else
config[k] = nil
end
return unless config
for k in %i[pickup boxfill ends] do
if config[k]
# allow clearing it using dummy value '{}' ('' would break recurring_select)
if config[k][:recurr].present? && config[k][:recurr] != '{}'
config[k][:recurr] = ActiveSupport::JSON.decode(config[k][:recurr])
config[k][:recurr] = FoodsoftDateUtil.rule_from(config[k][:recurr]).to_ical if config[k][:recurr]
else
config[k] = nil
end
end
end

View file

@ -10,21 +10,21 @@ class Admin::FinancesController < Admin::BaseController
def update_bank_accounts
@bank_accounts = BankAccount.order('name')
render :layout => false
render layout: false
end
def update_bank_gateways
@bank_gateways = BankGateway.order('name')
render :layout => false
render layout: false
end
def update_transaction_types
@financial_transaction_classes = FinancialTransactionClass.includes(:financial_transaction_types).order('name ASC')
render :layout => false
render layout: false
end
def update_supplier_categories
@supplier_categories = SupplierCategory.order('name')
render :layout => false
render layout: false
end
end

View file

@ -6,25 +6,25 @@ class Admin::FinancialTransactionClassesController < Admin::BaseController
render layout: false
end
def create
@financial_transaction_class = FinancialTransactionClass.new(params[:financial_transaction_class])
if @financial_transaction_class.save
redirect_to update_transaction_types_admin_finances_url, status: 303
else
render action: 'new', layout: false
end
end
def edit
@financial_transaction_class = FinancialTransactionClass.find(params[:id])
render action: 'new', layout: false
end
def create
@financial_transaction_class = FinancialTransactionClass.new(params[:financial_transaction_class])
if @financial_transaction_class.save
redirect_to update_transaction_types_admin_finances_url, status: :see_other
else
render action: 'new', layout: false
end
end
def update
@financial_transaction_class = FinancialTransactionClass.find(params[:id])
if @financial_transaction_class.update(params[:financial_transaction_class])
redirect_to update_transaction_types_admin_finances_url, status: 303
redirect_to update_transaction_types_admin_finances_url, status: :see_other
else
render action: 'new', layout: false
end
@ -33,9 +33,9 @@ class Admin::FinancialTransactionClassesController < Admin::BaseController
def destroy
@financial_transaction_class = FinancialTransactionClass.find(params[:id])
@financial_transaction_class.destroy!
redirect_to update_transaction_types_admin_finances_url, status: 303
rescue => error
flash.now[:alert] = error.message
redirect_to update_transaction_types_admin_finances_url, status: :see_other
rescue StandardError => e
flash.now[:alert] = e.message
render template: 'shared/alert'
end
end

View file

@ -7,25 +7,25 @@ class Admin::FinancialTransactionTypesController < Admin::BaseController
render layout: false
end
def create
@financial_transaction_type = FinancialTransactionType.new(params[:financial_transaction_type])
if @financial_transaction_type.save
redirect_to update_transaction_types_admin_finances_url, status: 303
else
render action: 'new', layout: false
end
end
def edit
@financial_transaction_type = FinancialTransactionType.find(params[:id])
render action: 'new', layout: false
end
def create
@financial_transaction_type = FinancialTransactionType.new(params[:financial_transaction_type])
if @financial_transaction_type.save
redirect_to update_transaction_types_admin_finances_url, status: :see_other
else
render action: 'new', layout: false
end
end
def update
@financial_transaction_type = FinancialTransactionType.find(params[:id])
if @financial_transaction_type.update(params[:financial_transaction_type])
redirect_to update_transaction_types_admin_finances_url, status: 303
redirect_to update_transaction_types_admin_finances_url, status: :see_other
else
render action: 'new', layout: false
end
@ -34,9 +34,9 @@ class Admin::FinancialTransactionTypesController < Admin::BaseController
def destroy
@financial_transaction_type = FinancialTransactionType.find(params[:id])
@financial_transaction_type.destroy!
redirect_to update_transaction_types_admin_finances_url, status: 303
rescue => error
flash.now[:alert] = error.message
redirect_to update_transaction_types_admin_finances_url, status: :see_other
rescue StandardError => e
flash.now[:alert] = e.message
render template: 'shared/alert'
end
end

View file

@ -3,28 +3,28 @@ class Admin::MailDeliveryStatusController < Admin::BaseController
def index
@maildeliverystatus = MailDeliveryStatus.order(created_at: :desc)
@maildeliverystatus = @maildeliverystatus.where(email: params[:email]) unless params[:email].blank?
@maildeliverystatus = @maildeliverystatus.where(email: params[:email]) if params[:email].present?
@maildeliverystatus = @maildeliverystatus.page(params[:page]).per(@per_page)
end
def show
@maildeliverystatus = MailDeliveryStatus.find(params[:id])
filename = "maildeliverystatus_#{params[:id]}.#{MIME::Types[@maildeliverystatus.attachment_mime].first.preferred_extension}"
send_data(@maildeliverystatus.attachment_data, :filename => filename, :type => @maildeliverystatus.attachment_mime)
send_data(@maildeliverystatus.attachment_data, filename: filename, type: @maildeliverystatus.attachment_mime)
end
def destroy_all
@maildeliverystatus = MailDeliveryStatus.delete_all
redirect_to admin_mail_delivery_status_index_path, notice: t('.notice')
rescue => error
redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: error.message)
rescue StandardError => e
redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: e.message)
end
def destroy
@maildeliverystatus = MailDeliveryStatus.find(params[:id])
@maildeliverystatus.destroy
redirect_to admin_mail_delivery_status_index_path, notice: t('.notice')
rescue => error
redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: error.message)
rescue StandardError => e
redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: e.message)
end
end

View file

@ -2,16 +2,15 @@ class Admin::OrdergroupsController < Admin::BaseController
inherit_resources
def index
@ordergroups = Ordergroup.undeleted.sort_by_param(params["sort"])
@ordergroups = Ordergroup.undeleted.sort_by_param(params['sort'])
if request.format.csv?
send_data OrdergroupsCsv.new(@ordergroups).to_csv, filename: 'ordergroups.csv', type: 'text/csv'
send_data OrdergroupsCsv.new(@ordergroups).to_csv, filename: 'ordergroups.csv',
type: 'text/csv'
end
# if somebody uses the search field:
unless params[:query].blank?
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%")
end
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present?
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
end
@ -19,8 +18,8 @@ class Admin::OrdergroupsController < Admin::BaseController
def destroy
@ordergroup = Ordergroup.find(params[:id])
@ordergroup.mark_as_deleted
redirect_to admin_ordergroups_url, notice: t('admin.ordergroups.destroy.notice')
rescue => error
redirect_to admin_ordergroups_url, alert: t('admin.ordergroups.destroy.error')
redirect_to admin_ordergroups_url, notice: t('.notice')
rescue StandardError => e
redirect_to admin_ordergroups_url, alert: t('.error')
end
end

View file

@ -6,6 +6,11 @@ class Admin::SupplierCategoriesController < Admin::BaseController
render layout: false
end
def edit
@supplier_category = SupplierCategory.find(params[:id])
render action: 'new', layout: false
end
def create
@supplier_category = SupplierCategory.new(params[:supplier_category])
if @supplier_category.valid? && @supplier_category.save
@ -15,11 +20,6 @@ class Admin::SupplierCategoriesController < Admin::BaseController
end
end
def edit
@supplier_category = SupplierCategory.find(params[:id])
render action: 'new', layout: false
end
def update
@supplier_category = SupplierCategory.find(params[:id])

View file

@ -3,16 +3,14 @@ class Admin::UsersController < Admin::BaseController
def index
@users = params[:show_deleted] ? User.deleted : User.undeleted
@users = @users.sort_by_param(params["sort"])
@users = @users.sort_by_param(params['sort'])
@users = @users.includes(:mail_delivery_status)
if request.format.csv?
send_data UsersCsv.new(@users).to_csv, filename: 'users.csv', type: 'text/csv'
end
send_data UsersCsv.new(@users).to_csv, filename: 'users.csv', type: 'text/csv' if request.format.csv?
# if somebody uses the search field:
@users = @users.natural_search(params[:user_name]) unless params[:user_name].blank?
@users = @users.natural_search(params[:user_name]) if params[:user_name].present?
@users = @users.page(params[:page]).per(@per_page)
end
@ -20,17 +18,17 @@ class Admin::UsersController < Admin::BaseController
def destroy
@user = User.find(params[:id])
@user.mark_as_deleted
redirect_to admin_users_url, notice: t('admin.users.destroy.notice')
rescue => error
redirect_to admin_users_url, alert: t('admin.users.destroy.error', error: error.message)
redirect_to admin_users_url, notice: t('.notice')
rescue StandardError => e
redirect_to admin_users_url, alert: t('.error', error: e.message)
end
def restore
@user = User.find(params[:id])
@user.restore
redirect_to admin_users_url, notice: t('admin.users.restore.notice')
rescue => error
redirect_to admin_users_url, alert: t('admin.users.restore.error', error: error.message)
redirect_to admin_users_url, notice: t('.notice')
rescue StandardError => e
redirect_to admin_users_url, alert: t('.error', error: e.message)
end
def sudo

View file

@ -4,7 +4,7 @@ class Admin::WorkgroupsController < Admin::BaseController
def index
@workgroups = Workgroup.order('name ASC')
# if somebody uses the search field:
@workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].blank?
@workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present?
@workgroups = @workgroups.page(params[:page]).per(@per_page)
end
@ -12,8 +12,8 @@ class Admin::WorkgroupsController < Admin::BaseController
def destroy
@workgroup = Workgroup.find(params[:id])
@workgroup.destroy
redirect_to admin_workgroups_url, notice: t('admin.workgroups.destroy.notice')
rescue => error
redirect_to admin_workgroups_url, alert: t('admin.workgroups.destroy.error', error: error.message)
redirect_to admin_workgroups_url, notice: t('.notice')
rescue StandardError => e
redirect_to admin_workgroups_url, alert: t('.error', error: e.message)
end
end

View file

@ -20,29 +20,30 @@ class Api::V1::BaseController < ApplicationController
def require_ordergroup
authenticate
unless current_ordergroup.present?
raise Api::Errors::PermissionRequired.new('Forbidden, must be in an ordergroup')
end
return if current_ordergroup.present?
raise Api::Errors::PermissionRequired, 'Forbidden, must be in an ordergroup'
end
def require_minimum_balance
minimum_balance = FoodsoftConfig[:minimum_balance] or return
if current_ordergroup.account_balance < minimum_balance
raise Api::Errors::PermissionRequired.new(t('application.controller.error_minimum_balance', min: minimum_balance))
end
return unless current_ordergroup.account_balance < minimum_balance
raise Api::Errors::PermissionRequired, t('application.controller.error_minimum_balance', min: minimum_balance)
end
def require_enough_apples
if current_ordergroup.not_enough_apples?
s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples, stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
raise Api::Errors::PermissionRequired.new(s)
end
return unless current_ordergroup.not_enough_apples?
s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples,
stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
raise Api::Errors::PermissionRequired, s
end
def require_config_enabled(config)
unless FoodsoftConfig[config]
raise Api::Errors::PermissionRequired.new(t('application.controller.error_not_enabled', config: config))
end
return if FoodsoftConfig[config]
raise Api::Errors::PermissionRequired, t('application.controller.error_not_enabled', config: config)
end
def skip_session
@ -52,12 +53,12 @@ class Api::V1::BaseController < ApplicationController
def not_found_handler(e)
# remove where-clauses from error message (not suitable for end-users)
msg = e.message.try { |m| m.sub(/\s*\[.*?\]\s*$/, '') } || 'Not found'
render status: 404, json: { error: 'not_found', error_description: msg }
render status: :not_found, json: { error: 'not_found', error_description: msg }
end
def not_acceptable_handler(e)
msg = e.message || 'Data not acceptable'
render status: 422, json: { error: 'not_acceptable', error_description: msg }
render status: :unprocessable_entity, json: { error: 'not_acceptable', error_description: msg }
end
def doorkeeper_unauthorized_render_options(error:)
@ -70,11 +71,11 @@ class Api::V1::BaseController < ApplicationController
def permission_required_handler(e)
msg = e.message || 'Forbidden, user has no access'
render status: 403, json: { error: 'forbidden', error_description: msg }
render status: :forbidden, json: { error: 'forbidden', error_description: msg }
end
# @todo something with ApplicationHelper#show_user
def show_user(user = current_user, **options)
def show_user(user = current_user, **_options)
user.display
end
end

View file

@ -16,7 +16,8 @@ class Api::V1::User::FinancialTransactionsController < Api::V1::BaseController
def create
transaction_type = FinancialTransactionType.find(create_params[:financial_transaction_type_id])
ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user, transaction_type)
ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user,
transaction_type)
render json: ft
end

View file

@ -4,8 +4,8 @@ class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController
before_action -> { doorkeeper_authorize! 'group_orders:user' }
before_action :require_ordergroup
before_action :require_minimum_balance, only: [:create, :update] # destroy is ok
before_action :require_enough_apples, only: [:create, :update] # destroy is ok
before_action :require_minimum_balance, only: %i[create update] # destroy is ok
before_action :require_enough_apples, only: %i[create update] # destroy is ok
# @todo allow decreasing amounts when minimum balance isn't met
def index
@ -35,7 +35,8 @@ class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController
goa = nil
GroupOrderArticle.transaction do
goa = scope_for_update.includes(:group_order_article_quantities).find(params.require(:id))
goa.update_quantities((update_params[:quantity] || goa.quantity).to_i, (update_params[:tolerance] || goa.tolerance).to_i)
goa.update_quantities((update_params[:quantity] || goa.quantity).to_i,
(update_params[:tolerance] || goa.tolerance).to_i)
goa.order_article.update_results!
goa.group_order.update_price!
goa.group_order.update!(updated_by: current_user)

View file

@ -8,13 +8,13 @@ class Api::V1::User::OrdergroupController < Api::V1::BaseController
financial_overview: {
account_balance: ordergroup.account_balance.to_f,
available_funds: ordergroup.get_available_funds.to_f,
financial_transaction_class_sums: FinancialTransactionClass.sorted.map { |c|
financial_transaction_class_sums: FinancialTransactionClass.sorted.map do |c|
{
id: c.id,
name: c.display,
amount: ordergroup["sum_of_class_#{c.id}"].to_f
}
}
end
}
}
end

View file

@ -19,10 +19,10 @@ class ApplicationController < ActionController::Base
private
def set_user_last_activity
if current_user && (session[:last_activity] == nil || session[:last_activity] < 1.minutes.ago)
current_user.update_attribute(:last_activity, Time.now)
session[:last_activity] = Time.now
end
return unless current_user && (session[:last_activity].nil? || session[:last_activity] < 1.minute.ago)
current_user.update_attribute(:last_activity, Time.now)
session[:last_activity] = Time.now
end
# Many plugins can be turned on and off on the fly with a `use_` configuration option.
@ -64,11 +64,11 @@ class ApplicationController < ActionController::Base
end
def items_per_page
if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500
@per_page = params[:per_page].to_i
else
@per_page = 20
end
@per_page = if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500
params[:per_page].to_i
else
20
end
end
# Set timezone according to foodcoop preference.

View file

@ -4,17 +4,17 @@ class ArticleCategoriesController < ApplicationController
before_action :authenticate_article_meta
def create
create!(:notice => I18n.t('article_categories.create.notice')) { article_categories_path }
create!(notice: I18n.t('article_categories.create.notice')) { article_categories_path }
end
def update
update!(:notice => I18n.t('article_categories.update.notice')) { article_categories_path }
update!(notice: I18n.t('article_categories.update.notice')) { article_categories_path }
end
def destroy
destroy!
rescue => error
redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: error.message)
rescue StandardError => e
redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: e.message)
end
protected

View file

@ -2,24 +2,24 @@ class ArticlesController < ApplicationController
before_action :authenticate_article_meta, :find_supplier
def index
if params['sort']
sort = case params['sort']
when "name" then "articles.name"
when "unit" then "articles.unit"
when "article_category" then "article_categories.name"
when "note" then "articles.note"
when "availability" then "articles.availability"
when "name_reverse" then "articles.name DESC"
when "unit_reverse" then "articles.unit DESC"
when "article_category_reverse" then "article_categories.name DESC"
when "note_reverse" then "articles.note DESC"
when "availability_reverse" then "articles.availability DESC"
sort = if params['sort']
case params['sort']
when 'name' then 'articles.name'
when 'unit' then 'articles.unit'
when 'article_category' then 'article_categories.name'
when 'note' then 'articles.note'
when 'availability' then 'articles.availability'
when 'name_reverse' then 'articles.name DESC'
when 'unit_reverse' then 'articles.unit DESC'
when 'article_category_reverse' then 'article_categories.name DESC'
when 'note_reverse' then 'articles.note DESC'
when 'availability_reverse' then 'articles.availability DESC'
end
else
sort = "article_categories.name, articles.name"
end
else
'article_categories.name, articles.name'
end
@articles = Article.undeleted.where(supplier_id: @supplier, :type => nil).includes(:article_category).order(sort)
@articles = Article.undeleted.where(supplier_id: @supplier, type: nil).includes(:article_category).order(sort)
if request.format.csv?
send_data ArticlesCsv.new(@articles, encoding: 'utf-8').to_csv, filename: 'articles.csv', type: 'text/csv'
@ -32,31 +32,31 @@ class ArticlesController < ApplicationController
respond_to do |format|
format.html
format.js { render :layout => false }
format.js { render layout: false }
end
end
def new
@article = @supplier.articles.build(:tax => FoodsoftConfig[:tax_default])
render :layout => false
@article = @supplier.articles.build(tax: FoodsoftConfig[:tax_default])
render layout: false
end
def copy
@article = @supplier.articles.find(params[:article_id]).dup
render :layout => false
render layout: false
end
def edit
@article = Article.find(params[:id])
render :action => 'new', :layout => false
render action: 'new', layout: false
end
def create
@article = Article.new(params[:article])
if @article.valid? && @article.save
render :layout => false
render layout: false
else
render :action => 'new', :layout => false
render action: 'new', layout: false
end
end
@ -65,9 +65,9 @@ class ArticlesController < ApplicationController
@article = Article.find(params[:id])
if @article.update(params[:article])
render :layout => false
render layout: false
else
render :action => 'new', :layout => false
render action: 'new', layout: false
end
end
@ -75,7 +75,7 @@ class ArticlesController < ApplicationController
def destroy
@article = Article.find(params[:id])
@article.mark_as_deleted unless @order = @article.in_open_order # If article is in an active Order, the Order will be returned
render :layout => false
render layout: false
end
# Renders a form for editing all articles from a supplier
@ -87,19 +87,17 @@ class ArticlesController < ApplicationController
def update_all
invalid_articles = false
begin
Article.transaction do
unless params[:articles].blank?
# Update other article attributes...
@articles = Article.find(params[:articles].keys)
@articles.each do |article|
unless article.update(params[:articles][article.id.to_s])
invalid_articles = true unless invalid_articles # Remember that there are validation errors
end
Article.transaction do
if params[:articles].present?
# Update other article attributes...
@articles = Article.find(params[:articles].keys)
@articles.each do |article|
unless article.update(params[:articles][article.id.to_s])
invalid_articles ||= true # Remember that there are validation errors
end
raise ActiveRecord::Rollback if invalid_articles # Rollback all changes
end
raise ActiveRecord::Rollback if invalid_articles # Rollback all changes
end
end
@ -134,32 +132,31 @@ class ArticlesController < ApplicationController
end
end
# action succeded
redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page])
rescue => error
redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page]),
:alert => I18n.t('errors.general_msg', :msg => error)
redirect_to supplier_articles_url(@supplier, per_page: params[:per_page])
rescue StandardError => e
redirect_to supplier_articles_url(@supplier, per_page: params[:per_page]),
alert: I18n.t('errors.general_msg', msg: e)
end
# lets start with parsing articles from uploaded file, yeah
# Renders the upload form
def upload
end
def upload; end
# Update articles from a spreadsheet
def parse_upload
uploaded_file = params[:articles]['file'] or raise I18n.t('articles.controller.parse_upload.no_file')
type = params[:articles]['type']
options = { filename: uploaded_file.original_filename }
options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
options[:convert_units] = (params[:articles]['convert_units'] == '1')
options[:update_category] = (params[:articles]['update_category'] == '1')
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, type, options
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile,
options
if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice')
redirect_to supplier_articles_path(@supplier),
notice: I18n.t('articles.controller.parse_upload.notice')
end
@ignored_article_count = 0
rescue => error
redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
rescue StandardError => e
redirect_to upload_supplier_articles_path(@supplier), alert: I18n.t('errors.general_msg', msg: e.message)
end
# sync all articles with the external database
@ -167,13 +164,14 @@ class ArticlesController < ApplicationController
def sync
# check if there is an shared_supplier
unless @supplier.shared_supplier
redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
redirect_to supplier_articles_url(@supplier),
alert: I18n.t('articles.controller.sync.shared_alert', supplier: @supplier.name)
end
# sync articles against external database
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_all
if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
end
return unless @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.sync.notice')
end
# Updates, deletes articles when upload or sync form is submitted
@ -188,7 +186,7 @@ class ArticlesController < ApplicationController
# delete articles
begin
@outlisted_articles.each(&:mark_as_deleted)
rescue
rescue StandardError
# raises an exception when used in current order
has_error = true
end
@ -200,15 +198,15 @@ class ArticlesController < ApplicationController
raise ActiveRecord::Rollback if has_error
end
if !has_error
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice')
else
if has_error
@updated_article_pairs = @updated_articles.map do |article|
orig_article = Article.find(article.id)
[article, orig_article.unequal_attributes(article)]
end
flash.now.alert = I18n.t('articles.controller.error_invalid')
render params[:from_action] == 'sync' ? :sync : :parse_upload
else
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice')
end
end
@ -220,18 +218,18 @@ class ArticlesController < ApplicationController
q[:name_cont_all] = params.fetch(:name_cont_all_joined, '').split(' ')
search = @supplier.shared_supplier.shared_articles.ransack(q)
@articles = search.result.page(params[:page]).per(10)
render :layout => false
render layout: false
end
# fills a form whith values of the selected shared_article
# when the direct parameter is set and the article is valid, it is imported directly
def import
@article = SharedArticle.find(params[:shared_article_id]).build_new_article(@supplier)
@article.article_category_id = params[:article_category_id] unless params[:article_category_id].blank?
if params[:direct] && !params[:article_category_id].blank? && @article.valid? && @article.save
render :action => 'create', :layout => false
@article.article_category_id = params[:article_category_id] if params[:article_category_id].present?
if params[:direct] && params[:article_category_id].present? && @article.valid? && @article.save
render action: 'create', layout: false
else
render :action => 'new', :layout => false
render action: 'new', layout: false
end
end

View file

@ -9,15 +9,19 @@ module Concerns::Auth
def current_user
# check if there is a valid session and return the logged-in user (its object)
if session[:user_id] && params[:foodcoop]
# for shared-host installations. check if the cookie-subdomain fits to request.
@current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
end
return unless session[:user_id] && params[:foodcoop]
# for shared-host installations. check if the cookie-subdomain fits to request.
@current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
end
def deny_access
session[:return_to] = request.original_url
redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path))
redirect_to root_url,
alert: I18n.t('application.controller.error_denied',
sign_in: ActionController::Base.helpers.link_to(
t('application.controller.error_denied_sign_in'), login_path
))
end
private
@ -47,12 +51,7 @@ module Concerns::Auth
def authenticate(role = 'any')
# Attempt to retrieve authenticated user from controller instance or session...
if !current_user
# No user at all: redirect to login page.
logout
session[:return_to] = request.original_url
redirect_to_login :alert => I18n.t('application.controller.error_authn')
else
if current_user
# We have an authenticated user, now check role...
# Roles gets the user through his memberships.
hasRole = case role
@ -73,6 +72,11 @@ module Concerns::Auth
else
deny_access
end
else
# No user at all: redirect to login page.
logout
session[:return_to] = request.original_url
redirect_to_login alert: I18n.t('application.controller.error_authn')
end
end
@ -116,13 +120,13 @@ module Concerns::Auth
# if fails the user will redirected to startpage
def authenticate_membership_or_admin(group_id = params[:id])
@group = Group.find(group_id)
unless @group.member?(@current_user) || @current_user.role_admin?
redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
end
return if @group.member?(@current_user) || @current_user.role_admin?
redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
end
def authenticate_or_token(prefix, role = 'any')
if not params[:token].blank?
if params[:token].present?
begin
TokenVerifier.new(prefix).verify(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature

View file

@ -36,9 +36,9 @@ module Concerns::AuthApi
# Make sure that at least one the given OAuth scopes is valid for the current user's permissions.
# @raise Api::Errors::PermissionsRequired
def doorkeeper_authorize_roles!(*scopes)
unless scopes.any? { |scope| doorkeeper_scope_permitted?(scope) }
raise Api::Errors::PermissionRequired.new('Forbidden, no permission')
end
return if scopes.any? { |scope| doorkeeper_scope_permitted?(scope) }
raise Api::Errors::PermissionRequired, 'Forbidden, no permission'
end
# Check whether a given OAuth scope is permitted for the current user.
@ -48,9 +48,7 @@ module Concerns::AuthApi
def doorkeeper_scope_permitted?(scope)
scope_parts = scope.split(':')
# user sub-scopes like +config:user+ are always permitted
if scope_parts.last == 'user'
return true
end
return true if scope_parts.last == 'user'
case scope_parts.first
when 'user' then return true # access to the current user's own profile
@ -64,8 +62,8 @@ module Concerns::AuthApi
end
case scope
when 'orders:read' then return true
when 'orders:write' then return current_user.role_orders?
when 'orders:read' then true
when 'orders:write' then current_user.role_orders?
end
end
end

View file

@ -24,12 +24,12 @@ module Concerns::FoodcoopScope
elsif FoodsoftConfig.allowed_foodcoop? foodcoop
FoodsoftConfig.select_foodcoop foodcoop
else
raise ActionController::RoutingError.new 'Foodcoop Not Found'
raise ActionController::RoutingError, 'Foodcoop Not Found'
end
end
# Always stay in foodcoop url scope
def default_url_options(options = {})
def default_url_options(_options = {})
super().merge({ foodcoop: FoodsoftConfig.scope })
end
end

View file

@ -18,7 +18,7 @@ module Concerns::Locale
end
def browser_language
request.env['HTTP_ACCEPT_LANGUAGE'] ? request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first : nil
request.env['HTTP_ACCEPT_LANGUAGE']&.scan(/^[a-z]{2}/)&.first
end
def default_language
@ -30,7 +30,7 @@ module Concerns::Locale
def select_language_according_to_priority
language = explicitly_requested_language || session_language || user_settings_language
language ||= browser_language unless FoodsoftConfig[:ignore_browser_locale]
language.presence&.to_sym unless language.blank?
language.presence&.to_sym if language.present?
end
def available_locales
@ -38,11 +38,11 @@ module Concerns::Locale
end
def set_locale
if available_locales.include?(select_language_according_to_priority)
::I18n.locale = select_language_according_to_priority
else
::I18n.locale = default_language
end
::I18n.locale = if available_locales.include?(select_language_according_to_priority)
select_language_according_to_priority
else
default_language
end
locale = session[:locale] = ::I18n.locale
logger.info("Set locale to #{locale}")

View file

@ -3,7 +3,7 @@ module Concerns::SendOrderPdf
protected
def send_order_pdf order, document
def send_order_pdf(order, document)
klass = case document
when 'groups' then OrderByGroups
when 'articles' then OrderByArticles

View file

@ -1,5 +1,5 @@
class DeliveriesController < ApplicationController
before_action :find_supplier, :exclude => :fill_new_stock_article_form
before_action :find_supplier, exclude: :fill_new_stock_article_form
def index
@deliveries = @supplier.deliveries.order('date DESC')
@ -15,6 +15,10 @@ class DeliveriesController < ApplicationController
@delivery.date = Date.today # TODO: move to model/database
end
def edit
@delivery = Delivery.find(params[:id])
end
def create
@delivery = Delivery.new(params[:delivery])
@ -22,14 +26,10 @@ class DeliveriesController < ApplicationController
flash[:notice] = I18n.t('deliveries.create.notice')
redirect_to [@supplier, @delivery]
else
render :action => "new"
render action: 'new'
end
end
def edit
@delivery = Delivery.find(params[:id])
end
def update
@delivery = Delivery.find(params[:id])
@ -37,7 +37,7 @@ class DeliveriesController < ApplicationController
flash[:notice] = I18n.t('deliveries.update.notice')
redirect_to [@supplier, @delivery]
else
render :action => "edit"
render action: 'edit'
end
end
@ -52,18 +52,18 @@ class DeliveriesController < ApplicationController
def add_stock_change
@stock_change = StockChange.new
@stock_change.stock_article = StockArticle.find(params[:stock_article_id])
render :layout => false
render layout: false
end
def form_on_stock_article_create # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
render :layout => false
render layout: false
end
def form_on_stock_article_update # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
render :layout => false
render layout: false
end
end

View file

@ -1,13 +1,12 @@
class FeedbackController < ApplicationController
def new
end
def new; end
def create
if params[:message].present?
Mailer.feedback(current_user, params[:message]).deliver_now
redirect_to root_url, notice: t('feedback.create.notice')
redirect_to root_url, notice: t('.notice')
else
render :action => 'new'
render action: 'new'
end
end
end

View file

@ -5,39 +5,39 @@ class Finance::BalancingController < Finance::BaseController
def new
@order = Order.find(params[:order_id])
flash.now.alert = t('finance.balancing.new.alert') if @order.closed?
flash.now.alert = t('.alert') if @order.closed?
@comments = @order.comments
@articles = @order.order_articles.ordered_or_member.includes(:article, :article_price,
group_order_articles: { group_order: :ordergroup })
sort_param = params['sort'] || 'name'
@articles = case sort_param
when 'name' then
when 'name'
@articles.order('articles.name ASC')
when 'name_reverse' then
when 'name_reverse'
@articles.order('articles.name DESC')
when 'order_number' then
when 'order_number'
@articles.order('articles.order_number ASC')
when 'order_number_reverse' then
when 'order_number_reverse'
@articles.order('articles.order_number DESC')
else
@articles
end
render layout: false if request.xhr?
end
def new_on_order_article_create # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
render :layout => false
render layout: false
end
def new_on_order_article_update # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
render :layout => false
render layout: false
end
def update_summary
@ -46,29 +46,29 @@ class Finance::BalancingController < Finance::BaseController
def edit_note
@order = Order.find(params[:id])
render :layout => false
render layout: false
end
def update_note
@order = Order.find(params[:id])
if @order.update(params[:order])
render :layout => false
render layout: false
else
render :action => :edit_note, :layout => false
render action: :edit_note, layout: false
end
end
def edit_transport
@order = Order.find(params[:id])
render :layout => false
render layout: false
end
def update_transport
@order = Order.find(params[:id])
@order.update!(params[:order])
redirect_to new_finance_order_path(order_id: @order.id)
rescue => error
redirect_to new_finance_order_path(order_id: @order.id), alert: t('errors.general_msg', msg: error.message)
rescue StandardError => e
redirect_to new_finance_order_path(order_id: @order.id), alert: t('errors.general_msg', msg: e.message)
end
# before the order will booked, a view lists all Ordergroups and its order_prices
@ -81,18 +81,18 @@ class Finance::BalancingController < Finance::BaseController
@order = Order.find(params[:id])
@type = FinancialTransactionType.find_by_id(params.permit(:type)[:type])
@order.close!(@current_user, @type)
redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice')
rescue => error
redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message)
redirect_to finance_order_index_url, notice: t('.notice')
rescue StandardError => e
redirect_to new_finance_order_url(order_id: @order.id), alert: t('.alert', message: e.message)
end
# Close the order directly, without automaticly updating ordergroups account balances
def close_direct
@order = Order.find(params[:id])
@order.close_direct!(@current_user)
redirect_to finance_order_index_url, notice: t('finance.balancing.close_direct.notice')
rescue => error
redirect_to finance_order_index_url, alert: t('finance.balancing.close_direct.alert', message: error.message)
redirect_to finance_order_index_url, notice: t('.notice')
rescue StandardError => e
redirect_to finance_order_index_url, alert: t('.alert', message: e.message)
end
def close_all_direct_with_invoice
@ -103,8 +103,8 @@ class Finance::BalancingController < Finance::BaseController
count += 1
end
end
redirect_to finance_order_index_url, notice: t('finance.balancing.close_all_direct_with_invoice.notice', count: count)
rescue => error
redirect_to finance_order_index_url, alert: t('errors.general_msg', msg: error.message)
redirect_to finance_order_index_url, notice: t('.notice', count: count)
rescue StandardError => e
redirect_to finance_order_index_url, alert: t('errors.general_msg', msg: e.message)
end
end

View file

@ -8,8 +8,8 @@ class Finance::BankAccountsController < Finance::BaseController
@bank_account = BankAccount.find(params[:id])
count = @bank_account.assign_unlinked_transactions
redirect_to finance_bank_account_transactions_url(@bank_account), notice: t('.notice', count: count)
rescue => error
redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: error.message)
rescue StandardError => e
redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: e.message)
end
def import
@ -33,8 +33,8 @@ class Finance::BankAccountsController < Finance::BaseController
end
needs_redirect = ok
rescue => error
flash.alert = t('errors.general_msg', msg: error.message)
rescue StandardError => e
flash.alert = t('errors.general_msg', msg: e.message)
needs_redirect = true
ensure
return unless needs_redirect

View file

@ -3,26 +3,30 @@ class Finance::BankTransactionsController < ApplicationController
inherit_resources
def index
if params["sort"]
sort = case params["sort"]
when "date" then "date"
when "amount" then "amount"
when "financial_link" then "financial_link_id"
when "date_reverse" then "date DESC"
when "amount_reverse" then "amount DESC"
when "financial_link_reverse" then "financial_link_id DESC"
sort = if params['sort']
case params['sort']
when 'date' then 'date'
when 'amount' then 'amount'
when 'financial_link' then 'financial_link_id'
when 'date_reverse' then 'date DESC'
when 'amount_reverse' then 'amount DESC'
when 'financial_link_reverse' then 'financial_link_id DESC'
end
else
sort = "date DESC"
end
else
'date DESC'
end
@bank_account = BankAccount.find(params[:bank_account_id])
@bank_transactions_all = @bank_account.bank_transactions.order(sort).includes(:financial_link)
@bank_transactions_all = @bank_transactions_all.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%", "%#{params[:query]}%") unless params[:query].nil?
unless params[:query].nil?
@bank_transactions_all = @bank_transactions_all.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%",
"%#{params[:query]}%")
end
@bank_transactions = @bank_transactions_all.page(params[:page]).per(@per_page)
respond_to do |format|
format.js; format.html { render }
format.js
format.html { render }
format.csv do
send_data BankTransactionsCsv.new(@bank_transactions_all).to_csv, filename: 'transactions.csv', type: 'text/csv'
end

View file

@ -1,5 +1,5 @@
class Finance::FinancialLinksController < Finance::BaseController
before_action :find_financial_link, except: [:create, :incomplete]
before_action :find_financial_link, except: %i[create incomplete]
def show
@items = @financial_link.bank_transactions.map do |bt|
@ -37,7 +37,7 @@ class Finance::FinancialLinksController < Finance::BaseController
def create
@financial_link = FinancialLink.first_unused_or_create
if params[:bank_transaction] then
if params[:bank_transaction]
bank_transaction = BankTransaction.find(params[:bank_transaction])
bank_transaction.update_attribute :financial_link, @financial_link
end
@ -72,14 +72,16 @@ class Finance::FinancialLinksController < Finance::BaseController
def create_financial_transaction
financial_transaction = FinancialTransaction.new(financial_transaction_params)
financial_transaction.ordergroup.add_financial_transaction! financial_transaction.amount, financial_transaction.note, current_user, financial_transaction.financial_transaction_type, @financial_link
financial_transaction.ordergroup.add_financial_transaction! financial_transaction.amount,
financial_transaction.note, current_user, financial_transaction.financial_transaction_type, @financial_link
redirect_to finance_link_url(@financial_link), notice: t('.notice')
rescue => error
redirect_to finance_link_url(@financial_link), alert: t('errors.general_msg', msg: error)
rescue StandardError => e
redirect_to finance_link_url(@financial_link), alert: t('errors.general_msg', msg: e)
end
def index_financial_transaction
@financial_transactions = FinancialTransaction.without_financial_link.includes(:financial_transaction_type, :ordergroup)
@financial_transactions = FinancialTransaction.without_financial_link.includes(:financial_transaction_type,
:ordergroup)
end
def add_financial_transaction
@ -123,7 +125,7 @@ class Finance::FinancialLinksController < Finance::BaseController
end
def find_best_fitting_ordergroup_id_for_financial_link(financial_link_id)
FinancialTransaction.joins(<<-SQL).order(created_on: :desc).pluck(:ordergroup_id).first
FinancialTransaction.joins(<<-SQL).order(created_on: :desc).pick(:ordergroup_id)
JOIN bank_transactions a ON financial_transactions.financial_link_id = a.financial_link_id
JOIN bank_transactions b ON a.iban = b.iban AND b.financial_link_id = #{financial_link_id.to_i}
SQL

View file

@ -1,22 +1,22 @@
class Finance::FinancialTransactionsController < ApplicationController
before_action :authenticate_finance
before_action :find_ordergroup, :except => [:new_collection, :create_collection, :index_collection]
before_action :find_ordergroup, except: %i[new_collection create_collection index_collection]
inherit_resources
# belongs_to :ordergroup
def index
if params['sort']
sort = case params['sort']
when "date" then "created_on"
when "note" then "note"
when "amount" then "amount"
when "date_reverse" then "created_on DESC"
when "note_reverse" then "note DESC"
when "amount_reverse" then "amount DESC"
sort = if params['sort']
case params['sort']
when 'date' then 'created_on'
when 'note' then 'note'
when 'amount' then 'amount'
when 'date_reverse' then 'created_on DESC'
when 'note_reverse' then 'note DESC'
when 'amount_reverse' then 'amount DESC'
end
else
sort = "created_on DESC"
end
else
'created_on DESC'
end
@q = FinancialTransaction.ransack(params[:q])
@financial_transactions_all = @q.result(distinct: true).includes(:user).order(sort)
@ -26,9 +26,11 @@ class Finance::FinancialTransactionsController < ApplicationController
@financial_transactions = @financial_transactions_all.page(params[:page]).per(@per_page)
respond_to do |format|
format.js; format.html { render }
format.js
format.html { render }
format.csv do
send_data FinancialTransactionsCsv.new(@financial_transactions_all).to_csv, filename: 'transactions.csv', type: 'text/csv'
send_data FinancialTransactionsCsv.new(@financial_transactions_all).to_csv, filename: 'transactions.csv',
type: 'text/csv'
end
end
end
@ -38,11 +40,11 @@ class Finance::FinancialTransactionsController < ApplicationController
end
def new
if @ordergroup
@financial_transaction = @ordergroup.financial_transactions.build
else
@financial_transaction = FinancialTransaction.new
end
@financial_transaction = if @ordergroup
@ordergroup.financial_transactions.build
else
FinancialTransaction.new
end
end
def create
@ -53,16 +55,18 @@ class Finance::FinancialTransactionsController < ApplicationController
else
@financial_transaction.save!
end
redirect_to finance_group_transactions_path(@ordergroup), notice: I18n.t('finance.financial_transactions.controller.create.notice')
rescue ActiveRecord::RecordInvalid => error
flash.now[:alert] = error.message
render :action => :new
redirect_to finance_group_transactions_path(@ordergroup),
notice: I18n.t('finance.financial_transactions.controller.create.notice')
rescue ActiveRecord::RecordInvalid => e
flash.now[:alert] = e.message
render action: :new
end
def destroy
transaction = FinancialTransaction.find(params[:id])
transaction.revert!(current_user)
redirect_to finance_group_transactions_path(transaction.ordergroup), notice: t('finance.financial_transactions.controller.destroy.notice')
redirect_to finance_group_transactions_path(transaction.ordergroup),
notice: t('finance.financial_transactions.controller.destroy.notice')
end
def new_collection
@ -88,17 +92,17 @@ class Finance::FinancialTransactionsController < ApplicationController
params[:financial_transactions].each do |trans|
# ignore empty amount fields ...
unless trans[:amount].blank?
amount = LocalizeInput.parse(trans[:amount]).to_f
note = params[:note]
ordergroup = Ordergroup.find(trans[:ordergroup_id])
if params[:set_balance]
note += " (#{amount})"
amount -= ordergroup.financial_transaction_class_balance(type.financial_transaction_class)
end
ordergroup.add_financial_transaction!(amount, note, @current_user, type, financial_link)
foodcoop_amount -= amount
next if trans[:amount].blank?
amount = LocalizeInput.parse(trans[:amount]).to_f
note = params[:note]
ordergroup = Ordergroup.find(trans[:ordergroup_id])
if params[:set_balance]
note += " (#{amount})"
amount -= ordergroup.financial_transaction_class_balance(type.financial_transaction_class)
end
ordergroup.add_financial_transaction!(amount, note, @current_user, type, financial_link)
foodcoop_amount -= amount
end
if params[:create_foodcoop_transaction]
@ -107,7 +111,7 @@ class Finance::FinancialTransactionsController < ApplicationController
user: @current_user,
amount: foodcoop_amount,
note: params[:note],
financial_link: financial_link,
financial_link: financial_link
})
ft.save!
end
@ -117,8 +121,8 @@ class Finance::FinancialTransactionsController < ApplicationController
url = financial_link ? finance_link_url(financial_link.id) : finance_ordergroups_url
redirect_to url, notice: I18n.t('finance.financial_transactions.controller.create_collection.notice')
rescue => error
flash.now[:alert] = error.message
rescue StandardError => e
flash.now[:alert] = e.message
render action: :new_collection
end

View file

@ -1,15 +1,16 @@
class Finance::InvoicesController < ApplicationController
before_action :authenticate_finance_or_invoices
before_action :find_invoice, only: [:show, :edit, :update, :destroy]
before_action :ensure_can_edit, only: [:edit, :update, :destroy]
before_action :find_invoice, only: %i[show edit update destroy]
before_action :ensure_can_edit, only: %i[edit update destroy]
def index
@invoices_all = Invoice.includes(:supplier, :deliveries, :orders).order('date DESC')
@invoices = @invoices_all.page(params[:page]).per(@per_page)
respond_to do |format|
format.js; format.html { render }
format.js
format.html { render }
format.csv do
send_data InvoicesCsv.new(@invoices_all).to_csv, filename: 'invoices.csv', type: 'text/csv'
end
@ -20,11 +21,10 @@ class Finance::InvoicesController < ApplicationController
@suppliers = Supplier.includes(:invoices).where('invoices.paid_on IS NULL').references(:invoices)
end
def show
end
def show; end
def new
@invoice = Invoice.new :supplier_id => params[:supplier_id]
@invoice = Invoice.new supplier_id: params[:supplier_id]
@invoice.deliveries << Delivery.find_by_id(params[:delivery_id]) if params[:delivery_id]
@invoice.orders << Order.find_by_id(params[:order_id]) if params[:order_id]
fill_deliveries_and_orders_collection @invoice.id, @invoice.supplier_id
@ -36,12 +36,14 @@ class Finance::InvoicesController < ApplicationController
def form_on_supplier_id_change
fill_deliveries_and_orders_collection params[:invoice_id], params[:supplier_id]
render :layout => false
render layout: false
end
def fill_deliveries_and_orders_collection(invoice_id, supplier_id)
@deliveries_collection = Delivery.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, supplier_id).order(date: :desc).limit(25)
@orders_collection = Order.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, supplier_id).order(ends: :desc).limit(25)
@deliveries_collection = Delivery.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id,
supplier_id).order(date: :desc).limit(25)
@orders_collection = Order.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id,
supplier_id).order(ends: :desc).limit(25)
end
def create
@ -58,7 +60,7 @@ class Finance::InvoicesController < ApplicationController
end
else
fill_deliveries_and_orders_collection @invoice.id, @invoice.supplier_id
render :action => "new"
render action: 'new'
end
end
@ -81,7 +83,7 @@ class Finance::InvoicesController < ApplicationController
@invoice = Invoice.find(params[:invoice_id])
type = MIME::Types[@invoice.attachment_mime].first
filename = "invoice_#{@invoice.id}_attachment.#{type.preferred_extension}"
send_data(@invoice.attachment_data, :filename => filename, :type => type)
send_data(@invoice.attachment_data, filename: filename, type: type)
end
private
@ -92,8 +94,8 @@ class Finance::InvoicesController < ApplicationController
# Returns true if @current_user can edit the invoice..
def ensure_can_edit
unless @invoice.user_can_edit?(current_user)
deny_access
end
return if @invoice.user_can_edit?(current_user)
deny_access
end
end

View file

@ -1,11 +1,11 @@
class Finance::OrdergroupsController < Finance::BaseController
def index
m = /^(?<col>name|sum_of_class_\d+)(?<reverse>_reverse)?$/.match params["sort"]
m = /^(?<col>name|sum_of_class_\d+)(?<reverse>_reverse)?$/.match params['sort']
if m
sort = m[:col]
sort += ' DESC' if m[:reverse]
else
sort = "name"
sort = 'name'
end
@ordergroups = Ordergroup.undeleted.order(sort)
@ -14,7 +14,7 @@ class Finance::OrdergroupsController < Finance::BaseController
@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 }
tmp[c.id] = c.financial_transactions.reduce(0) { |sum, t| sum + t.amount }
end
end
end

View file

@ -1,20 +1,16 @@
class Foodcoop::OrdergroupsController < ApplicationController
def index
@ordergroups = Ordergroup.undeleted.sort_by_param(params["sort"])
@ordergroups = Ordergroup.undeleted.sort_by_param(params['sort'])
unless params[:name].blank? # Search by name
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%")
end
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%") if params[:name].present? # Search by name
if params[:only_active] # Select only active groups
@ordergroups = @ordergroups.active
end
@ordergroups = @ordergroups.active if params[:only_active] # Select only active groups
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
respond_to do |format|
format.html # index.html.erb
format.js { render :layout => false }
format.js { render layout: false }
end
end
end

View file

@ -1,19 +1,22 @@
class Foodcoop::UsersController < ApplicationController
before_action -> { require_config_disabled :disable_members_overview }
def index
@users = User.undeleted.sort_by_param(params["sort"])
@users = User.undeleted.sort_by_param(params['sort'])
# if somebody uses the search field:
@users = @users.natural_search(params[:user_name]) unless params[:user_name].blank?
@users = @users.natural_search(params[:user_name]) if params[:user_name].present?
if params[:ordergroup_name]
@users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", "%#{params[:ordergroup_name]}%")
@users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?",
"%#{params[:ordergroup_name]}%")
end
@users = @users.page(params[:page]).per(@per_page)
respond_to do |format|
format.html # index.html.haml
format.js { render :layout => false } # index.js.erb
format.js { render layout: false } # index.js.erb
end
end
end

View file

@ -1,9 +1,9 @@
class Foodcoop::WorkgroupsController < ApplicationController
before_action :authenticate_membership_or_admin,
:except => [:index]
except: [:index]
def index
@workgroups = Workgroup.order("name")
@workgroups = Workgroup.order('name')
end
def edit
@ -13,9 +13,9 @@ class Foodcoop::WorkgroupsController < ApplicationController
def update
@workgroup = Workgroup.find(params[:id])
if @workgroup.update(params[:workgroup])
redirect_to foodcoop_workgroups_url, :notice => I18n.t('workgroups.update.notice')
redirect_to foodcoop_workgroups_url, notice: I18n.t('workgroups.update.notice')
else
render :action => 'edit'
render action: 'edit'
end
end
end

View file

@ -1,6 +1,6 @@
class GroupOrderArticlesController < ApplicationController
before_action :authenticate_finance
before_action :find_group_order_article, except: [:new, :create]
before_action :find_group_order_article, except: %i[new create]
layout false # We only use this controller to server js snippets, no need for layout rendering

View file

@ -3,9 +3,9 @@
class GroupOrdersController < ApplicationController
# Security
before_action :ensure_ordergroup_member
before_action :ensure_open_order, :only => [:new, :create, :edit, :update, :order, :stock_order, :saveOrder]
before_action :ensure_my_group_order, only: [:show, :edit, :update]
before_action :enough_apples?, only: [:new, :create]
before_action :ensure_open_order, only: %i[new create edit update order stock_order saveOrder]
before_action :ensure_my_group_order, only: %i[show edit update]
before_action :enough_apples?, only: %i[new create]
# Index page.
def index
@ -13,9 +13,17 @@ class GroupOrdersController < ApplicationController
@finished_not_closed_orders_including_group_order = Order.finished_not_closed.ordergroup_group_orders_map(@ordergroup)
end
def show
@order = @group_order.order
end
def new
ordergroup = params[:stock_order] ? nil : @ordergroup
@group_order = @order.group_orders.build(:ordergroup => ordergroup, :updated_by => current_user)
@group_order = @order.group_orders.build(ordergroup: ordergroup, updated_by: current_user)
@ordering_data = @group_order.load_data
end
def edit
@ordering_data = @group_order.load_data
end
@ -23,34 +31,26 @@ class GroupOrdersController < ApplicationController
@group_order = GroupOrder.new(params[:group_order])
begin
@group_order.save_ordering!
redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.create.notice')
redirect_to group_order_url(@group_order), notice: I18n.t('group_orders.create.notice')
rescue ActiveRecord::StaleObjectError
redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_stale')
rescue => exception
logger.error('Failed to update order: ' + exception.message)
redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_general')
redirect_to group_orders_url, alert: I18n.t('group_orders.create.error_stale')
rescue StandardError => e
logger.error('Failed to update order: ' + e.message)
redirect_to group_orders_url, alert: I18n.t('group_orders.create.error_general')
end
end
def show
@order = @group_order.order
end
def edit
@ordering_data = @group_order.load_data
end
def update
@group_order.attributes = params[:group_order]
@group_order.updated_by = current_user
begin
@group_order.save_ordering!
redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.update.notice')
redirect_to group_order_url(@group_order), notice: I18n.t('group_orders.update.notice')
rescue ActiveRecord::StaleObjectError
redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_stale')
rescue => exception
logger.error('Failed to update order: ' + exception.message)
redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_general')
redirect_to group_orders_url, alert: I18n.t('group_orders.update.error_stale')
rescue StandardError => e
logger.error('Failed to update order: ' + e.message)
redirect_to group_orders_url, alert: I18n.t('group_orders.update.error_general')
end
end
@ -74,16 +74,16 @@ class GroupOrdersController < ApplicationController
# Used as a :before_action by OrdersController.
def ensure_ordergroup_member
@ordergroup = @current_user.ordergroup
if @ordergroup.nil?
redirect_to root_url, :alert => I18n.t('group_orders.errors.no_member')
end
return unless @ordergroup.nil?
redirect_to root_url, alert: I18n.t('group_orders.errors.no_member')
end
def ensure_open_order
@order = Order.includes([:supplier, :order_articles]).find(order_id_param)
@order = Order.includes(%i[supplier order_articles]).find(order_id_param)
unless @order.open?
flash[:notice] = I18n.t('group_orders.errors.closed')
redirect_to :action => 'index'
redirect_to action: 'index'
end
rescue ActiveRecord::RecordNotFound
redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
@ -91,17 +91,17 @@ class GroupOrdersController < ApplicationController
def ensure_my_group_order
@group_order = GroupOrder.find(params[:id])
if @group_order.ordergroup != @ordergroup && (@group_order.ordergroup || !current_user.role_orders?)
redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
end
return unless @group_order.ordergroup != @ordergroup && (@group_order.ordergroup || !current_user.role_orders?)
redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
end
def enough_apples?
if @ordergroup.not_enough_apples?
redirect_to group_orders_url,
alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples,
stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
end
return unless @ordergroup.not_enough_apples?
redirect_to group_orders_url,
alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples,
stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
end
def order_id_param

View file

@ -9,8 +9,7 @@ class HomeController < ApplicationController
@unassigned_tasks = Task.order(:due_date).next_unassigned_tasks_for(current_user)
end
def profile
end
def profile; end
def reference_calculator
if current_user.ordergroup
@ -18,7 +17,7 @@ class HomeController < ApplicationController
@bank_accounts = @types.includes(:bank_account).map(&:bank_account).uniq.compact
@bank_accounts = [BankAccount.last] if @bank_accounts.empty?
else
redirect_to root_path, alert: I18n.t('group_orders.errors.no_member')
redirect_to root_url, alert: I18n.t('group_orders.errors.no_member')
end
end
@ -26,7 +25,7 @@ class HomeController < ApplicationController
if @current_user.update(user_params)
@current_user.ordergroup.update(ordergroup_params) if ordergroup_params
session[:locale] = @current_user.locale
redirect_to my_profile_path, notice: I18n.t('home.changes_saved')
redirect_to my_profile_url, notice: I18n.t('home.changes_saved')
else
render :profile
end
@ -36,40 +35,43 @@ class HomeController < ApplicationController
@user = @current_user
@ordergroup = @user.ordergroup
unless @ordergroup.nil?
if @ordergroup.nil?
redirect_to root_path, alert: I18n.t('home.no_ordergroups')
else
@ordergroup = Ordergroup.include_transaction_class_sum.find(@ordergroup.id)
if params['sort']
sort = case params['sort']
when "date" then "created_on"
when "note" then "note"
when "amount" then "amount"
when "date_reverse" then "created_on DESC"
when "note_reverse" then "note DESC"
when "amount_reverse" then "amount DESC"
sort = if params['sort']
case params['sort']
when 'date' then 'created_on'
when 'note' then 'note'
when 'amount' then 'amount'
when 'date_reverse' then 'created_on DESC'
when 'note_reverse' then 'note DESC'
when 'amount_reverse' then 'amount DESC'
end
else
sort = "created_on DESC"
end
else
'created_on DESC'
end
@financial_transactions = @ordergroup.financial_transactions.visible.page(params[:page]).per(@per_page).order(sort)
@financial_transactions = @financial_transactions.where('financial_transactions.note LIKE ?', "%#{params[:query]}%") if params[:query].present?
if params[:query].present?
@financial_transactions = @financial_transactions.where('financial_transactions.note LIKE ?',
"%#{params[:query]}%")
end
else
redirect_to root_path, alert: I18n.t('home.no_ordergroups')
end
end
# cancel personal memberships direct from the myProfile-page
def cancel_membership
if params[:membership_id]
membership = @current_user.memberships.find(params[:membership_id])
else
membership = @current_user.memberships.find_by_group_id!(params[:group_id])
end
membership = if params[:membership_id]
@current_user.memberships.find(params[:membership_id])
else
@current_user.memberships.find_by_group_id!(params[:group_id])
end
membership.destroy
redirect_to my_profile_path, notice: I18n.t('home.ordergroup_cancelled', :group => membership.group.name)
redirect_to my_profile_path, notice: I18n.t('home.ordergroup_cancelled', group: membership.group.name)
end
protected
@ -82,8 +84,8 @@ class HomeController < ApplicationController
end
def ordergroup_params
if params[:user][:ordergroup]
params.require(:user).require(:ordergroup).permit(:contact_address)
end
return unless params[:user][:ordergroup]
params.require(:user).require(:ordergroup).permit(:contact_address)
end
end

View file

@ -3,7 +3,7 @@ class InvitesController < ApplicationController
before_action -> { require_config_disabled :disable_invite }
def new
@invite = Invite.new(:user => @current_user, :group => @group)
@invite = Invite.new(user: @current_user, group: @group)
end
def create
@ -27,6 +27,10 @@ class InvitesController < ApplicationController
protected
def authenticate_membership_or_admin_for_invites
authenticate_membership_or_admin((params[:invite][:group_id] rescue params[:id]))
authenticate_membership_or_admin(begin
params[:invite][:group_id]
rescue StandardError
params[:id]
end)
end
end

View file

@ -1,6 +1,6 @@
class LoginController < ApplicationController
skip_before_action :authenticate # no authentication since this is the login page
before_action :validate_token, :only => [:new_password, :update_password]
before_action :validate_token, only: %i[new_password update_password]
# Display the form to enter an email address requesting a token to set a new password.
def forgot_password
@ -9,20 +9,17 @@ class LoginController < ApplicationController
# Sends an email to a user with the token that allows setting a new password through action "password".
def reset_password
if request.get? || params[:user].nil? # Catch for get request and give better error message.
redirect_to forgot_password_url, alert: I18n.t('errors.general_again') and return
end
redirect_to forgot_password_url, alert: I18n.t('errors.general_again') and return if request.get? || params[:user].nil? # Catch for get request and give better error message.
if (user = User.undeleted.find_by_email(params[:user][:email]))
user.request_password_reset!
end
redirect_to login_url, :notice => I18n.t('login.controller.reset_password.notice')
redirect_to login_url, notice: I18n.t('login.controller.reset_password.notice')
end
# Set a new password with a token from the password reminder email.
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
def new_password
end
def new_password; end
# Sets a new password.
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
@ -32,7 +29,7 @@ class LoginController < ApplicationController
@user.reset_password_token = nil
@user.reset_password_expires = nil
@user.save
redirect_to login_url, :notice => I18n.t('login.controller.update_password.notice')
redirect_to login_url, notice: I18n.t('login.controller.update_password.notice')
else
render :new_password
end
@ -50,14 +47,14 @@ class LoginController < ApplicationController
@user = User.new(params[:user])
@user.email = @invite.email
if @user.save
Membership.new(:user => @user, :group => @invite.group).save!
Membership.new(user: @user, group: @invite.group).save!
@invite.destroy
session[:locale] = @user.locale
redirect_to login_url, notice: I18n.t('login.controller.accept_invitation.notice')
end
end
else
@user = User.new(:email => @invite.email)
@user = User.new(email: @invite.email)
end
end
@ -65,8 +62,8 @@ class LoginController < ApplicationController
def validate_token
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
if (@user.nil? || @user.reset_password_expires < Time.now)
redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid')
end
return unless @user.nil? || @user.reset_password_expires < Time.now
redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid')
end
end

View file

@ -1,7 +1,7 @@
class OrderArticlesController < ApplicationController
before_action :fetch_order, except: :destroy
before_action :authenticate_finance_or_invoices, except: [:new, :create]
before_action :authenticate_finance_orders_or_pickup, except: [:edit, :update, :destroy]
before_action :authenticate_finance_or_invoices, except: %i[new create]
before_action :authenticate_finance_orders_or_pickup, except: %i[edit update destroy]
layout false # We only use this controller to serve js snippets, no need for layout rendering
@ -9,28 +9,26 @@ class OrderArticlesController < ApplicationController
@order_article = @order.order_articles.build(params[:order_article])
end
def edit
@order_article = OrderArticle.find(params[:id])
end
def create
# The article may be ordered with zero units - in that case do not complain.
# If order_article is ordered and a new order_article is created, an error message will be
# given mentioning that the article already exists, which is desired.
@order_article = @order.order_articles.where(:article_id => params[:order_article][:article_id]).first
unless @order_article && @order_article.units_to_order == 0
@order_article = @order.order_articles.build(params[:order_article])
end
@order_article = @order.order_articles.where(article_id: params[:order_article][:article_id]).first
@order_article = @order.order_articles.build(params[:order_article]) unless @order_article && @order_article.units_to_order == 0
@order_article.save!
rescue
rescue StandardError
render action: :new
end
def edit
@order_article = OrderArticle.find(params[:id])
end
def update
@order_article = OrderArticle.find(params[:id])
begin
@order_article.update_article_and_price!(params[:order_article], params[:article], params[:article_price])
rescue
rescue StandardError
render action: :edit
end
end

View file

@ -1,15 +1,15 @@
class OrderCommentsController < ApplicationController
def new
@order = Order.find(params[:order_id])
@order_comment = @order.comments.build(:user => current_user)
@order_comment = @order.comments.build(user: current_user)
end
def create
@order_comment = OrderComment.new(params[:order_comment])
if @order_comment.save
render :layout => false
render layout: false
else
render :action => :new, :layout => false
render action: :new, layout: false
end
end
end

View file

@ -5,25 +5,26 @@ class OrdersController < ApplicationController
include Concerns::SendOrderPdf
before_action :authenticate_pickups_or_orders
before_action :authenticate_orders, except: [:receive, :receive_on_order_article_create, :receive_on_order_article_update, :show]
before_action :remove_empty_article, only: [:create, :update]
before_action :authenticate_orders,
except: %i[receive receive_on_order_article_create receive_on_order_article_update show]
before_action :remove_empty_article, only: %i[create update]
# List orders
def index
@open_orders = Order.open.includes(:supplier)
@finished_orders = Order.finished_not_closed.includes(:supplier)
@per_page = 15
if params['sort']
sort = case params['sort']
when "supplier" then "suppliers.name, ends DESC"
when "pickup" then "pickup DESC"
when "ends" then "ends DESC"
when "supplier_reverse" then "suppliers.name DESC"
when "ends_reverse" then "ends"
sort = if params['sort']
case params['sort']
when 'supplier' then 'suppliers.name, ends DESC'
when 'pickup' then 'pickup DESC'
when 'ends' then 'ends DESC'
when 'supplier_reverse' then 'suppliers.name DESC'
when 'ends_reverse' then 'ends'
end
else
sort = "ends DESC"
end
else
'ends DESC'
end
@suppliers = Supplier.having_articles.order('suppliers.name')
@orders = Order.closed.includes(:supplier).reorder(sort).page(params[:page]).per(@per_page)
end
@ -43,13 +44,13 @@ class OrdersController < ApplicationController
respond_to do |format|
format.html
format.js do
render :layout => false
render layout: false
end
format.pdf do
send_order_pdf @order, params[:document]
end
format.csv do
send_data OrderCsv.new(@order, options= {custom_csv: params[:custom_csv]}).to_csv, filename: @order.name + '.csv', type: 'text/csv'
send_data OrderCsv.new(@order).to_csv, filename: @order.name + '.csv', type: 'text/csv'
end
format.text do
send_data OrderTxt.new(@order).to_txt, filename: @order.name + '.txt', type: 'text/plain'
@ -57,19 +58,6 @@ class OrdersController < ApplicationController
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.
def new
if params[:order_id]
@ -79,8 +67,14 @@ class OrdersController < ApplicationController
else
@order = Order.new(supplier_id: params[:supplier_id]).init_dates
end
rescue => error
redirect_to orders_url, alert: t('errors.general_msg', msg: error.message)
rescue StandardError => e
redirect_to orders_url, alert: t('errors.general_msg', msg: e.message)
end
# Page to edit an exsiting order.
# editing finished orders is done in FinanceController
def edit
@order = Order.includes(:articles).find(params[:id])
end
# Save a new order.
@ -94,31 +88,25 @@ class OrdersController < ApplicationController
redirect_to @order
else
logger.debug "[debug] order errors: #{@order.errors.messages}"
render :action => 'new'
render action: 'new'
end
end
# Page to edit an exsiting order.
# editing finished orders is done in FinanceController
def edit
@order = Order.includes(:articles).find(params[:id])
end
# Update an existing order.
def update
@order = Order.find params[:id]
if @order.update(params[:order].merge(updated_by: current_user))
flash[:notice] = I18n.t('orders.update.notice')
redirect_to :action => 'show', :id => @order
redirect_to action: 'show', id: @order
else
render :action => 'edit'
render action: 'edit'
end
end
# Delete an order.
def destroy
Order.find(params[:id]).destroy
redirect_to :action => 'index'
redirect_to action: 'index'
end
# Finish a current order.
@ -126,8 +114,8 @@ class OrdersController < ApplicationController
order = Order.find(params[:id])
order.finish!(@current_user)
redirect_to order, notice: I18n.t('orders.finish.notice')
rescue => error
redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message)
rescue StandardError => e
redirect_to orders_url, alert: I18n.t('errors.general_msg', msg: e.message)
end
# Send a order to the supplier.
@ -135,20 +123,18 @@ class OrdersController < ApplicationController
order = Order.find(params[:id])
order.send_to_supplier!(@current_user)
redirect_to order, notice: I18n.t('orders.send_to_supplier.notice')
rescue => error
redirect_to order, alert: I18n.t('errors.general_msg', :msg => error.message)
rescue StandardError => e
redirect_to order, alert: I18n.t('errors.general_msg', msg: e.message)
end
def receive
@order = Order.find(params[:id])
unless request.post?
@order_articles = @order.order_articles.ordered_or_member.includes(:article).order('articles.order_number, articles.name')
else
if request.post?
Order.transaction do
s = update_order_amounts
@order.update_attribute(:state, 'received') if @order.state != 'received'
flash[:notice] = (s ? I18n.t('orders.receive.notice', :msg => s) : I18n.t('orders.receive.notice_none'))
flash[:notice] = (s ? I18n.t('orders.receive.notice', msg: s) : I18n.t('orders.receive.notice_none'))
end
NotifyReceivedOrderJob.perform_later(@order)
if current_user.role_orders? || current_user.role_finance?
@ -158,23 +144,25 @@ class OrdersController < ApplicationController
else
redirect_to receive_order_path(@order)
end
else
@order_articles = @order.order_articles.ordered_or_member.includes(:article).order('articles.order_number, articles.name')
end
end
def receive_on_order_article_create # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
render :layout => false
render layout: false
end
def receive_on_order_article_update # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
render :layout => false
render layout: false
end
protected
def update_order_amounts
return if not params[:order_articles]
return unless params[:order_articles]
# where to leave remainder during redistribution
rest_to = []
@ -189,35 +177,42 @@ class OrdersController < ApplicationController
# "MySQL lock timeout exceeded" errors. It's ok to do
# this article-by-article anway.
params[:order_articles].each do |oa_id, oa_params|
unless oa_params.blank?
oa = OrderArticle.find(oa_id)
# update attributes; don't use update_attribute because it calls save
# which makes received_changed? not work anymore
oa.attributes = oa_params
if oa.units_received_changed?
counts[0] += 1
unless oa.units_received.blank?
cunits[0] += oa.units_received * oa.article.unit_quantity
oacounts = oa.redistribute oa.units_received * oa.price.unit_quantity, rest_to
oacounts.each_with_index { |c, i| cunits[i + 1] += c; counts[i + 1] += 1 if c > 0 }
next if oa_params.blank?
oa = OrderArticle.find(oa_id)
# update attributes; don't use update_attribute because it calls save
# which makes received_changed? not work anymore
oa.attributes = oa_params
if oa.units_received_changed?
counts[0] += 1
if oa.units_received.present?
cunits[0] += oa.units_received * oa.article.unit_quantity
oacounts = oa.redistribute oa.units_received * oa.price.unit_quantity, rest_to
oacounts.each_with_index do |c, i|
cunits[i + 1] += c
counts[i + 1] += 1 if c > 0
end
end
oa.save!
end
oa.save!
end
return nil if counts[0] == 0
notice = []
notice << I18n.t('orders.update_order_amounts.msg1', count: counts[0], units: cunits[0])
notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1], units: cunits[1]) if params[:rest_to_tolerance]
if params[:rest_to_tolerance]
notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1],
units: cunits[1])
end
notice << I18n.t('orders.update_order_amounts.msg3', count: counts[2], units: cunits[2]) if params[:rest_to_stock]
if counts[3] > 0 || cunits[3] > 0
notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3], units: cunits[3])
notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3],
units: cunits[3])
end
notice.join(', ')
end
def remove_empty_article
params[:order][:article_ids].reject!(&:blank?) if params[:order] && params[:order][:article_ids]
params[:order][:article_ids].compact_blank! if params[:order] && params[:order][:article_ids]
end
end

View file

@ -12,16 +12,20 @@ class SessionsController < ApplicationController
user = User.authenticate(params[:nick], params[:password])
if user
user.update_attribute(:last_login, Time.now)
login_and_redirect_to_return_to user, :notice => I18n.t('sessions.logged_in')
login_and_redirect_to_return_to user, notice: I18n.t('sessions.logged_in')
else
flash.now.alert = I18n.t(FoodsoftConfig[:use_nick] ? 'sessions.login_invalid_nick' : 'sessions.login_invalid_email')
render "new"
render 'new'
end
end
def destroy
logout
redirect_to login_url, :notice => I18n.t('sessions.logged_out')
if FoodsoftConfig[:logout_redirect_url].present?
redirect_to FoodsoftConfig[:logout_redirect_url], allow_other_host: true
else
redirect_to login_url, notice: I18n.t('sessions.logged_out')
end
end
# redirect to root, going to default foodcoop when none given

View file

@ -7,21 +7,21 @@ class StockTakingsController < ApplicationController
def new
@stock_taking = StockTaking.new
StockArticle.undeleted.each { |a| @stock_taking.stock_changes.build(:stock_article => a) }
StockArticle.undeleted.each { |a| @stock_taking.stock_changes.build(stock_article: a) }
end
def new_on_stock_article_create # See publish/subscribe design pattern in /doc.
stock_article = StockArticle.find(params[:stock_article_id])
@stock_change = StockChange.new(:stock_article => stock_article)
@stock_change = StockChange.new(stock_article: stock_article)
render :layout => false
render layout: false
end
def create
create!(:notice => I18n.t('stock_takings.create.notice'))
create!(notice: I18n.t('stock_takings.create.notice'))
end
def update
update!(:notice => I18n.t('stock_takings.update.notice'))
update!(notice: I18n.t('stock_takings.update.notice'))
end
end

View file

@ -7,57 +7,13 @@ class StockitController < ApplicationController
def index_on_stock_article_create # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
render :layout => false
render layout: false
end
def index_on_stock_article_update # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
render :layout => false
end
# three possibilites to fill a new_stock_article form
# (1) start from blank or use params
def new
@stock_article = StockArticle.new(params[:stock_article])
render :layout => false
end
# (2) StockArticle as template
def copy
@stock_article = StockArticle.find(params[:stock_article_id]).dup
render :layout => false
end
# (3) non-stock Article as template
def derive
@stock_article = Article.find(params[:old_article_id]).becomes(StockArticle).dup
render :layout => false
end
def create
@stock_article = StockArticle.new({ quantity: 0 }.merge(params[:stock_article]))
@stock_article.save!
render :layout => false
rescue ActiveRecord::RecordInvalid
render :action => 'new', :layout => false
end
def edit
@stock_article = StockArticle.find(params[:id])
render :layout => false
end
def update
@stock_article = StockArticle.find(params[:id])
@stock_article.update!(params[:stock_article])
render :layout => false
rescue ActiveRecord::RecordInvalid
render :action => 'edit', :layout => false
render layout: false
end
def show
@ -65,24 +21,68 @@ class StockitController < ApplicationController
@stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC')
end
# three possibilites to fill a new_stock_article form
# (1) start from blank or use params
def new
@stock_article = StockArticle.new(params[:stock_article])
render layout: false
end
# (2) StockArticle as template
def copy
@stock_article = StockArticle.find(params[:stock_article_id]).dup
render layout: false
end
# (3) non-stock Article as template
def derive
@stock_article = Article.find(params[:old_article_id]).becomes(StockArticle).dup
render layout: false
end
def edit
@stock_article = StockArticle.find(params[:id])
render layout: false
end
def create
@stock_article = StockArticle.new({ quantity: 0 }.merge(params[:stock_article]))
@stock_article.save!
render layout: false
rescue ActiveRecord::RecordInvalid
render action: 'new', layout: false
end
def update
@stock_article = StockArticle.find(params[:id])
@stock_article.update!(params[:stock_article])
render layout: false
rescue ActiveRecord::RecordInvalid
render action: 'edit', layout: false
end
def show_on_stock_article_update # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
render :layout => false
render layout: false
end
def destroy
@stock_article = StockArticle.find(params[:id])
@stock_article.mark_as_deleted
render :layout => false
rescue => error
render :partial => "destroy_fail", :layout => false,
:locals => { :fail_msg => I18n.t('errors.general_msg', :msg => error.message) }
render layout: false
rescue StandardError => e
render partial: 'destroy_fail', layout: false,
locals: { fail_msg: I18n.t('errors.general_msg', msg: e.message) }
end
# TODO: Fix this!!
def articles_search
@articles = Article.not_in_stock.limit(8).where('name LIKE ?', "%#{params[:term]}%")
render :json => @articles.map(&:name)
render json: @articles.map(&:name)
end
end

View file

@ -9,7 +9,7 @@ class StylesController < ApplicationController
def foodcoop
css = FoodsoftConfig[:custom_css]
if css.blank?
render body: nil, content_type: 'text/css', status: 404
render body: nil, content_type: 'text/css', status: :not_found
else
expires_in 1.week, public: true if params[:md5].present?
render body: css, content_type: 'text/css'

View file

@ -1,5 +1,5 @@
class SuppliersController < ApplicationController
before_action :authenticate_suppliers, :except => [:index, :list]
before_action :authenticate_suppliers, except: %i[index list]
helper :deliveries
def index
@ -24,6 +24,10 @@ class SuppliersController < ApplicationController
end
end
def edit
@supplier = Supplier.find(params[:id])
end
def create
@supplier = Supplier.new(supplier_params)
@supplier.supplier_category ||= SupplierCategory.first
@ -31,21 +35,17 @@ class SuppliersController < ApplicationController
flash[:notice] = I18n.t('suppliers.create.notice')
redirect_to suppliers_path
else
render :action => 'new'
render action: 'new'
end
end
def edit
@supplier = Supplier.find(params[:id])
end
def update
@supplier = Supplier.find(params[:id])
if @supplier.update(supplier_params)
flash[:notice] = I18n.t('suppliers.update.notice')
redirect_to @supplier
else
render :action => 'edit'
render action: 'edit'
end
end
@ -54,8 +54,8 @@ class SuppliersController < ApplicationController
@supplier.mark_as_deleted
flash[:notice] = I18n.t('suppliers.destroy.notice')
redirect_to suppliers_path
rescue => e
flash[:error] = I18n.t('errors.general_msg', :msg => e.message)
rescue StandardError => e
flash[:error] = I18n.t('errors.general_msg', msg: e.message)
redirect_to @supplier
end

View file

@ -11,35 +11,33 @@ class TasksController < ApplicationController
@accepted_tasks = Task.accepted_tasks_for(current_user)
end
def new
@task = Task.new(current_user_id: current_user.id)
end
def create
@task = Task.new(current_user_id: current_user.id)
@task.created_by = current_user
@task.attributes = (task_params)
if params[:periodic]
@task.periodic_task_group = PeriodicTaskGroup.new
end
if @task.save
@task.periodic_task_group.create_tasks_for_upfront_days if params[:periodic]
redirect_to tasks_url, :notice => I18n.t('tasks.create.notice')
else
render :template => "tasks/new"
end
end
def show
@task = Task.find(params[:id])
end
def new
@task = Task.new(current_user_id: current_user.id)
end
def edit
@task = Task.find(params[:id])
@periodic = !!params[:periodic]
@task.current_user_id = current_user.id
end
def create
@task = Task.new(current_user_id: current_user.id)
@task.created_by = current_user
@task.attributes = (task_params)
@task.periodic_task_group = PeriodicTaskGroup.new if params[:periodic]
if @task.save
@task.periodic_task_group.create_tasks_for_upfront_days if params[:periodic]
redirect_to tasks_url, notice: I18n.t('tasks.create.notice')
else
render template: 'tasks/new'
end
end
def update
@task = Task.find(params[:id])
task_group = @task.periodic_task_group
@ -50,16 +48,14 @@ class TasksController < ApplicationController
if @task.errors.empty? && @task.save
task_group.update_tasks_including(@task, prev_due_date) if params[:periodic]
flash[:notice] = I18n.t('tasks.update.notice')
if was_periodic && !@task.periodic?
flash[:notice] = I18n.t('tasks.update.notice_converted')
end
flash[:notice] = I18n.t('tasks.update.notice_converted') if was_periodic && !@task.periodic?
if @task.workgroup
redirect_to workgroup_tasks_url(workgroup_id: @task.workgroup_id)
else
redirect_to tasks_url
end
else
render :template => "tasks/edit"
render template: 'tasks/edit'
end
end
@ -75,7 +71,7 @@ class TasksController < ApplicationController
end
task.update_ordergroup_stats(user_ids)
redirect_to tasks_url, :notice => I18n.t('tasks.destroy.notice')
redirect_to tasks_url, notice: I18n.t('tasks.destroy.notice')
end
# assign current_user to the task and set the assignment to "accepted"
@ -85,20 +81,20 @@ class TasksController < ApplicationController
if ass = task.is_assigned?(current_user)
ass.update_attribute(:accepted, true)
else
task.assignments.create(:user => current_user, :accepted => true)
task.assignments.create(user: current_user, accepted: true)
end
redirect_to user_tasks_path, :notice => I18n.t('tasks.accept.notice')
redirect_to user_tasks_path, notice: I18n.t('tasks.accept.notice')
end
# deletes assignment between current_user and given taskcurrent_user_id: current_user.id
def reject
Task.find(params[:id]).users.delete(current_user)
redirect_to :action => "index"
redirect_to action: 'index'
end
def set_done
Task.find(params[:id]).update_attribute :done, true
redirect_to tasks_url, :notice => I18n.t('tasks.set_done.notice')
redirect_to tasks_url, notice: I18n.t('tasks.set_done.notice')
end
# Shows all tasks, which are already done
@ -109,9 +105,9 @@ class TasksController < ApplicationController
# shows workgroup (normal group) to edit weekly_tasks_template
def workgroup
@group = Group.find(params[:workgroup_id])
if @group.is_a? Ordergroup
redirect_to tasks_url, :alert => I18n.t('tasks.error_not_found')
end
return unless @group.is_a? Ordergroup
redirect_to tasks_url, alert: I18n.t('tasks.error_not_found')
end
private

View file

@ -3,7 +3,7 @@ class UsersController < ApplicationController
def index
@users = User.undeleted.natural_search(params[:q])
respond_to do |format|
format.json { render :json => @users.map(&:token_attributes).to_json }
format.json { render json: @users.map(&:token_attributes).to_json }
end
end
end

View file

@ -1,11 +1,11 @@
class OrderByArticles < OrderPdf
def filename
I18n.t('documents.order_by_articles.filename', :name => order.name, :date => order.ends.to_date) + '.pdf'
I18n.t('documents.order_by_articles.filename', name: order.name, date: order.ends.to_date) + '.pdf'
end
def title
I18n.t('documents.order_by_articles.title', :name => order.name,
:date => order.ends.strftime(I18n.t('date.formats.default')))
I18n.t('documents.order_by_articles.title', name: order.name,
date: order.ends.strftime(I18n.t('date.formats.default')))
end
def body

View file

@ -1,11 +1,11 @@
class OrderByGroups < OrderPdf
def filename
I18n.t('documents.order_by_groups.filename', :name => order.name, :date => order.ends.to_date) + '.pdf'
I18n.t('documents.order_by_groups.filename', name: order.name, date: order.ends.to_date) + '.pdf'
end
def title
I18n.t('documents.order_by_groups.title', :name => order.name,
:date => order.ends.strftime(I18n.t('date.formats.default')))
I18n.t('documents.order_by_groups.title', name: order.name,
date: order.ends.strftime(I18n.t('date.formats.default')))
end
def body

View file

@ -2,7 +2,7 @@ class OrderFax < OrderPdf
BATCH_SIZE = 250
def filename
I18n.t('documents.order_fax.filename', :name => order.name, :date => order.ends.to_date) + '.pdf'
I18n.t('documents.order_fax.filename', name: order.name, date: order.ends.to_date) + '.pdf'
end
def title
@ -20,16 +20,18 @@ class OrderFax < OrderPdf
move_down 5
text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :right
move_down 5
unless order.supplier.try(:customer_number).blank?
text "#{Supplier.human_attribute_name :customer_number}: #{order.supplier[:customer_number]}", size: fontsize(9), align: :right
if order.supplier.try(:customer_number).present?
text "#{Supplier.human_attribute_name :customer_number}: #{order.supplier[:customer_number]}",
size: fontsize(9), align: :right
move_down 5
end
unless contact[:phone].blank?
if contact[:phone].present?
text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :right
move_down 5
end
unless contact[:email].blank?
text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :right
if contact[:email].present?
text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9),
align: :right
end
end
@ -38,7 +40,7 @@ class OrderFax < OrderPdf
text order.name
move_down 5
text order.supplier.try(:address).to_s
unless order.supplier.try(:fax).blank?
if order.supplier.try(:fax).present?
move_down 5
text "#{Supplier.human_attribute_name :fax}: #{order.supplier[:fax]}"
end
@ -50,7 +52,7 @@ class OrderFax < OrderPdf
move_down 10
text "#{Delivery.human_attribute_name :date}:"
move_down 10
unless order.supplier.try(:contact_person).blank?
if order.supplier.try(:contact_person).present?
text "#{Supplier.human_attribute_name :contact_person}: #{order.supplier[:contact_person]}"
move_down 10
end
@ -78,8 +80,8 @@ class OrderFax < OrderPdf
table.row(0).border_bottom_width = 2
table.columns(1).align = :right
table.columns(3..6).align = :right
table.row(data.length - 1).columns(0..5).borders = [:top, :bottom]
table.row(data.length - 1).columns(0).borders = [:top, :bottom, :left]
table.row(data.length - 1).columns(0..5).borders = %i[top bottom]
table.row(data.length - 1).columns(0).borders = %i[top bottom left]
table.row(data.length - 1).border_top_width = 2
end
# font_size: fontsize(8),
@ -98,7 +100,7 @@ class OrderFax < OrderPdf
.preload(:article, :article_price)
end
def each_order_article
order_articles.find_each_with_order(batch_size: BATCH_SIZE) { |oa| yield oa }
def each_order_article(&block)
order_articles.find_each_with_order(batch_size: BATCH_SIZE, &block)
end
end

View file

@ -3,12 +3,12 @@ class OrderMatrix < OrderPdf
PLACEHOLDER_CHAR = 'X'
def filename
I18n.t('documents.order_matrix.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf'
I18n.t('documents.order_matrix.filename', name: @order.name, date: @order.ends.to_date) + '.pdf'
end
def title
I18n.t('documents.order_matrix.title', :name => @order.name,
:date => @order.ends.strftime(I18n.t('date.formats.default')))
I18n.t('documents.order_matrix.title', name: @order.name,
date: @order.ends.strftime(I18n.t('date.formats.default')))
end
def body
@ -87,7 +87,7 @@ class OrderMatrix < OrderPdf
table.cells.border_width = 0.5
table.cells.border_color = '666666'
table.row(0).borders = [:bottom, :left]
table.row(0).borders = %i[bottom left]
table.row(0).padding = [2, 0, 2, 0]
table.row(1..-1).height = row_height_1
table.column(0..1).borders = []
@ -106,7 +106,7 @@ class OrderMatrix < OrderPdf
table.column(2 + idx).border_width = 2
end
table.row_colors = ['dddddd', 'ffffff']
table.row_colors = %w[dddddd ffffff]
end
first_page = false

View file

@ -28,7 +28,11 @@ module Admin::ConfigsHelper
options[:default] = options[:input_html].delete(:value)
return form.input key, options, &block
end
block ||= proc { config_input_field form, key, options.merge(options[:input_html]) } if options[:as] == :select_recurring
if options[:as] == :select_recurring
block ||= proc {
config_input_field form, key, options.merge(options[:input_html])
}
end
form.input key, options, &block
end
@ -57,11 +61,12 @@ module Admin::ConfigsHelper
unchecked_value = options.delete(:unchecked_value) || 'false'
options[:checked] = 'checked' if v = options.delete(:value) && v != 'false'
# different key for hidden field so that allow clocking on label focuses the control
form.hidden_field(key, id: "#{key}_", value: unchecked_value, as: :hidden) + form.check_box(key, options, checked_value, false)
form.hidden_field(key, id: "#{key}_", value: unchecked_value,
as: :hidden) + form.check_box(key, options, checked_value, false)
elsif options[:as] == :select_recurring
options[:value] = FoodsoftDateUtil.rule_from(options[:value])
options[:rules] ||= []
options[:rules].unshift options[:value] unless options[:value].blank?
options[:rules].unshift options[:value] if options[:value].present?
options[:rules].push [I18n.t('recurring_select.not_recurring'), '{}'] if options.delete(:allow_blank) # blank after current value
form.select_recurring key, options.delete(:rules).uniq, options
else
@ -73,7 +78,7 @@ module Admin::ConfigsHelper
# @param form [ActionView::Helpers::FormBuilder] Form object.
# @param key [Symbol, String] Configuration key of a boolean (e.g. +use_messages+).
# @option options [String] :label Label to show
def config_use_heading(form, key, options = {})
def config_use_heading(form, key, options = {}, &block)
head = content_tag :label do
lbl = options[:label] || config_input_label(form, key)
field = config_input_field(form, key, as: :boolean, boolean_style: :inline,
@ -83,9 +88,7 @@ module Admin::ConfigsHelper
content_tag :span, (lbl + field).html_safe, config_input_tooltip_options(form, key, {})
end
end
fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}") do
yield
end
fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}", &block)
head + fields
end
@ -127,7 +130,7 @@ module Admin::ConfigsHelper
# tooltip with help info to the right
cfg_path = form.lookup_model_names[1..-1] + [key]
tooltip = I18n.t("config.hints.#{cfg_path.map(&:to_s).join('.')}", default: '')
unless tooltip.blank?
if tooltip.present?
options[:data] ||= {}
options[:data][:toggle] ||= 'tooltip'
options[:data][:placement] ||= 'right'

View file

@ -2,9 +2,7 @@ module Admin::OrdergroupsHelper
def ordergroup_members_title(ordergroup)
s = ''
s += ordergroup.users.map(&:name).join(', ') if ordergroup.users.any?
if ordergroup.contact_person.present?
s += "\n" + Ordergroup.human_attribute_name(:contact) + ": " + ordergroup.contact_person
end
s += "\n" + Ordergroup.human_attribute_name(:contact) + ': ' + ordergroup.contact_person if ordergroup.contact_person.present?
s
end
end

View file

@ -4,7 +4,7 @@ module ApplicationHelper
include PathHelper
def format_time(time = Time.now)
I18n.l(time, :format => "%d.%m.%Y %H:%M") unless time.nil?
I18n.l(time, format: :foodsoft_datetime) unless time.nil?
end
def format_date(time = Time.now)
@ -16,7 +16,7 @@ module ApplicationHelper
end
def format_datetime_timespec(time, format)
I18n.l(time, :format => format) unless (time.nil? || format.nil?)
I18n.l(time, format: format) unless time.nil? || format.nil?
end
def format_currency(amount)
@ -26,28 +26,28 @@ module ApplicationHelper
# Splits an IBAN into groups of 4 digits displayed with margins in between
def format_iban(iban)
iban && iban.scan(/..?.?.?/).map { |item| content_tag(:span, item, style: "margin-right: 0.5em;") }.join.html_safe
iban && iban.scan(/..?.?.?/).map { |item| content_tag(:span, item, style: 'margin-right: 0.5em;') }.join.html_safe
end
# Creates ajax-controlled-links for pagination
def pagination_links_remote(collection, options = {})
per_page = options[:per_page] || @per_page
params = options[:params] || {}
params = params.merge({ :per_page => per_page })
paginate collection, :params => params, :remote => true
params = params.merge({ per_page: per_page })
paginate collection, params: params, remote: true
end
# Link-collection for per_page-options when using the pagination-plugin
def items_per_page(options = {})
per_page_options = options[:per_page_options] || [20, 50, 100, 500]
current = options[:current] || @per_page
params = params || {}
params ||= {}
links = per_page_options.map do |per_page|
params.merge!({ :per_page => per_page })
params.merge!({ per_page: per_page })
link_class = 'btn'
link_class << ' disabled' if per_page == current
link_to(per_page, params, :remote => true, class: link_class)
link_to(per_page, params, remote: true, class: link_class)
end
if options[:wrap] == false
@ -63,21 +63,19 @@ module ApplicationHelper
# Hmtl options
remote = options[:remote].nil? ? true : options[:remote]
class_name = case params[:sort]
when key then
when key
'sortup'
when key + '_reverse' then
when key + '_reverse'
'sortdown'
else
nil
end
html_options = {
:title => I18n.t('helpers.application.sort_by', text: text),
:remote => remote,
:class => class_name
title: I18n.t('helpers.application.sort_by', text: text),
remote: remote,
class: class_name
}
# Url options
key += "_reverse" if params[:sort] == key
key += '_reverse' if params[:sort] == key
per_page = options[:per_page] || @per_page
url_options = params.merge(per_page: per_page, sort: key)
url_options.merge!({ page: params[:page] }) if params[:page]
@ -95,14 +93,16 @@ module ApplicationHelper
# be overridden by the option 'desc'.
# Other options are passed through to I18n.
def heading_helper(model, attribute, options = {})
i18nopts = { count: 2 }.merge(options.select { |a| !['short', 'desc'].include?(a) })
i18nopts = { count: 2 }.merge(options.select { |a| !%w[short desc].include?(a) })
s = model.human_attribute_name(attribute, i18nopts)
if options[:short]
desc = options[:desc]
desc ||= model.human_attribute_name("#{attribute}_desc".to_sym, options.merge({ fallback: true, default: '', count: 2 }))
desc ||= model.human_attribute_name("#{attribute}_desc".to_sym,
options.merge({ fallback: true, default: '', count: 2 }))
desc.blank? && desc = s
sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({ fallback: true, default: '', count: 2 }))
s = raw "<abbr title='#{desc}'>#{sshort}</abbr>" unless sshort.blank?
sshort = model.human_attribute_name("#{attribute}_short".to_sym,
options.merge({ fallback: true, default: '', count: 2 }))
s = raw "<abbr title='#{desc}'>#{sshort}</abbr>" if sshort.present?
end
s
end
@ -117,7 +117,7 @@ module ApplicationHelper
# Returns the weekday. 0 is sunday, 1 is monday and so on
def weekday(dayNumber)
weekdays = I18n.t('date.day_names')
return weekdays[dayNumber]
weekdays[dayNumber]
end
# to set a title for both the h1-tag and the title in the header
@ -136,13 +136,13 @@ module ApplicationHelper
def icon(name, options = {})
icons = {
:delete => { :file => 'b_drop.png', :alt => I18n.t('ui.delete') },
:edit => { :file => 'b_edit.png', :alt => I18n.t('ui.edit') },
:members => { :file => 'b_users.png', :alt => I18n.t('helpers.application.edit_user') }
delete: { file: 'b_drop.png', alt: I18n.t('ui.delete') },
edit: { file: 'b_edit.png', alt: I18n.t('ui.edit') },
members: { file: 'b_users.png', alt: I18n.t('helpers.application.edit_user') }
}
options[:alt] ||= icons[name][:alt]
options[:title] ||= icons[name][:title]
options.merge!({ :size => '16x16', :border => "0" })
options.merge!({ size: '16x16', border: '0' })
image_tag icons[name][:file], options
end
@ -150,27 +150,29 @@ module ApplicationHelper
# Remote links with default 'loader'.gif during request
def remote_link_to(text, options = {})
remote_options = {
:before => "Element.show('loader')",
:success => "Element.hide('loader')",
:method => :get
before: "Element.show('loader')",
success: "Element.hide('loader')",
method: :get
}
link_to(text, options[:url], remote_options.merge(options))
end
def format_roles(record, icon = false)
roles = %w(suppliers article_meta orders pickups finance invoices admin)
roles = %w[suppliers article_meta orders pickups finance invoices admin]
roles.select! { |role| record.send "role_#{role}?" }
names = Hash[roles.map { |r| [r, I18n.t("helpers.application.role_#{r}")] }]
names = roles.index_with { |r| I18n.t("helpers.application.role_#{r}") }
if icon
roles.map { |r| image_tag("role-#{r}.png", size: '22x22', border: 0, alt: names[r], title: names[r]) }.join('&nbsp;').html_safe
roles.map do |r|
image_tag("role-#{r}.png", size: '22x22', border: 0, alt: names[r], title: names[r])
end.join('&nbsp;').html_safe
else
roles.map { |r| names[r] }.join(', ')
end
end
def link_to_gmaps(address)
link_to h(address), "http://maps.google.com/?q=#{h(address)}", :title => I18n.t('helpers.application.show_google_maps'),
:target => "_blank"
link_to h(address), "http://maps.google.com/?q=#{h(address)}", title: I18n.t('helpers.application.show_google_maps'),
target: '_blank', rel: 'noopener'
end
# Returns flash messages html.
@ -186,8 +188,8 @@ module ApplicationHelper
type = :success if type == 'notice'
type = :error if type == 'alert'
text = content_tag(:div,
content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => "close", "data-dismiss" => "alert") +
message, :class => "alert fade in alert-#{type}")
content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => 'close', 'data-dismiss' => 'alert') +
message, class: "alert fade in alert-#{type}")
flash_messages << text if message
end
flash_messages.join("\n").html_safe
@ -195,17 +197,17 @@ module ApplicationHelper
# render base errors in a form after failed validation
# http://railsapps.github.io/twitter-bootstrap-rails.html
def base_errors resource
def base_errors(resource)
return '' if resource.errors.empty? || resource.errors[:base].empty?
messages = resource.errors[:base].map { |msg| content_tag(:li, msg) }.join
render :partial => 'shared/base_errors', :locals => { :error_messages => messages }
render partial: 'shared/base_errors', locals: { error_messages: messages }
end
# show a user, depending on settings
def show_user(user = @current_user, options = {})
if user.nil?
"?"
'?'
elsif FoodsoftConfig[:use_nick]
if options[:full] && options[:markup]
raw "<b>#{h user.nick}</b> (#{h user.first_name} #{h user.last_name})"
@ -216,7 +218,7 @@ module ApplicationHelper
user.nick.nil? ? I18n.t('helpers.application.nick_fallback') : user.nick
end
else
"#{user.first_name} #{user.last_name}" + (options[:unique] ? " (\##{user.id})" : '')
"#{user.first_name} #{user.last_name}" + (options[:unique] ? " (##{user.id})" : '')
end
end
@ -258,9 +260,9 @@ module ApplicationHelper
# @return [String] stylesheet tag for foodcoop CSS style (+custom_css+ foodcoop config)
# @see #foodcoop_css_path
def foodcoop_css_tag(options = {})
unless FoodsoftConfig[:custom_css].blank?
stylesheet_link_tag foodcoop_css_path, media: 'all'
end
def foodcoop_css_tag(_options = {})
return if FoodsoftConfig[:custom_css].blank?
stylesheet_link_tag foodcoop_css_path, media: 'all'
end
end

View file

@ -3,13 +3,13 @@ module ArticlesHelper
def highlight_new(unequal_attributes, attribute)
return unless unequal_attributes
unequal_attributes.has_key?(attribute) ? "background-color: yellow" : ""
unequal_attributes.has_key?(attribute) ? 'background-color: yellow' : ''
end
def row_classes(article)
classes = []
classes << "unavailable" if !article.availability
classes << "just-updated" if article.recently_updated && article.availability
classes.join(" ")
classes << 'unavailable' unless article.availability
classes << 'just-updated' if article.recently_updated && article.availability
classes.join(' ')
end
end

View file

@ -11,11 +11,11 @@ module DeliveriesHelper
def articles_for_select2(articles, except = [], &block)
articles = articles.reorder('articles.name ASC')
articles = articles.reject { |a| not except.index(a.id).nil? } if except
block_given? or block = Proc.new { |a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" }
articles = articles.reject { |a| !except.index(a.id).nil? } if except
block_given? or block = proc { |a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" }
articles.map do |a|
{ :id => a.id, :text => block.call(a) }
end.unshift({ :id => '', :text => '' })
{ id: a.id, text: block.call(a) }
end.unshift({ id: '', text: '' })
end
def articles_for_table(articles)
@ -23,10 +23,14 @@ module DeliveriesHelper
end
def stock_change_remove_link(stock_change_form)
return link_to t('deliveries.stock_change_fields.remove_article'), "#", :class => 'remove_new_stock_change btn btn-small' if stock_change_form.object.new_record?
if stock_change_form.object.new_record?
return link_to t('deliveries.stock_change_fields.remove_article'), '#',
class: 'remove_new_stock_change btn btn-small'
end
output = stock_change_form.hidden_field :_destroy
output += link_to t('deliveries.stock_change_fields.remove_article'), "#", :class => 'destroy_stock_change btn btn-small'
return output.html_safe
output += link_to t('deliveries.stock_change_fields.remove_article'), '#',
class: 'destroy_stock_change btn btn-small'
output.html_safe
end
end

View file

@ -2,11 +2,11 @@ module Finance::BalancingHelper
def balancing_view_partial
view = params[:view] || 'edit_results'
case view
when 'edit_results' then
when 'edit_results'
'edit_results_by_articles'
when 'groups_overview' then
when 'groups_overview'
'shared/articles_by/groups'
when 'articles_overview' then
when 'articles_overview'
'shared/articles_by/articles'
end
end

View file

@ -1,9 +1,9 @@
module Finance::InvoicesHelper
def format_delivery_item delivery
def format_delivery_item(delivery)
format_date(delivery.date)
end
def format_order_item order
def format_order_item(order)
"#{format_date(order.ends)} (#{number_to_currency(order.sum)})"
end
end

View file

@ -2,12 +2,12 @@ module GroupOrderArticlesHelper
# return an edit field for a GroupOrderArticle result
def group_order_article_edit_result(goa)
result = number_with_precision goa.result, strip_insignificant_zeros: true
unless goa.group_order.order.finished? && current_user.role_finance?
result
else
if goa.group_order.order.finished? && current_user.role_finance?
simple_form_for goa, remote: true, html: { 'data-submit-onchange' => 'changed', class: 'delta-input' } do |f|
f.input_field :result, as: :delta, class: 'input-nano', data: { min: 0 }, id: "r_#{goa.id}", value: result
end
else
result
end
end
end

View file

@ -1,10 +1,11 @@
module GroupOrdersHelper
def data_to_js(ordering_data)
ordering_data[:order_articles].map { |id, data|
[id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance], data[:used_quantity], data[:quantity_available]]
}.map { |row|
ordering_data[:order_articles].map do |id, data|
[id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance],
data[:used_quantity], data[:quantity_available]]
end.map do |row|
"addData(#{row.join(', ')});"
}.join("\n")
end.join("\n")
end
# Returns a link to the page where a group_order can be edited.
@ -14,9 +15,9 @@ module GroupOrdersHelper
path = if options[:show] && group_order
group_order_path(group_order)
elsif group_order
edit_group_order_path(group_order, :order_id => order.id)
edit_group_order_path(group_order, order_id: order.id)
else
new_group_order_path(:order_id => order.id)
new_group_order_path(order_id: order.id)
end
options.delete(:show)
name = block_given? ? capture(&block) : order.name
@ -26,7 +27,7 @@ module GroupOrdersHelper
# Return css class names for order result table
def order_article_class_name(quantity, tolerance, result)
if (quantity + tolerance > 0)
if quantity + tolerance > 0
result > 0 ? 'success' : 'failed'
else
'ignored'
@ -45,20 +46,12 @@ module GroupOrdersHelper
end
def get_missing_units_css_class(quantity_missing)
if (quantity_missing == 1)
return 'missing-few';
elsif (quantity_missing == 0)
return ''
if quantity_missing == 1
'missing-few'
elsif quantity_missing == 0
''
else
return 'missing-many'
'missing-many'
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

View file

@ -1,6 +1,6 @@
module OrderArticlesHelper
def article_label_with_unit(article)
pkg_info = pkg_helper(article, plain: true)
"#{article.name} (#{[article.unit, pkg_info].reject(&:blank?).join(' ')})"
"#{article.name} (#{[article.unit, pkg_info].compact_blank.join(' ')})"
end
end

View file

@ -18,7 +18,7 @@ module OrdersHelper
def options_for_suppliers_to_select
options = [[I18n.t('helpers.orders.option_choose')]]
options += Supplier.map { |s| [s.name, url_for(action: "new", supplier_id: s.id)] }
options += Supplier.map { |s| [s.name, url_for(action: 'new', supplier_id: s.id)] }
options += [[I18n.t('helpers.orders.option_stock'), url_for(action: 'new', supplier_id: nil)]]
options_for_select(options)
end
@ -29,13 +29,13 @@ module OrdersHelper
nil
else
units_info = []
[:units_to_order, :units_billed, :units_received].map do |unit|
if n = order_article.send(unit)
line = n.to_s + ' '
line += pkg_helper(order_article.price, options) + ' ' unless n == 0
line += OrderArticle.human_attribute_name("#{unit}_short", count: n)
units_info << line
end
%i[units_to_order units_billed units_received].map do |unit|
next unless n = order_article.send(unit)
line = n.to_s + ' '
line += pkg_helper(order_article.price, options) + ' ' unless n == 0
line += OrderArticle.human_attribute_name("#{unit}_short", count: n)
units_info << line
end
units_info.join(', ').html_safe
end
@ -67,8 +67,8 @@ module OrdersHelper
def pkg_helper_icon(c = nil, options = {})
options = { tag: 'i', class: '' }.merge(options)
if c.nil?
c = "&nbsp;".html_safe
options[:class] += " icon-only"
c = '&nbsp;'.html_safe
options[:class] += ' icon-only'
end
content_tag(options[:tag], c, class: "package #{options[:class]}").html_safe
end
@ -94,11 +94,12 @@ module OrdersHelper
autocomplete: 'off'
if order_article.result_manually_changed?
input_html = content_tag(:span, class: 'input-prepend intable', title: t('orders.edit_amount.field_locked_title', default: '')) {
input_html = content_tag(:span, class: 'input-prepend intable',
title: t('orders.edit_amount.field_locked_title', default: '')) do
button_tag(nil, type: :button, class: 'btn unlocker') {
content_tag(:i, nil, class: 'icon icon-unlock')
} + input_html
}
end
end
input_html.html_safe
@ -109,18 +110,16 @@ module OrdersHelper
def ordergroup_count(order)
group_orders = order.group_orders.includes(:ordergroup)
txt = "#{group_orders.count} #{Ordergroup.model_name.human count: group_orders.count}"
if group_orders.count == 0
return txt
else
desc = group_orders.includes(:ordergroup).map { |g| g.ordergroup_name }.join(', ')
content_tag(:abbr, txt, title: desc).html_safe
end
return txt if group_orders.count == 0
desc = group_orders.includes(:ordergroup).map { |g| g.ordergroup_name }.join(', ')
content_tag(:abbr, txt, title: desc).html_safe
end
# @param order_or_supplier [Order, Supplier] Order or supplier to link to
# @return [String] Link to order or supplier, showing its name.
def supplier_link(order_or_supplier)
if order_or_supplier.kind_of?(Order) && order_or_supplier.stockit?
if order_or_supplier.is_a?(Order) && order_or_supplier.stockit?
link_to(order_or_supplier.name, stock_articles_path).html_safe
else
link_to(@order.supplier.name, supplier_path(@order.supplier)).html_safe
@ -152,19 +151,8 @@ module OrdersHelper
if order.stockit?
content_tag :div, t('orders.index.action_receive'), class: "btn disabled #{options[:class]}"
else
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
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

View file

@ -1,8 +1,8 @@
module StockitHelper
def stock_article_classes(article)
class_names = []
class_names << "unavailable" if article.quantity_available <= 0
class_names.join(" ")
class_names << 'unavailable' if article.quantity_available <= 0
class_names.join(' ')
end
def link_to_stock_change_reason(stock_change)
@ -17,8 +17,8 @@ module StockitHelper
def stock_article_price_hint(stock_article)
t('simple_form.hints.stock_article.edit_stock_article.price',
:stock_article_copy_link => link_to(t('stockit.form.copy_stock_article'),
stock_article_copy_path(stock_article),
:remote => true))
stock_article_copy_link: link_to(t('stockit.form.copy_stock_article'),
stock_article_copy_path(stock_article),
remote: true))
end
end

View file

@ -1,16 +1,16 @@
module TasksHelper
def task_assignments(task)
task.assignments.map do |ass|
content_tag :span, show_user(ass.user), :class => (ass.accepted? ? 'accepted' : 'unaccepted')
end.join(", ").html_safe
content_tag :span, show_user(ass.user), class: (ass.accepted? ? 'accepted' : 'unaccepted')
end.join(', ').html_safe
end
# generate colored number of still required users
def highlighted_required_users(task)
unless task.enough_users_assigned?
content_tag :span, task.still_required_users, class: 'badge badge-important',
title: I18n.t('helpers.tasks.required_users', :count => task.still_required_users)
end
return if task.enough_users_assigned?
content_tag :span, task.still_required_users, class: 'badge badge-important',
title: I18n.t('helpers.tasks.required_users', count: task.still_required_users)
end
def task_title(task)

View file

@ -6,7 +6,7 @@ class DeltaInput < SimpleForm::Inputs::StringInput
options[:data] ||= {}
options[:data][:delta] ||= 1
options[:autocomplete] ||= 'off'
# TODO get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option
# TODO: get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option
template.content_tag :div, class: 'delta-input input-prepend input-append' do
delta_button(content_tag(:i, nil, class: 'icon icon-minus'), -1, options) +

View file

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

View file

@ -0,0 +1,7 @@
// app/javascript/trix-editor-overrides.js
window.addEventListener("trix-file-accept", function(event) {
if (event.file.size > 1024 * 1024 * 512) {
event.preventDefault()
alert(I18n.t('js.trix_editor.file_size_alert'))
}
})

View file

@ -29,7 +29,7 @@ class AppleBar
def mean_order_amount_per_job
(1 / @global_avg).round
rescue
rescue StandardError
0
end

View file

@ -23,7 +23,7 @@ class ArticlesCsv < RenderCsv
def data
@object.each do |o|
yield [
'',
o.availability ? I18n.t('simple_form.yes') : I18n.t('simple_form.no'),
o.order_number,
o.name,
o.note,

View file

@ -41,14 +41,14 @@ class BankAccountConnector
end
end
@@registered_classes = Set.new
@registered_classes = Set.new
def self.register(klass)
@@registered_classes.add klass
@registered_classes.add klass
end
def self.find(iban)
@@registered_classes.each do |klass|
@registered_classes.each do |klass|
return klass if klass.handles(iban)
end
nil

View file

@ -17,16 +17,16 @@ class BankAccountInformationImporter
ret = 0
booked.each do |t|
amount = parse_account_information_amount t[:transactionAmount]
entityName = amount < 0 ? t[:creditorName] : t[:debtorName]
entityAccount = amount < 0 ? t[:creditorAccount] : t[:debtorAccount]
entity_name = amount < 0 ? t[:creditorName] : t[:debtorName]
entity_account = amount < 0 ? t[:creditorAccount] : t[:debtorAccount]
reference = [t[:endToEndId], t[:remittanceInformationUnstructured]].join("\n").strip
@bank_account.bank_transactions.where(external_id: t[:transactionId]).first_or_create.update({
date: t[:bookingDate],
amount: amount,
iban: entityAccount && entityAccount[:iban],
iban: entity_account && entity_account[:iban],
reference: reference,
text: entityName,
text: entity_name,
receipt: t[:additionalInformation]
})
ret += 1
@ -34,7 +34,7 @@ class BankAccountInformationImporter
balances = (data[:balances] ? data[:balances].map { |b| [b[:balanceType], b[:balanceAmount]] } : []).to_h
balance = balances.values.first
%w(closingBooked expected authorised openingBooked interimAvailable forwardAvailable nonInvoiced).each do |type|
%w[closingBooked expected authorised openingBooked interimAvailable forwardAvailable nonInvoiced].each do |type|
value = balances[type]
if value
balance = value

View file

@ -10,66 +10,68 @@ module DateTimeAttributeValidate
super
attributes.each do |attribute|
validate -> { self.send("#{attribute}_datetime_value_valid") }
validate -> { send("#{attribute}_datetime_value_valid") }
# allow resetting the field to nil
before_validation do
if self.instance_variable_get("@#{attribute}_is_set")
date = self.instance_variable_get("@#{attribute}_date_value")
time = self.instance_variable_get("@#{attribute}_time_value")
if date.blank? && time.blank?
self.send("#{attribute}=", nil)
end
if instance_variable_get("@#{attribute}_is_set")
date = instance_variable_get("@#{attribute}_date_value")
time = instance_variable_get("@#{attribute}_time_value")
send("#{attribute}=", nil) if date.blank? && time.blank?
end
end
# remember old date and time values
define_method("#{attribute}_date_value=") do |val|
self.instance_variable_set("@#{attribute}_is_set", true)
self.instance_variable_set("@#{attribute}_date_value", val)
instance_variable_set("@#{attribute}_is_set", true)
instance_variable_set("@#{attribute}_date_value", val)
begin
self.send("#{attribute}_date=", val)
rescue
send("#{attribute}_date=", val)
rescue StandardError
nil
end
end
define_method("#{attribute}_time_value=") do |val|
self.instance_variable_set("@#{attribute}_is_set", true)
self.instance_variable_set("@#{attribute}_time_value", val)
instance_variable_set("@#{attribute}_is_set", true)
instance_variable_set("@#{attribute}_time_value", val)
begin
self.send("#{attribute}_time=", val)
rescue
send("#{attribute}_time=", val)
rescue StandardError
nil
end
end
# fallback to field when values are not set
define_method("#{attribute}_date_value") do
self.instance_variable_get("@#{attribute}_date_value") || self.send("#{attribute}_date").try { |e| e.strftime('%Y-%m-%d') }
instance_variable_get("@#{attribute}_date_value") || send("#{attribute}_date").try do |e|
e.strftime('%Y-%m-%d')
end
end
define_method("#{attribute}_time_value") do
self.instance_variable_get("@#{attribute}_time_value") || self.send("#{attribute}_time").try { |e| e.strftime('%H:%M') }
instance_variable_get("@#{attribute}_time_value") || send("#{attribute}_time").try do |e|
e.strftime('%H:%M')
end
end
private
# validate date and time
define_method("#{attribute}_datetime_value_valid") do
date = self.instance_variable_get("@#{attribute}_date_value")
date = instance_variable_get("@#{attribute}_date_value")
unless date.blank? || begin
Date.parse(date)
rescue
rescue StandardError
nil
end
errors.add(attribute, "is not a valid date") # @todo I18n
errors.add(attribute, 'is not a valid date') # @todo I18n
end
time = self.instance_variable_get("@#{attribute}_time_value")
time = instance_variable_get("@#{attribute}_time_value")
unless time.blank? || begin
Time.parse(time)
rescue
rescue StandardError
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

View file

@ -14,7 +14,7 @@ module Foodsoft
cattr_accessor :variables
# Hash of variables. Note that keys are Strings.
@@variables = {
@variables = {
'scope' => -> { FoodsoftConfig.scope },
'name' => -> { FoodsoftConfig[:name] },
'contact.street' => -> { FoodsoftConfig[:contact][:street] },
@ -39,13 +39,13 @@ module Foodsoft
'supplier_count' => -> { Supplier.undeleted.count },
'active_supplier_count' => -> { active_supplier_count },
'active_suppliers' => -> { active_suppliers },
'first_order_date' => -> { I18n.l Order.first.try { |o| o.starts.to_date } }
'first_order_date' => -> { I18n.l(Order.first.try { |o| o.starts.to_date }) }
}
# Return expanded variable
# @return [String] Expanded variable
def self.get(var)
s = @@variables[var.to_s]
s = @variables[var.to_s]
s.respond_to?(:call) ? s.call : s.to_s
end
@ -55,7 +55,7 @@ module Foodsoft
# @return [String] Expanded string
def self.expand(str, options = {})
str.gsub(/{{([._a-zA-Z0-9]+)}}/) do
options[::Regexp.last_match(1)] || self.get(::Regexp.last_match(1))
options[::Regexp.last_match(1)] || get(::Regexp.last_match(1))
end
end

View file

@ -70,7 +70,7 @@ class FoodsoftConfig
# Load initial config from development or production
set_config Rails.env
# Overwrite scope to have a better namescope than 'production'
self.scope = config[:default_scope] or raise "No default_scope is set"
self.scope = config[:default_scope] or raise 'No default_scope is set'
# Set defaults for backward-compatibility
set_missing
# Make sure relevant configuration is applied, also in single coops mode,
@ -79,7 +79,7 @@ class FoodsoftConfig
end
def init_mailing
[:protocol, :host, :port, :script_name].each do |k|
%i[protocol host port script_name].each do |k|
ActionMailer::Base.default_url_options[k] = self[k] if self[k]
end
end
@ -117,7 +117,7 @@ class FoodsoftConfig
# @return [Object] Value of the key.
def [](key)
if RailsSettings::CachedSettings.table_exists? && allowed_key?(key)
value = RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"]
value = RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"]
value = config[key] if value.nil?
value
else
@ -139,20 +139,20 @@ class FoodsoftConfig
if config[key] == value || (config[key].nil? && value == false)
# delete (ok if it was already deleted)
begin
RailsSettings::CachedSettings.destroy "foodcoop.#{self.scope}.#{key}"
RailsSettings::CachedSettings.destroy "foodcoop.#{scope}.#{key}"
rescue RailsSettings::Settings::SettingNotFound
end
else
# or store
RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] = value
RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"] = value
end
true
end
# @return [Array<String>] Configuration keys that are set (either in +app_config.yml+ or database).
def keys
keys = RailsSettings::CachedSettings.get_all("foodcoop.#{self.scope}.").try(:keys) || []
keys.map! { |k| k.gsub(/^foodcoop\.#{self.scope}\./, '') }
keys = RailsSettings::CachedSettings.get_all("foodcoop.#{scope}.").try(:keys) || []
keys.map! { |k| k.gsub(/^foodcoop\.#{scope}\./, '') }
keys += config.keys
keys.map(&:to_s).uniq
end
@ -181,10 +181,10 @@ class FoodsoftConfig
# @return [Boolean] Whether this key may be set in the database
def allowed_key?(key)
# fast check for keys without nesting
if self.config[:protected].include? key
!self.config[:protected][key]
if config[:protected].include? key
!config[:protected][key]
else
!self.config[:protected][:all]
!config[:protected][:all]
end
# @todo allow to check nested keys as well
end
@ -287,7 +287,9 @@ class FoodsoftConfig
def normalize_value(value)
value = value.map { |v| normalize_value(v) } if value.is_a? Array
if value.is_a? Hash
value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map { |a| [a[0], normalize_value(a[1])] }]
value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map do |a|
[a[0], normalize_value(a[1])]
end]
end
case value
when 'true' then true

View file

@ -8,26 +8,24 @@ module FoodsoftDateUtil
# @todo handle ical parse errors
occ = begin
schedule.next_occurrence(from).to_time
rescue
rescue StandardError
nil
end
end
if options && options[:time] && occ
occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight)
end
occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight) if options && options[:time] && occ
occ
end
# @param p [String, Symbol, Hash, IceCube::Rule] What to return a rule from.
# @param rule [String, Symbol, Hash, IceCube::Rule] What to return a rule from.
# @return [IceCube::Rule] Recurring rule
def self.rule_from(p)
case p
def self.rule_from(rule)
case rule
when String
IceCube::Rule.from_ical(p)
IceCube::Rule.from_ical(rule)
when Hash
IceCube::Rule.from_hash(p)
IceCube::Rule.from_hash(rule)
else
p
rule
end
end
end

25
app/lib/foodsoft_file.rb Normal file
View file

@ -0,0 +1,25 @@
# Foodsoft-file import
class FoodsoftFile
# parses a string from a foodsoft-file
# returns two arrays with articles and outlisted_articles
# the parsed article is a simple hash
def self.parse(file, options = {})
SpreadsheetFile.parse file, options do |row, row_index|
next if row[2].blank?
article = { order_number: row[1],
name: row[2],
note: row[3],
manufacturer: row[4],
origin: row[5],
unit: row[6],
price: row[7],
tax: row[8],
deposit: (row[9].nil? ? '0' : row[9]),
unit_quantity: row[10],
article_category: row[13] }
status = row[0] && row[0].strip.downcase == 'x' ? :outlisted : nil
yield status, article, row_index
end
end
end

View file

@ -23,8 +23,8 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
recipient = rcpt_to.gsub(/^\s*<\s*(.*)\s*>\s*$/, '\1')
@handlers << self.class.find_handler(recipient)
rcpt_to
rescue => error
logger.info("Can not accept mail for '#{rcpt_to}': #{error}")
rescue StandardError => e
logger.info("Can not accept mail for '#{rcpt_to}': #{e}")
raise MidiSmtpServer::Smtpd550Exception
end
@ -32,16 +32,16 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
@handlers.each do |handler|
handler.call(ctx[:message][:data])
end
rescue => error
ExceptionNotifier.notify_exception(error, data: ctx)
raise error
rescue StandardError => e
ExceptionNotifier.notify_exception(e, data: ctx)
raise e
ensure
@handlers.clear
end
def self.find_handler(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]
FoodsoftConfig.select_multifoodcoop m[:foodcoop]
@ -53,6 +53,6 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
end
end
raise "invalid format for recipient"
raise 'invalid format for recipient'
end
end

View file

@ -2,60 +2,28 @@ 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
[
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
def data
@object.order_articles.ordered.includes([:article, :article_price]).all.map do |oa|
@object.order_articles.ordered.includes(%i[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])
oa.units_to_order,
oa.article.order_number,
oa.article.name,
oa.article.unit,
oa.price.unit_quantity > 1 ? oa.price.unit_quantity : nil,
number_to_currency(oa.price.price * oa.price.unit_quantity),
number_to_currency(oa.total_price)
]
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

@ -55,7 +55,7 @@ class OrderPdf < RenderPdf
end
def group_order_article_quantity_with_tolerance(goa)
goa.tolerance > 0 ? "#{goa.quantity} + #{goa.tolerance}" : "#{goa.quantity}"
goa.tolerance > 0 ? "#{goa.quantity} + #{goa.tolerance}" : goa.quantity.to_s
end
def group_order_article_result(goa)
@ -88,7 +88,7 @@ class OrderPdf < RenderPdf
.pluck('groups.name', 'SUM(group_orders.price)', 'ordergroup_id', 'SUM(group_orders.transport)')
result.map do |item|
[item.first || stock_ordergroup_name] + item[1..-1]
[item.first || stock_ordergroup_name] + item[1..]
end
end
@ -103,7 +103,7 @@ class OrderPdf < RenderPdf
def each_ordergroup_batch(batch_size)
offset = 0
while true
loop do
go_records = ordergroups(offset, batch_size)
break unless go_records.any?
@ -113,7 +113,7 @@ class OrderPdf < RenderPdf
# get quantity for each article and ordergroup
goa_records = group_order_articles(group_ids)
.group('group_order_articles.order_article_id, group_orders.ordergroup_id')
.pluck('group_order_articles.order_article_id', 'group_orders.ordergroup_id', 'SUM(COALESCE(group_order_articles.result, group_order_articles.quantity))')
.pluck('group_order_articles.order_article_id', 'group_orders.ordergroup_id', Arel.sql('SUM(COALESCE(group_order_articles.result, group_order_articles.quantity))'))
# transform the flat list of results in a hash (with the article as key), which contains an array for all ordergroups
results = goa_records.group_by(&:first).transform_values do |value|
@ -136,7 +136,7 @@ class OrderPdf < RenderPdf
group_order_articles(ordergroup)
.includes(order_article: { article: [:supplier] })
.order('suppliers.name, articles.name')
.preload(order_article: [:article_price, :order])
.preload(order_article: %i[article_price order])
.each(&block)
end

View file

@ -8,23 +8,19 @@ class OrderTxt
def to_txt
supplier = @order.supplier
contact = FoodsoftConfig[:contact].symbolize_keys
text = I18n.t('orders.fax.heading', :name => FoodsoftConfig[:name])
text += "\n#{Supplier.human_attribute_name(:customer_number)}: #{supplier.customer_number}" unless supplier.customer_number.blank?
text = I18n.t('orders.fax.heading', name: FoodsoftConfig[:name])
text += "\n#{Supplier.human_attribute_name(:customer_number)}: #{supplier.customer_number}" if supplier.customer_number.present?
text += "\n" + I18n.t('orders.fax.delivery_day')
text += "\n\n#{supplier.name}\n#{supplier.address}\n#{Supplier.human_attribute_name(:fax)}: #{supplier.fax}\n\n"
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 += "****** " + I18n.t('orders.fax.articles') + "\n\n"
text += format("%8s %8s %s\n", I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'), I18n.t('orders.fax.name'))
text += '****** ' + I18n.t('orders.fax.articles') + "\n\n"
text += format("%8s %8s %s\n", I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'),
I18n.t('orders.fax.name'))
# now display all ordered articles
@order.order_articles.ordered.includes([:article, :article_price]).each do |oa|
@order.order_articles.ordered.includes(%i[article article_price]).each do |oa|
text += format("%8s %8d %s\n", oa.article.order_number, oa.units_to_order.to_i, oa.article.name)
end
text
end
# Helper method to test pdf via rails console: OrderTxt.new(order).save_tmp
def save_tmp
File.write("#{Rails.root}/tmp/#{self.class.to_s.underscore}.txt", to_csv.force_encoding("UTF-8"))
end
end

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