Compare commits

..

26 commits

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

things shouldn't just disapear

order article disabled button should be gray

roup order swap plus and minus buttons

because it's more naturally intuitive like this

group order make order details collapse

group order pull search to the right

group order make 'current orders' more obvious

fix switch menu

rework group order edit form

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

wip: rework group order details

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

rework group order show view

dashboard make show edit current order action more precise

group order package indication uses  more color-blind friendly color

group order fix dancing quantity buttons

group order switch menu use show view

group order show window with some explanations

group order edit title more clear

grou order edit show less infos

group order switch view next iteration

grou order index narrower tables

move order details to show again

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

add rute and controller method to orders controller

add custom csv to download dropdown

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

use opts in .parse

adapt specs to include file format

add specs for odin, bnn, foodsoft files

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

remove depr foodsoftfile.rb and spreadsheet.rb

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

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

fix: .drone docker rails version

add .drone caching

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

add translations

adapt test

fix bug
2023-02-24 18:54:32 +01:00
44 changed files with 718 additions and 398 deletions

View file

@ -79,7 +79,7 @@ steps:
- name: deployment - name: deployment
image: git.local-it.org/philipp/stack-ssh-deply:latest image: git.local-it.org/philipp/stack-ssh-deply:latest
settings: settings:
stack: "foodsoft_${DRONE_COMMIT:0:8}" stack: "foodsoft_${DRONE_BRANCH}"
compose: "deployment/compose.yml" compose: "deployment/compose.yml"
deploy_key: deploy_key:
from_secret: drone_deploy_key from_secret: drone_deploy_key
@ -96,23 +96,23 @@ steps:
- proxy - proxy
environment: environment:
IMAGE: git.local-it.org/foodsoft/foodsoft:${DRONE_COMMIT:0:8} IMAGE: git.local-it.org/foodsoft/foodsoft:${DRONE_COMMIT:0:8}
STACK_NAME: "foodsoft_${DRONE_COMMIT:0:8}" STACK_NAME: "foodsoft_${DRONE_BRANCH}"
DOMAIN: "${DRONE_COMMIT:0:8}.foodsoft.dev.local-it.cloud" DOMAIN: "foodsoft.dev.local-it.cloud"
LETS_ENCRYPT_ENV: production LETS_ENCRYPT_ENV: production
FOODCOOP_MULTI_INSTALL: true FOODCOOP_MULTI_INSTALL: true
FOODCOOP_NAME: example FOODCOOP_NAME: Einkaufskooperative Foobar
FOODCOOP_CITY: XXX FOODCOOP_CITY: Berlin
FOODCOOP_COUNTRY: XXX FOODCOOP_COUNTRY: Deutschland
FOODCOOP_EMAIL: info@example.org FOODCOOP_EMAIL: foodsoft@local-it.org
FOODCOOP_PHONE: XXX FOODCOOP_PHONE: 123456789
FOODCOOP_STREET: XXX FOODCOOP_STREET: Einkaufsstraße 5
FOODCOOP_ZIP_CODE: XXX FOODCOOP_ZIP_CODE: 12345
FOODCOOP_HOMEPAGE: https://order.example.org FOODCOOP_HOMEPAGE: https://foodsoft.local-it.org
FOODCOOP_HELP_URL: https://order.example.org FOODCOOP_HELP_URL: https://git.local-it.org/foodsoft/foodsoft
FOODCOOP_TIME_ZONE: Berlin FOODCOOP_TIME_ZONE: Berlin
FOODCOOP_USE_NICK: true FOODCOOP_USE_NICK: true
FOODCOOP_LANGUAGE: de FOODCOOP_LANGUAGE: de
FOODCOOP_FOOTER: '<a href="https://example.org/">example</a> hosted by <a href="https://yourhoster.org">Your Tech Co-op</a>.' 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 USE_APPLE_POINTS: false
STOP_ORDERING_UNDER: 75 STOP_ORDERING_UNDER: 75
MINIMUM_BALANCE: 0 MINIMUM_BALANCE: 0
@ -120,15 +120,15 @@ steps:
MYSQL_HOST: db MYSQL_HOST: db
MYSQL_PORT: 3306 MYSQL_PORT: 3306
MYSQL_USER: foodsoft MYSQL_USER: foodsoft
EMAIL_SENDER: noreply@example.org EMAIL_SENDER: demo@local-it.org
EMAIL_ERROR: systems@example.org EMAIL_ERROR: flip@yksflip.de
SMTP_ADDRESS: mail.example.com SMTP_ADDRESS: mail.local-it.org
SMTP_AUTHENTICATION: plain SMTP_AUTHENTICATION: login
SMTP_DOMAIN: mail.example.com SMTP_DOMAIN: mail.local-it.org
SMTP_ENABLE_STARTTLS_AUTO: true SMTP_ENABLE_STARTTLS_AUTO: true
SMTP_PORT: 587 SMTP_PORT: 587
SMTP_USER_NAME: foodsoft SMTP_USER_NAME: demo@local-it.org
EMAIL_REPLY_DOMAIN: example.org EMAIL_REPLY_DOMAIN:
SMTP_SERVER_HOST: 0.0.0.0 SMTP_SERVER_HOST: 0.0.0.0
SMTP_SERVER_PORT: 2525 SMTP_SERVER_PORT: 2525
SECRET_DB_PASSWORD_VERSION: v1 SECRET_DB_PASSWORD_VERSION: v1

View file

@ -23,7 +23,7 @@ gem 'bootsnap', require: false
gem 'mysql2' gem 'mysql2'
gem 'prawn' gem 'prawn'
gem 'prawn-table' gem 'prawn-table'
gem 'haml' gem 'haml', '~> 5.0'
gem 'haml-rails' gem 'haml-rails'
gem 'kaminari' gem 'kaminari'
gem 'simple_form' gem 'simple_form'

View file

@ -242,9 +242,8 @@ GEM
rails (>= 4.0.0) rails (>= 4.0.0)
globalid (1.0.0) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
haml (6.1.1) haml (5.2.2)
temple (>= 0.8.2) temple (>= 0.8.0)
thor
tilt tilt
haml-rails (2.1.0) haml-rails (2.1.0)
actionpack (>= 5.1) actionpack (>= 5.1)
@ -640,7 +639,7 @@ DEPENDENCIES
foodsoft_polls! foodsoft_polls!
foodsoft_wiki! foodsoft_wiki!
gaffe gaffe
haml haml (~> 5.0)
haml-rails haml-rails
hashie (~> 3.4.6) hashie (~> 3.4.6)
i18n-js (~> 3.0.0.rc8) i18n-js (~> 3.0.0.rc8)

161
README.md
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

@ -81,7 +81,7 @@ class Supplier < ApplicationRecord
all_order_numbers = [] all_order_numbers = []
updated_article_pairs, outlisted_articles, new_articles = [], [], [] updated_article_pairs, outlisted_articles, new_articles = [], [], []
custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml") custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
opts = options.except(:convert_units, :outlist_absent) opts = options.except(:convert_units, :outlist_absent, :update_category)
custom_codes_file_path = custom_codes_path if File.exist?(custom_codes_path) 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| FoodsoftArticleImport.parse(file, custom_file_path: custom_codes_file_path, type: type, **opts) do |new_attrs, status, line|
article = articles.undeleted.where(order_number: new_attrs[:order_number]).first article = articles.undeleted.where(order_number: new_attrs[:order_number]).first

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -930,6 +930,7 @@ es:
action_save: Guardar pedido action_save: Guardar pedido
new_funds: Nuevo balance de cuenta new_funds: Nuevo balance de cuenta
price: Precio price: Precio
price_per_base_unit: Precio de base
reset_article_search: Reinicia la búsqueda reset_article_search: Reinicia la búsqueda
search_article: Busca artículos... search_article: Busca artículos...
sum_amount: Cantidad actual sum_amount: Cantidad actual
@ -1261,6 +1262,9 @@ es:
units_ordered: Unidades pedidas units_ordered: Unidades pedidas
create: create:
notice: Se ha creado el pedido notice: Se ha creado el pedido
custom_csv:
description: Por favor elija el orden de los atributos así como los atributos para el archivo csv
column: columna
edit: edit:
title: 'Edita pedido: %{name}' title: 'Edita pedido: %{name}'
edit_amount: edit_amount:

View file

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

View file

@ -1018,6 +1018,7 @@ nl:
error_stale: In de tussentijd heeft iemand anders ook bestelt, daarom kon de bestelling niet bijgewerkt worden. error_stale: In de tussentijd heeft iemand anders ook bestelt, daarom kon de bestelling niet bijgewerkt worden.
notice: Bestelling opgeslagen. notice: Bestelling opgeslagen.
errors: errors:
balance_alert: Accountsaldo in het rood
closed: Deze bestelling is al gesloten. closed: Deze bestelling is al gesloten.
no_member: Je bent geen lid van dit huishouden. no_member: Je bent geen lid van dit huishouden.
notfound: Foute URL, dit is niet jouw bestelling. notfound: Foute URL, dit is niet jouw bestelling.
@ -1025,10 +1026,12 @@ nl:
action_save: Bestelling opslaan action_save: Bestelling opslaan
new_funds: Nieuw tegoed new_funds: Nieuw tegoed
price: Prijs price: Prijs
price_per_base_unit: Basisprjis
reset_article_search: Alles tonen reset_article_search: Alles tonen
search_article: Artikelen zoeken... search_article: Artikelen zoeken...
sum_amount: Huidig totaalbedrag sum_amount: Huidig totaalbedrag
title: Bestellen title: Bestellen
sub_title: Plaats bestelling voor %{order_name}
total_sum_amount: Totalbedrag total_sum_amount: Totalbedrag
total_tolerance: Totale tolerantie total_tolerance: Totale tolerantie
units: Eenheden units: Eenheden
@ -1072,7 +1075,6 @@ nl:
sum: Som sum: Som
title: Jouw bestelling voor %{order} title: Jouw bestelling voor %{order}
switch_order: switch_order:
remaining: "nog %{remaining}"
title: Lopende bestellingen title: Lopende bestellingen
update: update:
error_general: Er is een probleem opgetreden, de bestelling kon niet bijgewerkt worden. error_general: Er is een probleem opgetreden, de bestelling kon niet bijgewerkt worden.
@ -1440,6 +1442,9 @@ nl:
units_ordered: Bestelde eenheden units_ordered: Bestelde eenheden
create: create:
notice: De bestelling is aangemaakt. notice: De bestelling is aangemaakt.
custom_csv:
description: Kies de volgorde van de attributen en de attributen voor het csv-bestand
column: kolom
edit: edit:
title: 'Bestelling aanpassen: %{name}' title: 'Bestelling aanpassen: %{name}'
edit_amount: edit_amount:

View file

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

View file

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

View file

@ -39,6 +39,9 @@ nkn_supplier = Supplier.create!(
) )
chocolate_category = ArticleCategory.create!(name: "Schokolade") 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!( Article.create!(
name: "Vollmilch-Schokolade", name: "Vollmilch-Schokolade",
@ -95,7 +98,7 @@ seed_order(supplier_id: chocolate_supplier.id, starts: 0.days.ago, ends: 7.days.
apple = Article.create!( apple = Article.create!(
name: "Äpfel Elstar", name: "Äpfel Elstar",
supplier_id: nkn_supplier.id, supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id, article_category_id: obst_category.id,
manufacturer: "Obsthof Bruno Brugger", manufacturer: "Obsthof Bruno Brugger",
origin: "D", price: 3.49, tax: 7.0, origin: "D", price: 3.49, tax: 7.0,
unit: "1kg", unit_quantity: 10, unit: "1kg", unit_quantity: 10,
@ -105,7 +108,7 @@ apple = Article.create!(
brokkoli = Article.create!( brokkoli = Article.create!(
name: "Brokkoli", name: "Brokkoli",
supplier_id: nkn_supplier.id, supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id, article_category_id: obst_category.id,
manufacturer: "Fattoria degli Orsi", manufacturer: "Fattoria degli Orsi",
origin: "IT", price: 2.89, tax: 7.0, origin: "IT", price: 2.89, tax: 7.0,
unit: "400g", unit_quantity: 6, unit: "400g", unit_quantity: 6,
@ -115,17 +118,17 @@ brokkoli = Article.create!(
tomatoes = Article.create!( tomatoes = Article.create!(
name: "Tomaten", name: "Tomaten",
supplier_id: nkn_supplier.id, supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id, article_category_id: obst_category.id,
manufacturer: "Terra di Puglia", manufacturer: "Terra di Puglia",
origin: "IT", price: 2.89, tax: 7.0, origin: "IT", price: 2.89, tax: 7.0,
unit: "500g", unit_quantity: 20, unit: "500g", unit_quantity: 20,
note: "pomodori italianio, demeter", note: "pomodori italiani, demeter",
availability: true, order_number: "7") availability: true, order_number: "7")
rice = Article.create!( rice = Article.create!(
name: "Reis", name: "Reis",
supplier_id: nkn_supplier.id, supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id, article_category_id: reis_category.id,
manufacturer: "Finck", manufacturer: "Finck",
origin: "D", price: 3.29, tax: 7.0, origin: "D", price: 3.29, tax: 7.0,
unit: "3kg", unit_quantity: 10, unit: "3kg", unit_quantity: 10,
@ -135,7 +138,7 @@ rice = Article.create!(
spaghetti = Article.create!( spaghetti = Article.create!(
name: "Spaghetti", name: "Spaghetti",
supplier_id: nkn_supplier.id, supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id, article_category_id: nudeln_category.id,
manufacturer: "Pastificio Zanellini spa", manufacturer: "Pastificio Zanellini spa",
origin: "D", price: 2.89, tax: 7.0, origin: "D", price: 2.89, tax: 7.0,
unit: "500g", unit_quantity: 4, unit: "500g", unit_quantity: 4,

View file

@ -1,7 +1,7 @@
BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1 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;; 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;aus der Erde;;;VIB;;IT;C%;DE-™KO-001;120;03;10;55;;1;4 x400g;4;400g;1;N;;;;1,41;;;;1;;;4,49;3,20;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2,5;; 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;Datteltomaten, demeter;;;TDP;;IT;C%;DE-™KO-001;120;03;10;55;;1;20 x500g;20;500g;1;N;;;;1,41;;;;1;;;4,49;3,00;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;; 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;Reispfannen geeignet;;;FIN;;D;C%;DE-™KO-001;120;05;10;55;;1;12 x300g;12;300g;1;N;;;;1,41;;;;1;;;4,49;3,00;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;3,333333;; 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;Vollkorn;;;ZLN;;D;C%;DE-™KO-001;120;06;10;55;;1;4 x500g;4;500g;1;N;;;;1,41;;;;1;;;4,49;3,00;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;; 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;; 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;;

BIN
doc/foodcoop-explained.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

1
doc/logo-bmbf.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

1
doc/logo-okfn.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
doc/screenshots/order.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

BIN
doc/screenshots/rswag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -8,7 +8,10 @@ feature ArticlesController do
before { login user } before { login user }
describe ':index', js: true do describe ':index', js: true do
before { visit supplier_articles_path(supplier_id: supplier.id) } before {
login user
visit supplier_articles_path(supplier_id: supplier.id)
}
it 'can visit supplier articles path' do it 'can visit supplier articles path' do
expect(page).to have_content(supplier.name) expect(page).to have_content(supplier.name)

View file

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