Compare commits

..

3 commits

Author SHA1 Message Date
Philipp Rothmann
eba58fc191 wip: add messages tests
relates to #52
2023-02-17 13:40:45 +01:00
Philipp Rothmann
c24904fbe3 fix(messages): migrate message bodys to richtext 2023-02-15 09:59:25 +01:00
Philipp Rothmann
8eda266340 feat(messages): add html formatting to messages
This commit allows users to use the trix editor to send
messages with basic formatting and attachements.

* add active storage
* add actiontext
* add richtext field to messages
* add imageprocessing for message attachements
* add html email layout and adjust translations to use html urls
2023-02-10 12:28:40 +01:00
75 changed files with 598 additions and 2070 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

View file

@ -53,10 +53,9 @@ RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
rm -Rf /var/lib/apt/lists/* /var/cache/apt/*
# Make relevant dirs and files writable for app user
RUN mkdir -p tmp storage && \
RUN mkdir -p tmp && \
chown nobody config/app_config.yml && \
chown nobody tmp && \
chown nobody storage
chown nobody tmp
# Run app as unprivileged user
USER nobody

View file

@ -7,6 +7,7 @@ gem 'mail', '~> 2.7.1' # bug with mail 2.8.0 https://github.com/mikel/mail/issue
gem 'sassc-rails'
gem 'less-rails'
gem 'uglifier'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', platforms: :ruby
@ -23,7 +24,7 @@ gem 'bootsnap', require: false
gem 'mysql2'
gem 'prawn'
gem 'prawn-table'
gem 'haml', '~> 5.0'
gem 'haml'
gem 'haml-rails'
gem 'kaminari'
gem 'simple_form'
@ -49,7 +50,6 @@ 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'
@ -128,5 +128,5 @@ group :test do
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
@ -242,8 +234,9 @@ GEM
rails (>= 4.0.0)
globalid (1.0.0)
activesupport (>= 5.0)
haml (5.2.2)
temple (>= 0.8.0)
haml (6.1.1)
temple (>= 0.8.2)
thor
tilt
haml-rails (2.1.0)
actionpack (>= 5.1)
@ -560,8 +553,6 @@ GEM
sqlite3 (>= 1.3.3)
table_print (1.5.7)
temple (0.9.1)
terser (1.1.13)
execjs (>= 0.3.0, < 3)
therubyracer (0.12.3)
libv8 (~> 3.16.14.15)
ref
@ -582,6 +573,8 @@ GEM
unf (~> 0.1.0)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
@ -631,7 +624,6 @@ DEPENDENCIES
exception_notification
factory_bot_rails
faker
foodsoft_article_import!
foodsoft_discourse!
foodsoft_documents!
foodsoft_links!
@ -639,7 +631,7 @@ DEPENDENCIES
foodsoft_polls!
foodsoft_wiki!
gaffe
haml (~> 5.0)
haml
haml-rails
hashie (~> 3.4.6)
i18n-js (~> 3.0.0.rc8)
@ -698,9 +690,9 @@ DEPENDENCIES
sprockets (< 4)
sqlite3 (~> 1.3.6)
table_print
terser (~> 1.1)
therubyracer
twitter-bootstrap-rails (~> 2.2.8)
uglifier
web-console
whenever

161
README.md
View file

@ -1,124 +1,65 @@
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)
[Website](https://foodsoft.local-it.org)
[Prototypefund](https://prototypefund.de/project/weiterentwicklung-von-foodsoft/)
Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling).
A food cooperative is a group of people that buy food from suppliers of their own choosing. A collective do-it-yourself supermarket. Members order their products online and collect them on a specified day. And all put in a bit of work to make that possible. Foodsoft facilitates the process.
If you're a food coop considering to use foodsoft, please have a look at the [wiki page for foodcoops](https://github.com/foodcoops/foodsoft/wiki/For-foodcoops). When you'd like to experiment with or develop foodsoft, you can read [how to set it up](https://github.com/foodcoops/foodsoft/blob/master/doc/SETUP_DEVELOPMENT.md) on your own computer.
More information about using this software and contributing can be found on the [wiki](https://github.com/foodcoops/foodsoft/wiki).
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.
Developing
----------
Foodsoft wurde ursprünglich entwickelt und betrieben von [foodcoops.net](https://foodcoops.net/)
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.
Get a foodsoft dev-environment running in the browser with Gitpod
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/foodcoops/foodsoft)
Follow these [instructions](doc/SETUP_DEVELOPMENT_GITPOD.md) to complete setup from within the Gitpod workspace.
Deploying
---------
Setup foodsoft to [run in production](doc/SETUP_PRODUCTION.md), or join an existing
[hosting platform](https://foodcoops.net/foodsoft-hosting/).
#### Zielgruppe
License
-------
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.
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.
#### Vorhaben
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).
* ✅ Technische Schuld reduzieren
* ✅ Ruby on Rails Upgrade
* ✅ Artikel Import verbessern
(Großhandelschnitstelle)
* ✅ Userexperience Verbessern
#### Was ist eine Einkaufskooperative?
![Wie funktioniert eine Einkauskooperative?](./doc/foodcoop-explained.jpg)
State of this Fork
------------------
#### Increase Test Coverage
1. integration and model tests
* [x] fork
* [x] upstream [#966](https://github.com/foodcoops/foodsoft/pull/966)
1. Controller tests
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/8_increase_test_coverage_controllers)
* [ ] upstream [#970](https://github.com/foodcoops/foodsoft/pull/970)
#### Upgrade
1. Migrate to RSwag API Tests
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/28_introduce_rswag)
* [x] upstream [#969](https://github.com/foodcoops/foodsoft/pull/969)
1. Rails v7
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/9_rails_v_7)
* [x] upstream [#979](https://github.com/foodcoops/foodsoft/pull/979)
disussion [#956](https://github.com/foodcoops/foodsoft/issues/956)
1. Javascript Importmap
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/9_rails_v_7_js_importmap)
* [x] upstream
#### Article Order Import/Export
Updating Articles from large resellers and exporting orders is now much easier!
1. adds bnn fileformat that is used from large german resellers e.g. naturkost nord
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/11_bnn_import_article_update)
[gem](https://git.local-it.org/Foodsoft/foodsoft_article_import)
* [ ] upstream
1. Import category field
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/56_add_update_of_article_category_to_file_import)
* [ ] upstream
1. Export order as a custom csv file
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/12_generate_custom_csv_file)
* [ ] upstream
1. Naturkostnord Plugin
* [ ] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/12_nkn_file_plugin)
* [ ] upstream
#### Improve User Experience
1. Richtext editor for messages. Also allows sending attachements.
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/16_html_message_templates)
* [x] upstream
1. Show the sum of all order group balances
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/47_finance_ordergroup_sums)
* [x] upstream
1. UI improvements for group order view
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/uxui_group_order)
* [ ] upstream
1. Favorites
* [ ] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/20_favourites)
* [ ] upstream
1. Show the per kilo / litre price
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/11_include_kilo_litre_price)
* [ ] upstream
#### Other
1. Fix broken plugin mechanism
* [x] [fork](https://git.local-it.org/Foodsoft/foodsoft/src/branch/downgrade-haml)
* [x] upstream
#### Screenshots
![rswag](./doc/screenshots/rswag.png)
---
![bnn upload](./doc/screenshots/bnn_upload.png)
---
![message formatting](./doc/screenshots/message_formatting.png)
---
![balance sum](./doc/screenshots/balance_sum.png)
---
![custom csv export](./doc/screenshots/custom_csv_export.png)
csv export
---
![order](./doc/screenshots/order.png)
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

@ -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

@ -46,11 +46,6 @@ class ArticlesController < ApplicationController
render :layout => false
end
def edit
@article = Article.find(params[:id])
render :action => 'new', :layout => false
end
def create
@article = Article.new(params[:article])
if @article.valid? && @article.save
@ -60,6 +55,11 @@ class ArticlesController < ApplicationController
end
end
def edit
@article = Article.find(params[:id])
render :action => 'new', :layout => false
end
# Updates one Article and highlights the line if succeded
def update
@article = Article.find(params[:id])
@ -148,12 +148,10 @@ class ArticlesController < ApplicationController
# 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')
end

View file

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

View file

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

View file

@ -49,7 +49,7 @@ class OrdersController < ApplicationController
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 +57,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]

View file

@ -53,12 +53,4 @@ module GroupOrdersHelper
return '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

@ -155,16 +155,4 @@ module OrdersHelper
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

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

@ -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|
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

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

View file

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

View file

@ -0,0 +1,22 @@
require 'roo'
class SpreadsheetFile
def self.parse(file, options = {})
filepath = file.is_a?(String) ? file : file.to_path
filename = options.delete(:filename) || filepath
fileext = File.extname(filename)
options[:csv_options] = { col_sep: ';', encoding: 'utf-8' }.merge(options[:csv_options] || {})
s = Roo::Spreadsheet.open(filepath, options.merge({ extension: fileext }))
row_index = 1
s.each do |row|
if row_index == 1
# @todo try to detect headers; for now using the index is ok
else
yield row, row_index
end
row_index += 1
end
row_index
end
end

View file

@ -143,24 +143,20 @@ class Article < ApplicationRecord
new_unit = new_article.unit
end
attribute_hash = {
:name => [self.name, new_article.name],
:manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
:origin => [self.origin, new_article.origin],
:unit => [self.unit, new_unit],
:price => [self.price.to_f.round(2), new_price.to_f.round(2)],
:tax => [self.tax, new_article.tax],
:deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
# take care of different num-objects.
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
:note => [self.note.to_s, new_article.note.to_s]
}
if options[:update_category] == true
new_article_category = new_article.article_category
attribute_hash[:article_category] = [self.article_category, new_article_category] unless new_article_category.blank?
end
Article.compare_attributes(attribute_hash)
return Article.compare_attributes(
{
:name => [self.name, new_article.name],
:manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
:origin => [self.origin, new_article.origin],
:unit => [self.unit, new_unit],
:price => [self.price.to_f.round(2), new_price.to_f.round(2)],
:tax => [self.tax, new_article.tax],
:deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
# take care of different num-objects.
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
:note => [self.note.to_s, new_article.note.to_s]
}
)
end
# Compare attributes from two different articles.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -29,7 +29,7 @@ Rails.application.configure do
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
config.assets.js_compressor = :terser
config.assets.js_compressor = :uglifier
config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -47,7 +47,6 @@ Rails.application.routes.draw do
get :receive
post :receive
get :custom_csv
get :receive_on_order_article_create
get :receive_on_order_article_update
end

View file

@ -1,4 +1,4 @@
# default seed is minimal
require Rails.root.join('db/seeds/demo-seeds.rb')
require Rails.root.join('db/seeds/minimal.seeds.rb')
# to generate new seeds, use the seed_dumper gem

View file

@ -1,147 +0,0 @@
require_relative 'seed_helper.rb'
FinancialTransactionClass.create!(:id => 1, :name => 'Standard')
FinancialTransactionClass.create!(:id => 2, :name => 'Foodsoft')
FinancialTransactionType.create!(:id => 1, :name => "Foodcoop", :financial_transaction_class_id => 1)
alice = User.create!(:id => 1, :nick => "alice", :password => "secret", :first_name => "Alice", :last_name => "Administrator", :email => "admin@foo.test", :phone => "+4421486548", :created_on => 'Wed, 15 Jan 2014 16:15:33 UTC +00:00')
bob = User.create!(:id => 2, :nick => "bob", :password => "secret", :first_name => "Bob", :last_name => "Doe", :email => "bob@doe.test", :created_on => 'Sun, 19 Jan 2014 17:38:22 UTC +00:00')
Workgroup.create!(:id => 1, :name => "Administrators", :description => "System administrators.", :account_balance => 0.0, :created_on => 'Wed, 15 Jan 2014 16:15:33 UTC +00:00', :role_admin => true, :role_suppliers => true, :role_article_meta => true, :role_finance => true, :role_orders => true, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false)
Workgroup.create!(:id => 2, :name => "Finances", :account_balance => 0.0, :created_on => 'Sun, 19 Jan 2014 17:40:03 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => true, :role_orders => false, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false)
Ordergroup.create!(:id => 5, :name => "Alice WG", :account_balance => 0.90E2, :created_on => 'Sat, 18 Jan 2014 00:38:48 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :stats => { :jobs_size => 0, :orders_sum => 1021.74 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => true)
Ordergroup.create!(:id => 8, :name => "Bob's Family", :account_balance => 0.90E2, :created_on => 'Wed, 09 Apr 2014 12:23:29 UTC +00:00', :role_admin => false, :role_suppliers => false, :role_article_meta => false, :role_finance => false, :role_orders => false, :contact_person => "John Doe", :stats => { :jobs_size => 0, :orders_sum => 0 }, :next_weekly_tasks_number => 8, :ignore_apple_restriction => false)
FinancialTransaction.create!(:ordergroup_id => 5, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', :financial_transaction_type_id => 1)
FinancialTransaction.create!(:ordergroup_id => 8, :amount => 0.90E2, :note => "Bank transfer", :user_id => 2, :created_on => 'Mon, 17 Feb 2014 16:19:34 UTC +00:00', :financial_transaction_type_id => 1)
Membership.create!(:group_id => 1, :user_id => 1)
Membership.create!(:group_id => 5, :user_id => 1)
Membership.create!(:group_id => 2, :user_id => 2)
Membership.create!(:group_id => 8, :user_id => 2)
supplier_category = SupplierCategory.create!(:id => 1, :name => "Other", :financial_transaction_class_id => 1)
chocolate_supplier = Supplier.create!(
name: "Kollektiv CHOCK!",
address: "Grabower Straße 1\n12345 Berlin",
phone: "0123456789",
email: "info@bbakery.test",
supplier_category: supplier_category
)
nkn_supplier = Supplier.create!(
name: "Naturgut Süd",
address: "Somewhere in Hamburg, maybe St. Pauli?",
phone: "0123434789",
email: "foodsoft@local-it.org",
supplier_category: supplier_category
)
chocolate_category = ArticleCategory.create!(name: "Schokolade")
obst_category = ArticleCategory.create!(name: "Obst, Gemüse, Sprossen, Pilze")
nudeln_category = ArticleCategory.create!(name: "Nudeln, Trockenfrüchte, Müsli")
reis_category = ArticleCategory.create!(name: "Getreide, Ölsaaten. Nußkerne")
Article.create!(
name: "Vollmilch-Schokolade",
supplier_id: chocolate_supplier.id,
article_category_id: chocolate_category.id,
manufacturer: "Grabower Süßwaren GmbH",
origin: "D", price: 3.0, tax: 7.0,
unit: "200g", unit_quantity: 5,
note: "bio, fairtrade, 40% Kakao, vegan",
availability: true, order_number: "1")
Article.create!(
name: "Weiße Schokolade",
supplier_id: chocolate_supplier.id,
article_category_id: chocolate_category.id,
manufacturer: "Grabower Süßwaren GmbH",
origin: "D", price: 3.49, tax: 7.0,
unit: "200g", unit_quantity: 5,
note: "bio, fairtrade, 40% Kakao, vegan",
availability: true, order_number: "2")
dark_chocolate = Article.create!(
name: "Dunkle Schokolade",
supplier_id: chocolate_supplier.id,
article_category_id: chocolate_category.id,
manufacturer: "Grabower Süßwaren GmbH",
origin: "D", price: 2.89, tax: 7.0,
unit: "200g", unit_quantity: 5,
note: "bio, fairtrade, 40% Kakao, vegan",
availability: true, order_number: "3")
Article.create!(
name: "Himbeer-Schokolade",
supplier_id: chocolate_supplier.id,
article_category_id: chocolate_category.id,
manufacturer: "Grabower Süßwaren GmbH",
origin: "D", price: 2.89, tax: 7.0,
unit: "170g", unit_quantity: 4,
note: "bio, fairtrade, 40% Kakao, vegan",
availability: true, order_number: "4")
previous_order = seed_order(supplier_id: chocolate_supplier.id, starts: 10.days.ago, ends: 7.days.ago)
GroupOrderArticle.create!(
group_order: GroupOrder.create!(order_id: previous_order.id, ordergroup_id: 8),
order_article: previous_order.order_articles.find_by(article_id: dark_chocolate.id),
quantity: 5, tolerance: 0)
previous_order.close!(alice)
seed_order(supplier_id: chocolate_supplier.id, starts: 0.days.ago, ends: 7.days.from_now)
apple = Article.create!(
name: "Äpfel Elstar",
supplier_id: nkn_supplier.id,
article_category_id: obst_category.id,
manufacturer: "Obsthof Bruno Brugger",
origin: "D", price: 3.49, tax: 7.0,
unit: "1kg", unit_quantity: 10,
note: "lecker, fruchtig, demeter",
availability: true, order_number: "5")
brokkoli = Article.create!(
name: "Brokkoli",
supplier_id: nkn_supplier.id,
article_category_id: obst_category.id,
manufacturer: "Fattoria degli Orsi",
origin: "IT", price: 2.89, tax: 7.0,
unit: "400g", unit_quantity: 6,
note: "gesund und lecker",
availability: true, order_number: "6")
tomatoes = Article.create!(
name: "Tomaten",
supplier_id: nkn_supplier.id,
article_category_id: obst_category.id,
manufacturer: "Terra di Puglia",
origin: "IT", price: 2.89, tax: 7.0,
unit: "500g", unit_quantity: 20,
note: "pomodori italiani, demeter",
availability: true, order_number: "7")
rice = Article.create!(
name: "Reis",
supplier_id: nkn_supplier.id,
article_category_id: reis_category.id,
manufacturer: "Finck",
origin: "D", price: 3.29, tax: 7.0,
unit: "3kg", unit_quantity: 10,
note: "Reis im Vorratssack, demeter",
availability: true, order_number: "8")
spaghetti = Article.create!(
name: "Spaghetti",
supplier_id: nkn_supplier.id,
article_category_id: nudeln_category.id,
manufacturer: "Pastificio Zanellini spa",
origin: "D", price: 2.89, tax: 7.0,
unit: "500g", unit_quantity: 4,
note: "100% italienisches Hartweizengrieß",
availability: true, order_number: "9")

View file

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

View file

@ -1,65 +0,0 @@
TYPE=foodsoft
DOMAIN=order.example.org
#EXTRA_DOMAINS=', `www.order.example.com`'
LETS_ENCRYPT_ENV=production
COMPOSE_FILE="compose.yml"
# app settings
FOODCOOP_MULTI_INSTALL=true # Best for now, see https://github.com/foodcoops/foodsoft/pull/841
FOODCOOP_NAME=example
FOODCOOP_CITY=XXX
FOODCOOP_COUNTRY=XXX
FOODCOOP_EMAIL=info@example.org
FOODCOOP_PHONE=XXX
FOODCOOP_STREET=XXX
FOODCOOP_ZIP_CODE=XXX
FOODCOOP_HOMEPAGE=https://order.example.org
FOODCOOP_HELP_URL=https://order.example.org
FOODCOOP_TIME_ZONE=Amsterdam
FOODCOOP_USE_NICK=true
FOODCOOP_LANGUAGE=en
FOODCOOP_FOOTER='<a href="https://example.org/">example</a> hosted by <a href="https://yourhoster.org">Your Tech Co-op</a>.'
USE_APPLE_POINTS=false
STOP_ORDERING_UNDER=75
MINIMUM_BALANCE=0
# database settings
MYSQL_DB=foodsoft
MYSQL_HOST=db
MYSQL_PORT=3306
MYSQL_USER=foodsoft
# shared supplier list settings
# COMPOSE_FILE="$COMPOSE_FILE:compose.sharedlists.yml"
# ENABLE_SHARED_LISTS=0
# SHARED_LISTS_DB_TYPE=mysql2
# SHARED_LISTS_HOST=order.otherfoodcoop.org
# SHARED_LISTS_DB_NAME=sharedlists
# SHARED_LISTS_USER=example
# Group order invoices generation pull request
# https://github.com/foodcoops/foodsoft/pull/907
# COMPOSE_FILE="$COMPOSE_FILE:compose.groupOrderInvoice.yml"
# outgoing mail settings
EMAIL_SENDER=noreply@example.org
EMAIL_ERROR=systems@example.org
SMTP_ADDRESS=mail.example.com
SMTP_AUTHENTICATION=plain
SMTP_DOMAIN=mail.example.com
SMTP_ENABLE_STARTTLS_AUTO=true
SMTP_PORT=587
SMTP_USER_NAME=foodsoft
# incoming mail settings
EMAIL_REPLY_DOMAIN=example.org
SMTP_SERVER_HOST=0.0.0.0
SMTP_SERVER_PORT=2525
# secret versions
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 # length=30

View file

@ -1,168 +0,0 @@
# {{ env "DOMAIN" }} configuration
default: &defaults
# If you wanna serve more than one foodcoop with one installation
# Don't forget to setup databases for each foodcoop. See also MULTI_COOP_INSTALL
multi_coop_install: {{ env "FOODCOOP_MULTI_INSTALL" }}
# If multi_coop_install you have to use a coop name, which you you wanna be selected by default
default_scope: "{{ env "FOODCOOP_NAME" }}"
# name of this foodcoop
name: "{{ env "FOODCOOP_NAME" }}"
# foodcoop contact information (used for FAX messages)
contact:
street: "{{ env "FOODCOOP_STREET" }}"
zip_code: "{{ env "FOODCOOP_ZIP_CODE" }}"
city: "{{ env "FOODCOOP_CITY" }}"
country: "{{ env "FOODCOOP_COUNTRY" }}"
email: "{{ env "FOODCOOP_EMAIL" }}"
phone: "{{ env "FOODCOOP_PHONE" }}"
# Homepage
homepage: "{{ env "FOODCOOP_HOMEPAGE" }}"
# foodsoft documentation URL
help_url: "{{ env "FOODCOOP_HELP_URL" }}"
# documentation URL for the apples&pears work system
applepear_url: https://github.com/foodcoops/foodsoft/wiki/%C3%84pfel-u.-Birnen
# custom foodsoft software URL (used in footer)
foodsoft_url: https://foodcoops.github.io
# Default language
default_locale: {{ env "FOODCOOP_LANGUAGE" }}
# By default, foodsoft takes the language from the webbrowser/operating system.
# In case you really want foodsoft in a certain language by default, set this to true.
# When members are logged in, the language from their profile settings is still used.
ignore_browser_locale: false
# Default timezone, e.g. UTC, Amsterdam, Berlin, etc.
time_zone: "{{ env "FOODCOOP_TIME_ZONE" }}"
# Currency symbol, and whether to add a whitespace after the unit.
currency_unit: €
#currency_space: true
# price markup in percent
price_markup: 2.0
# default vat percentage for new articles
tax_default: 7.0
# tolerance order option: If set to false, article tolerance values do not count
# for total article price as long as the order is not finished.
tolerance_is_costly: false
# Ordergroups, which have less than 75 apples should not be allowed to make new orders
# Comment out this option to activate this restriction
stop_ordering_under: {{ env "STOP_ORDERING_UNDER" }}
# Comment out to completely hide apple points (be sure to comment stop_ordering_under)
use_apple_points: {{ env "USE_APPLE_POINTS" }}
# ordergroups can only order when their balance is higher than or equal to this
# not fully enforced right now, since the check is only client-side
minimum_balance: {{ env "MINIMUM_BALANCE" }}
# how many days there are between two periodic tasks
#tasks_period_days: 7
# how many days upfront periodic tasks are created
#tasks_upfront_days: 49
# default order schedule, used to provide initial dates for new orders
# (recurring dates in ical format; no spaces!)
#order_schedule:
# ends:
# recurr: FREQ=WEEKLY;INTERVAL=2;BYDAY=MO
# time: '9:00'
# # reference point, this is generally the first pickup day; empty is often ok
# #initial:
# When use_nick is enabled, there will be a nickname field in the user form,
# and the option to show a nickname instead of full name to foodcoop members.
# Members of a user's groups and administrators can still see full names.
use_nick: {{ env "FOODCOOP_USE_NICK" }}
# Most plugins can be enabled/disabled here as well. Messages and wiki are enabled
# by default and need to be set to false to disable. Most other plugins needs to
# be enabled before they do anything.
use_wiki: true
use_messages: true
use_documents: true
use_polls: true
# Base font size for generated PDF documents
#pdf_font_size: 12
# Page size for generated PDF documents
#pdf_page_size: A4
# Some documents (like group and article PDFs) can include page breaks
# after each sublist.
#pdf_add_page_breaks: true
# Alternatively, this can be set for each document.
#pdf_add_page_breaks:
# order_by_groups: true
# order_by_articles: true
# Page footer (html allowed). Default is a Foodsoft footer. Set to `blank` for no footer.
page_footer: {{ env "FOODCOOP_FOOTER" }}
# Custom CSS for the foodcoop
#custom_css: 'body { background-color: #fcffba; }'
# Uncomment to add tracking code for web statistics, e.g. for Piwik. (Added to bottom of page)
#webstats_tracking_code: |
# <!-- Piwik -->
# ......
# email address to be used as sender
email_sender: "{{ env "EMAIL_SENDER" }}"
# email address to be used as from
email_from: "{{ env "EMAIL_SENDER" }}"
# domain to be used for reply emails
reply_email_domain: {{ env "EMAIL_REPLY_DOMAIN" }}
# If your foodcoop uses a mailing list instead of internal messaging system
#mailing_list: list@example.org
#mailing_list_subscribe: list-subscribe@example.org
# Config for the exception_notification plugin
notification:
error_recipients:
- "{{ env "EMAIL_ERROR" }}"
sender_address: "\"Foodsoft error\" <{{ env "EMAIL_SENDER" }}>"
email_prefix: "[foodsoft] "
# http config for this host to generate links in emails (uses environment config when not set)
protocol: https
host: "{{ env "DOMAIN" }}"
#port: 3000
{{ if eq (env "ENABLE_SHARED_LISTS") "1" }}
# Access to sharedlists, the external article-database.
# This allows a foodcoop to subscribe to a selection of a supplier's full assortment,
# and makes it possible to share data with several foodcoops. Using this requires installing
# an additional application with a separate database.
shared_lists:
adapter: "{{ env "SHARED_LISTS_DB_TYPE" }}"
host: "{{ env "SHARED_LISTS_HOST" }}"
database: "{{ env "SHARED_LISTS_DB_NAME" }}"
username: "{{ env "SHARED_LISTS_USER" }}"
password: "{{ secret "shared_lists_db_password" }}"
{{ end }}
# don't remove this, required to run the app
production:
<<: *defaults
{{ env "FOODCOOP_NAME" }}:
<<: *defaults

View file

@ -1,190 +0,0 @@
---
version: "3.8"
x-env: &env
CERTBOT_DISABLED: 1
DOMAIN:
EMAIL_ERROR:
EMAIL_REPLY_DOMAIN:
EMAIL_SENDER:
FOODCOOP_CITY:
FOODCOOP_COUNTRY:
FOODCOOP_EMAIL:
FOODCOOP_FOOTER:
FOODCOOP_HELP_URL:
FOODCOOP_HOMEPAGE:
FOODCOOP_MULTI_INSTALL:
FOODCOOP_NAME:
FOODCOOP_PHONE:
FOODCOOP_STREET:
FOODCOOP_TIME_ZONE:
FOODCOOP_ZIP_CODE:
FOODCOOP_USE_NICK:
FOODCOOP_LANGUAGE:
LOG_LEVEL:
MINIMUM_BALANCE:
MYSQL_DB:
MYSQL_HOST:
MYSQL_PORT:
MYSQL_USER:
QUEUE: foodsoft_notifier
REDIS_URL: redis://cache:6379
SECRET_KEY_BASE_FILE: /run/secrets/secret_key_base
SMTP_ADDRESS:
SMTP_AUTHENTICATION:
SMTP_DOMAIN:
SMTP_ENABLE_STARTTLS_AUTO:
SMTP_PASSWORD_FILE: /run/secrets/smtp_password
SMTP_PORT:
SMTP_USER_NAME:
STOP_ORDERING_UNDER:
USE_APPLE_POINTS:
x-configs: &configs
- source: app_config
target: /usr/src/app/config/app_config.yml
- source: db_config
target: /usr/src/app/config/database.yml
- source: entrypoint
target: /usr/src/app/docker-entrypoint.sh
mode: 0555
x-secrets: &secrets
- db_password
- secret_key_base
- smtp_password
services:
app:
image: ${IMAGE}
networks:
- internal
- proxy
secrets: *secrets
configs: *configs
entrypoint: &entrypoint /usr/src/app/docker-entrypoint.sh
environment:
<<: *env
FOODSOFT_SERVICE: app
RAILS_SERVE_STATIC_FILES: 'true'
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 15s
timeout: 10s
retries: 10
start_period: 1m
deploy:
update_config:
failure_action: rollback
order: start-first
labels:
- "traefik.enable=true"
- "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
- "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=3000"
- "coop-cloud.${STACK_NAME}.version=1.0.0+4.7.1"
cron:
image: ${IMAGE}
secrets: *secrets
configs: *configs
entrypoint: *entrypoint
environment:
<<: *env
FOODSOFT_SERVICE: cron
networks:
- internal
worker:
image: ${IMAGE}
secrets: *secrets
configs: *configs
entrypoint: *entrypoint
environment:
<<: *env
FOODSOFT_SERVICE: worker
networks:
- internal
smtp:
image: ${IMAGE}
configs: *configs
entrypoint: *entrypoint
secrets: *secrets
environment:
<<: *env
FOODSOFT_SERVICE: smtp
SMTP_SERVER_HOST:
SMTP_SERVER_PORT:
networks:
- proxy
- internal
deploy:
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.foodsoft-smtp.rule=HostSNI(`*`)"
- "traefik.tcp.routers.foodsoft-smtp.entrypoints=foodsoft-smtp"
- "traefik.tcp.services.foodsoft-smtp.loadbalancer.server.port=${SMTP_SERVER_PORT}"
db:
image: "mariadb:10.6"
command: "mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_520_ci"
environment:
MYSQL_USER: ${MYSQL_USER}
MYSQL_DATABASE: ${MYSQL_DB}
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets:
- db_password
- db_root_password
volumes:
- "db:/var/lib/mysql"
networks:
- internal
deploy:
labels:
backupbot.backup: "true"
backupbot.backup.pre-hook: 'mkdir -p /tmp/backup/ && mysqldump --single-transaction -u root -p"$$(cat /run/secrets/db_root_password)" $${MYSQL_DATABASE} > /tmp/backup/backup.sql'
backupbot.backup.post-hook: "rm -rf /tmp/backup"
backupbot.backup.path: "/tmp/backup/"
cache:
image: "redis:6"
networks:
- internal
networks:
internal:
proxy:
external: true
volumes:
db:
configs:
app_config:
name: ${STACK_NAME}_app_config_${APP_CONFIG_VERSION}
file: app_config.yml.tmpl
template_driver: golang
db_config:
name: ${STACK_NAME}_db_config_${DB_CONFIG_VERSION}
file: database.yml.tmpl
template_driver: golang
entrypoint:
name: ${STACK_NAME}_entrypoint_${ENTRYPOINT_VERSION}
file: entrypoint.sh.tmpl
template_driver: golang
secrets:
db_password:
name: ${STACK_NAME}_db_password_${SECRET_DB_PASSWORD_VERSION}
external: true
db_root_password:
name: ${STACK_NAME}_db_root_password_${SECRET_DB_ROOT_PASSWORD_VERSION}
external: true
smtp_password:
name: ${STACK_NAME}_smtp_password_${SECRET_SMTP_PASSWORD_VERSION}
external: true
secret_key_base:
name: ${STACK_NAME}_secret_key_base_${SECRET_SECRET_KEY_BASE_VERSION}
external: true

View file

@ -1,9 +0,0 @@
production:
adapter: "mysql2"
encoding: "utf8mb4"
collation: "utf8mb4_unicode_520_ci"
username: "{{ env "MYSQL_USER" }}"
password: "{{ secret "db_password" }}"
database: "{{ env "MYSQL_DB" }}"
host: "{{ env "MYSQL_HOST" }}"
port: "{{ env "MYSQL_PORT" }}"

View file

@ -1,44 +0,0 @@
#!/bin/bash
set -eu
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
file_env "SECRET_KEY_BASE"
file_env "SMTP_PASSWORD"
echo "------------------------------------------------------------------------------"
echo "Running entrypoint commands against '$FOODSOFT_SERVICE' service"
echo "------------------------------------------------------------------------------"
if [ "$FOODSOFT_SERVICE" == "app" ]; then
bundle exec rake db:setup || true
bundle exec rake db:migrate || true
./proc-start web
elif [ "$FOODSOFT_SERVICE" == "cron" ]; then
./proc-start cron
elif [ "$FOODSOFT_SERVICE" == "worker" ]; then
./proc-start worker
elif [ "$FOODSOFT_SERVICE" == "smtp" ]; then
./proc-start mail
fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View file

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

View file

@ -0,0 +1,12 @@
require 'factory_bot'
FactoryBot.define do
factory :message do
sender {create :user}
subject { Faker::Lorem.words(number: 7) }
body { Faker::Lorem.words(number: 42)}
created_at {Time.now}
private { false }
send_method { 'recipients' }
end
end

View file

@ -0,0 +1,50 @@
require_relative '../../../../spec/spec_helper'
feature 'messages' do
let(:sending_user) { create :user }
let(:receiving_user) { create :user }
let(:message){ create :message }
before {
login sending_user
message.add_recipients([receiving_user])
}
describe 'index' do
before { visit messages_path }
it 'shows subject of message' do
expect(page).to have_content 'New message'
expect(page).to have_content message.subject
end
it 'can open message when clicked on subject' do
click_link(message.subject)
expect(page).to have_content message.body.to_plain_text
end
end
describe 'create new', js: true do
before {
login sending_user
visit new_message_path
}
after { page.save_screenshot('screenshot.png') }
# TODO: fix trix-editor stuff
# https://stackoverflow.com/questions/45962746/rails-capybara-populate-hidden-field-from-trix-editor
it 'shows input elements' do
choose 'Send to all members'
fill_in 'message_subject', with: 'hello friend'
# print page.body()
expect(page).to have_selector('trix-editor')
foo = find('#message_body_trix_input_message', visible: false)
puts foo.inspect
editor = find('trix-editor')
editor.click.set('foo bar 123')
# find('.message_body', visible: false).set("some value here")
click_button 'send message'
expect(page).to have_current_path(messages_path)
expect(page).to have_selector '.alert-success'
expect(page).to have_content 'hello friend'
end
end
end

View file

@ -0,0 +1,33 @@
require_relative '../../../../spec/spec_helper'
describe Message do
let(:sender) { create :user }
let(:receiver) { create :user }
let(:another) { create :user }
let(:message) { create :message,
sender: sender,
recipients: [receiver],
subject: 'hello friend',
body: 'hi how are you?',
private: false,
send_method: 'recipients'
}
it 'can be created' do
expect(message.recipients).to eq([receiver])
expect(message.subject).to eq('hello friend')
expect(message.body.to_plain_text).to eq('hi how are you?')
puts message.message_recipients.inspect
end
it 'can be private' do
expect(message.is_readable_for?(receiver)).to be_truthy
expect(message.is_readable_for?(sender)).to be_truthy
expect(message.is_readable_for?(another)).to be_truthy
message.update_attribute :private, true
expect(message.is_readable_for?(another)).to be_falsey
end
end

View file

@ -187,8 +187,8 @@ describe ArticlesController, type: :controller do
describe '#parse_upload' do
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/upload_test.csv'), original_filename: 'upload_test.csv') }
it 'updates articles from spreadsheet' do
post_with_supplier :parse_upload, params: { articles: { file: file, outlist_absent: '1', convert_units: '1', type: 'foodsoft' } }
it 'updates particles from spreadsheet' do
post_with_supplier :parse_upload, params: { articles: { file: file, outlist_absent: '1', convert_units: '1' } }
expect(response).to have_http_status(:success)
end

View file

@ -1,50 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe Finance::OrdergroupsController do
let(:user) { create(:user, :role_finance, :role_orders, :ordergroup) }
let(:fin_trans_type1) { create(:financial_transaction_type) }
let(:fin_trans_type2) { create(:financial_transaction_type) }
let(:fin_trans1) do
create(:financial_transaction,
user: user,
ordergroup: user.ordergroup,
financial_transaction_type: fin_trans_type1)
end
let(:fin_trans2) do
create(:financial_transaction,
user: user,
ordergroup: user.ordergroup,
financial_transaction_type: fin_trans_type1)
end
let(:fin_trans3) do
create(:financial_transaction,
user: user,
ordergroup: user.ordergroup,
financial_transaction_type: fin_trans_type2)
end
before { login user }
describe 'GET index' do
before do
fin_trans1
fin_trans2
fin_trans3
end
it 'renders index page' do
get_with_defaults :index
expect(response).to have_http_status(:success)
expect(response).to render_template('finance/ordergroups/index')
end
it 'calculates total balance sums correctly' do
get_with_defaults :index
expect(assigns(:total_balances).size).to eq(2)
expect(assigns(:total_balances)[fin_trans_type1.id]).to eq(fin_trans1.amount + fin_trans2.amount)
expect(assigns(:total_balances)[fin_trans_type2.id]).to eq(fin_trans3.amount)
end
end
end

View file

@ -1,5 +0,0 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
29932;;;;4280001958081;4280001958203;Walnoten (ongeroosterd);bio;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;1;1 kg;1;N;930190;99260;;1,41;;;;1;;;4,49;2,34;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
28391;;;;4280001958081;4280001958203;Pijnboompitten;dem;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;1;100 g;10;N;930190;99260;;1,41;;;;1;;;5,56;2.89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
1829;;;;4280001958081;4280001958203;Appelsap (verpakt);;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;4x250 ml;10;4x250 ml;10;N;930190;99260;;3,21;;;;1;;;4,49;2.89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;ml;28,571;;
177813;;;;4280001958081;4280001958203;Tomaten;bio;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;1;500 g;20;N;930190;99260;;1,20;;;;1;;;4,49;2.89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;g;28,571;;

View file

@ -1,2 +0,0 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
1;;;;4280001958081;4280001958203;Tomatoes;organic;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;20;500 g;1;N;930190;99260;;1,41;;;;1;;;4,49;1,20;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;g;28,571;;

View file

@ -1,273 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmlproduct>
<leverancierkop>
<leveranciersnummer>1039</leveranciersnummer>
<versienummer>1.08</versienummer>
<naam>Estafette Associatie C.V.</naam>
<plaats>Geldermalsen</plaats>
</leverancierkop>
<product>
<eancode>8719325207668</eancode>
<omschrijving>Walnoten (ongeroosterd)</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>1</inhoud>
<eenheid>kg</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk>Het warme woud</merk>
<kwaliteit>bio</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>NL</herkomst>
<herkomstregio></herkomstregio>
<btw>6</btw>
<cblcode>1017515</cblcode>
<bestelnummer>29932</bestelnummer>
<sve>10</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>2.34</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
<product>
<eancode>8719325207668</eancode>
<omschrijving>Pijnboompitten</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>100</inhoud>
<eenheid>g</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk>NELEMAN</merk>
<kwaliteit>dem</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>TR</herkomst>
<herkomstregio></herkomstregio>
<btw>6</btw>
<cblcode>1017515</cblcode>
<bestelnummer>28391</bestelnummer>
<sve>10</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>5.56</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
<product>
<eancode>8719325207668</eancode>
<omschrijving>Appelsap (verpakt)</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>4x250</inhoud>
<eenheid>ml</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0.4</statiegeld>
<merk>Appelgaarde</merk>
<kwaliteit></kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>DE</herkomst>
<herkomstregio></herkomstregio>
<btw>6</btw>
<cblcode>1017515</cblcode>
<bestelnummer>1829</bestelnummer>
<sve>10</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>3.21</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
<product>
<eancode>8719325207668</eancode>
<omschrijving>Tomaten</omschrijving>
<kassaomschrijving>Nucli rose</kassaomschrijving>
<plucode></plucode>
<weegschaalartikel>0</weegschaalartikel>
<wichtartikel>0</wichtartikel>
<pluartikel>0</pluartikel>
<inhoud>500</inhoud>
<eenheid>g</eenheid>
<verpakkingce>Stuk</verpakkingce>
<statiegeld>0</statiegeld>
<merk>De röde hof</merk>
<kwaliteit>bio</kwaliteit>
<keurmerkbio></keurmerkbio>
<keurmerkoverig></keurmerkoverig>
<herkomst>DE</herkomst>
<herkomstregio></herkomstregio>
<btw>6</btw>
<cblcode>1017515</cblcode>
<bestelnummer>177813</bestelnummer>
<sve>20</sve>
<status>Actief</status>
<ingredienten>druiven*</ingredienten>
<d204>0</d204>
<d209>0</d209>
<d210>2</d210>
<d212>2</d212>
<d213>0</d213>
<d214>0</d214>
<d234>0</d234>
<d215>2</d215>
<d239>2</d239>
<d216>0</d216>
<d217>2</d217>
<d220>0</d220>
<d221>2</d221>
<d223>0</d223>
<d236>2</d236>
<d235>2</d235>
<d238>2</d238>
<d225>2</d225>
<d228>1</d228>
<d232>0</d232>
<d237>2</d237>
<d240>0</d240>
<d241>2</d241>
<d242>2</d242>
<lengteverpakking></lengteverpakking>
<breedteverpakking></breedteverpakking>
<hoogteverpakking></hoogteverpakking>
<proefdiervrij>0</proefdiervrij>
<vegetarisch>0</vegetarisch>
<veganistisch>0</veganistisch>
<rauwemelk>0</rauwemelk>
<bewaartemperatuur>1</bewaartemperatuur>
<gebruikstips></gebruikstips>
<soortleverancier>2</soortleverancier>
<geriefartikel>0</geriefartikel>
<prijs>
<prijslijn>adviesprijs</prijslijn>
<ingangsdatum>2022-08-18</ingangsdatum>
<inkoopprijs>1.2</inkoopprijs>
<consumentenprijs>7.95</consumentenprijs>
</prijs>
</product>
</xmlproduct>

View file

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

View file

@ -1,17 +1,14 @@
require_relative '../spec_helper'
feature ArticlesController do
let(:user) { create(:user, groups: [create(:workgroup, role_article_meta: true)]) }
let(:supplier) { create(:supplier) }
let!(:article_category) { create(:article_category) }
let(:user) { create :user, groups: [create(:workgroup, role_article_meta: true)] }
let(:supplier) { create :supplier }
let!(:article_category) { create :article_category }
before { login user }
describe ':index', js: true do
before {
login user
visit supplier_articles_path(supplier_id: supplier.id)
}
before { visit supplier_articles_path(supplier_id: supplier.id) }
it 'can visit supplier articles path' do
expect(page).to have_content(supplier.name)
@ -21,7 +18,7 @@ feature ArticlesController do
it 'can create a new article' do
click_on I18n.t('articles.index.new')
expect(page).to have_selector('form#new_article')
article = build(:article, supplier: supplier, article_category: article_category)
article = build :article, supplier: supplier, article_category: article_category
within('#new_article') do
fill_in 'article_name', :with => article.name
fill_in 'article_unit', :with => article.unit
@ -52,7 +49,6 @@ feature ArticlesController do
let(:file) { Rails.root.join(test_file) }
it do
find("#articles_type option[value='foodsoft']").select_option
find('input[type="submit"]').click
expect(find("tr:nth-child(1) #new_articles__note").value).to eq "bio ◎"
expect(find("tr:nth-child(2) #new_articles__name").value).to eq "Pijnboompitten"
@ -68,124 +64,56 @@ feature ArticlesController do
end
end
Dir.glob('spec/fixtures/bnn_file_01.*') do |test_file|
describe "can import articles from #{test_file}" do
let(:file) { Rails.root.join(test_file) }
describe "can update existing article" do
let!(:article) { create :article, supplier: supplier, name: 'Foobar', order_number: 1, unit: '250 g' }
it do
find("#articles_type option[value='bnn']").select_option
find('input[type="submit"]').click
expect(find("tr:nth-child(1) #new_articles__note").value).to eq "bio"
expect(find("tr:nth-child(1) #new_articles__name").value).to eq "Walnoten (ongeroosterd)"
# set article category
4.times do |i|
all("tr:nth-child(#{i + 1}) select > option")[1].select_option
end
find('input[type="submit"]').click
expect(page).to have_content("Pijnboompitten")
expect(supplier.articles.count).to eq 4
end
it do
find('input[type="submit"]').click
expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes'
find('input[type="submit"]').click
article.reload
expect(article.name).to eq 'Tomatoes'
expect([article.unit, article.unit_quantity, article.price]).to eq ['500 g', 20, 1.2]
end
end
end
describe "updates" do
file_paths = ['spec/fixtures/foodsoft_file_02.csv', 'spec/fixtures/bnn_file_02.bnn', 'spec/fixtures/odin_file_02.xml']
let(:filename) { 'foodsoft_file_02.csv' }
let(:file) { Rails.root.join("spec/fixtures/#{filename}") }
let(:val) { 'foodsoft' }
let(:type) { %w[foodsoft bnn odin] }
describe "handles missing data" do
it do
find('input[type="submit"]').click # to overview
find('input[type="submit"]').click # missing category, re-show form
expect(find('tr.alert')).to be_present
expect(supplier.articles.count).to eq 0
before do
visit upload_supplier_articles_path(supplier_id: supplier.id)
attach_file 'articles_file', file
find("#articles_type option[value='#{val}']").select_option
all("tr select > option")[1].select_option
find('input[type="submit"]').click # now it should succeed
expect(supplier.articles.count).to eq 1
end
end
file_paths.each_with_index do |test_file, index|
describe "updates article for #{test_file}" do
let(:article) { create(:article, supplier: supplier, name: 'Foobar', order_number: 1, unit: '250 g') }
let(:file) { Rails.root.join(test_file) }
let(:val) { type[index] }
describe "can remove an existing article" do
let!(:article) { create :article, supplier: supplier, name: 'Foobar', order_number: 99999 }
it do
article.reload
find('input[type="submit"]').click
expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes'
find('input[type="submit"]').click
article.reload
expect(article.name).to eq 'Tomatoes'
if type[index] == "odin"
expect([article.unit, article.unit_quantity, article.price]).to eq ['500gr', 20, 1.20]
else
expect([article.unit, article.unit_quantity, article.price]).to eq ['500 g', 20, 1.20]
end
end
it do
check('articles_outlist_absent')
find('input[type="submit"]').click
expect(find("#outlisted_articles_#{article.id}", visible: :all)).to be_present
it "handles missing data" do
find('input[type="submit"]').click # to overview
find('input[type="submit"]').click # missing category, re-show form
expect(find('tr.alert')).to be_present
expect(supplier.articles.count).to eq 0
all("tr select > option")[1].select_option
find('input[type="submit"]').click # now it should succeed
expect(supplier.articles.count).to eq 1
end
all("tr select > option")[1].select_option
find('input[type="submit"]').click
expect(article.reload.deleted?).to be true
end
end
describe "takes over category from file" do
it do
find(:css, '#articles_update_category[value="1"]').set(true) # check take over category from file
expect(ArticleCategory.count).to eq 1 # new Category vegetables should be created from file
find('input[type="submit"]').click # upload file
find('input[type="submit"]').click # submit changes
expect(ArticleCategory.count).to eq 2 # it is
expect(supplier.articles.count).to eq 1
expect(supplier.articles.first.article_category.name).to eq "Vegetables"
end
end
describe "can convert units when updating" do
let!(:article) { create :article, supplier: supplier, order_number: 1, unit: '250 g' }
describe "overwrites article_category from file" do
let!(:article_category) { create(:article_category, name: "Fruit") }
let(:article) { create(:article, supplier: supplier, name: 'Tomatoes', order_number: 1, unit: '250 g', article_category: article_category) }
it do
find(:css, '#articles_update_category[value="1"]').set(true) # check take over category from file
find('input[type="submit"]').click #upload file
find('input[type="submit"]').click #submit changes
expect(supplier.articles.count).to eq 1
expect(supplier.articles.first.article_category.name).to eq "Vegetables"
end
end
describe "can remove an existing article" do
let!(:article) { create(:article, supplier: supplier, name: 'Foobar', order_number: 99999) }
it do
check('articles_outlist_absent')
find('input[type="submit"]').click
expect(find("#outlisted_articles_#{article.id}", visible: :all)).to be_present
all("tr select > option")[1].select_option
find('input[type="submit"]').click
expect(article.reload.deleted?).to be true
end
end
describe "can convert units when updating" do
let!(:article) { create(:article, supplier: supplier, order_number: 1, unit: '250 g') }
it do
check('articles_convert_units')
find('input[type="submit"]').click
expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes'
find('input[type="submit"]').click
article.reload
expect([article.unit, article.unit_quantity, article.price]).to eq ['250 g', 40, 0.6]
end
it do
check('articles_convert_units')
find('input[type="submit"]').click
expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes'
find('input[type="submit"]').click
article.reload
expect([article.unit, article.unit_quantity, article.price]).to eq ['250 g', 40, 0.6]
end
end
end

View file

@ -1,22 +0,0 @@
require_relative '../spec_helper'
describe QuantityUnit do
it "parses a string correctly" do
qu = QuantityUnit.parse("1.5 k g"); expect([qu.quantity, qu.unit]).to eq([1.5, "kg"])
qu = QuantityUnit.parse(" 1,5 kg"); expect([qu.quantity, qu.unit]).to eq([1.5, "kg"])
qu = QuantityUnit.parse("1500 g"); expect([qu.quantity, qu.unit]).to eq([1500, "g"])
qu = QuantityUnit.parse("1.5L "); expect([qu.quantity, qu.unit]).to eq([1.5, "l"])
qu = QuantityUnit.parse("2400mL"); expect([qu.quantity, qu.unit]).to eq([2400, "ml"])
end
it "scales prices correctly" do
qu = QuantityUnit.new(1.5, "kg")
expect(qu.scale_price_to_base_unit(12.34)).to eq([8.23, "kg"])
qu = QuantityUnit.new(1500, "g")
expect(qu.scale_price_to_base_unit(12.34)).to eq([8.23, "kg"])
qu = QuantityUnit.new(1.5, "l")
expect(qu.scale_price_to_base_unit(12.34)).to eq([8.23, "L"])
qu = QuantityUnit.new(2400, "ml")
expect(qu.scale_price_to_base_unit(12.34)).to eq([5.14, "L"])
end
end

View file

@ -11,7 +11,7 @@ describe Supplier do
options = { filename: 'foodsoft_file_01.csv' }
options[:outlist_absent] = true
options[:convert_units] = true
updated_article_pairs, outlisted_articles, new_articles = supplier.sync_from_file(Rails.root.join('spec/fixtures/foodsoft_file_01.csv'), "foodsoft", options)
updated_article_pairs, outlisted_articles, new_articles = supplier.sync_from_file(Rails.root.join('spec/fixtures/foodsoft_file_01.csv'), options)
expect(new_articles.length).to be > 0
expect(updated_article_pairs.first[1][:name]).to eq 'Tomaten'
expect(outlisted_articles.first).to eq article2

View file

@ -12,7 +12,7 @@ Capybara.javascript_driver = :apparition
# TODO: Remove temporary fix to ignore JavaScript errors
Capybara.register_driver :apparition do |app|
Capybara::Apparition::Driver.new(app, { js_errors: false })
Capybara::Apparition::Driver.new(app, { js_errors: false, window_size: [1920, 1080] })
end
# Requires supporting ruby files with custom matchers and macros, etc,
@ -78,3 +78,6 @@ class ActionDispatch::Routing::RouteSet
{ foodcoop: FoodsoftConfig.scope }.merge(options)
end
end
FactoryBot.definition_file_paths = %w(plugins/messages/spec/factories)
FactoryBot.find_definitions