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
image: git.local-it.org/philipp/stack-ssh-deply:latest
settings:
stack: "foodsoft_${DRONE_COMMIT:0:8}"
stack: "foodsoft_${DRONE_BRANCH}"
compose: "deployment/compose.yml"
deploy_key:
from_secret: drone_deploy_key
@ -96,23 +96,23 @@ steps:
- proxy
environment:
IMAGE: git.local-it.org/foodsoft/foodsoft:${DRONE_COMMIT:0:8}
STACK_NAME: "foodsoft_${DRONE_COMMIT:0:8}"
DOMAIN: "${DRONE_COMMIT:0:8}.foodsoft.dev.local-it.cloud"
STACK_NAME: "foodsoft_${DRONE_BRANCH}"
DOMAIN: "foodsoft.dev.local-it.cloud"
LETS_ENCRYPT_ENV: production
FOODCOOP_MULTI_INSTALL: true
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_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://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
STOP_ORDERING_UNDER: 75
MINIMUM_BALANCE: 0
@ -120,15 +120,15 @@ steps:
MYSQL_HOST: db
MYSQL_PORT: 3306
MYSQL_USER: foodsoft
EMAIL_SENDER: noreply@example.org
EMAIL_ERROR: systems@example.org
SMTP_ADDRESS: mail.example.com
SMTP_AUTHENTICATION: plain
SMTP_DOMAIN: mail.example.com
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: foodsoft
EMAIL_REPLY_DOMAIN: example.org
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

View file

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

View file

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

161
README.md
View file

@ -1,65 +1,124 @@
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).
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).
[Website](https://foodsoft.local-it.org)
[Prototypefund](https://prototypefund.de/project/weiterentwicklung-von-foodsoft/)
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),
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/).
Foodsoft wurde ursprünglich entwickelt und betrieben von [foodcoops.net](https://foodcoops.net/)
License
-------
#### Zielgruppe
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.
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.
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).
#### Vorhaben
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.
* ✅ 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)
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;
$('#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')
}
// update bgcolor
for (i in itemTotal) {
$('#td_price_' + i).css('background-color', bgcolor);
$('#balance-alert').css('display', 'none')
}
}

View file

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

View file

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

View file

@ -49,7 +49,7 @@ class OrdersController < ApplicationController
send_order_pdf @order, params[:document]
end
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
format.text do
send_data OrderTxt.new(@order).to_txt, filename: @order.name + '.txt', type: 'text/plain'
@ -57,6 +57,19 @@ 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,4 +53,12 @@ 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,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]}"
end
end
def custom_csv_collection
[
OrderArticle.human_attribute_name(:units_to_order),
Article.human_attribute_name(:order_number),
Article.human_attribute_name(:name),
Article.human_attribute_name(:unit),
Article.human_attribute_name(:unit_quantity_short),
ArticlePrice.human_attribute_name(:price),
OrderArticle.human_attribute_name(:total_price)
]
end
end

View file

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

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

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

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 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
.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
.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
.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.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
= 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
- 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= 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]
%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.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.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.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.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{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}/
%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}/

View file

@ -1,9 +1,10 @@
- orders = Order.open.started.reject{ |order| order == current_order }
- orders = Order.open.started
- unless orders.empty?
%h2= t '.title'
%ul.unstyled
%ul.nav.nav-pills.nav-stacked
.nav-header= t '.title'
%li= link_to t('ui.overview'), :group_orders
- orders.each do |order|
%li
= link_to_ordering(order, 'data-confirm_switch_order' => true)
- if order.ends
= t '.remaining', remaining: time_ago_in_words(order.ends)
.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)

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.numeric= number_to_currency(@ordergroup.get_available_funds)
= render :partial => "shared/open_orders", :locals => {:ordergroup => @ordergroup}
// finished orders
.row-fluid
.span9
= render :partial => "shared/open_orders", :locals => {:ordergroup => @ordergroup}
// finished orders
- unless @finished_not_closed_orders_including_group_order.empty?
%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)
.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)
// closed orders
- unless @closed_orders_including_group_order.empty?
%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
.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

View file

@ -7,107 +7,115 @@
- title t('.title', order: @order.name)
.row-fluid
.well.pull-left
// Order summary
.well.span2
= render 'switch_order', current_order: @order
.well.span9
%h2= t '.articles.title'
%dl.dl-horizontal
// Name
%dt= heading_helper Order, :name
%dd= @order.name
%dt= heading_helper Order, :note
%dd= @order.note
// Order Ends
%dt= heading_helper Order, :ends
%dd= format_time(@order.ends)
%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)
// 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
- if @order.closed?
%dt= heading_helper Order, :closed_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
= 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
// 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])
- 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])
- 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'
%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'
// Comments box
%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
%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

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
%tr
%th= heading_helper Order, :name
%th
%th= heading_helper Order, :pickup
%th= heading_helper Order, :ends
%th= t '.who_ordered'
@ -17,21 +18,23 @@
- total = 0
- orders.each do |order|
%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_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
= link_to_ordering(order, show: true) do
= number_to_currency(group_order.price)
= number_to_currency(group_order.price)
- else
%td{:colspan => 2}
- if total > 0
%tfooter
%tr
%th(colspan="3")
%th(colspan="4")
%th= t('.total_sum') + ':'
%th.numeric= number_to_currency(total)
- else

View file

@ -10,3 +10,4 @@
- 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

@ -1046,17 +1046,33 @@ 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
@ -1100,7 +1116,6 @@ 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.
@ -1465,6 +1480,9 @@ 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

@ -1048,17 +1048,33 @@ 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
@ -1102,7 +1118,6 @@ 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.
@ -1475,6 +1490,9 @@ 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:
@ -1628,6 +1646,7 @@ 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

@ -930,6 +930,7 @@ 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
@ -1261,6 +1262,9 @@ 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,6 +678,7 @@ 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
@ -1011,6 +1012,9 @@ 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

@ -1018,6 +1018,7 @@ 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.
@ -1025,10 +1026,12 @@ 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
@ -1072,7 +1075,6 @@ 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.
@ -1440,6 +1442,9 @@ 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,6 +47,7 @@ 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/minimal.seeds.rb')
require Rails.root.join('db/seeds/demo-seeds.rb')
# 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")
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",
@ -56,7 +59,7 @@ Article.create!(
article_category_id: chocolate_category.id,
manufacturer: "Grabower Süßwaren GmbH",
origin: "D", price: 3.49, tax: 7.0,
unit: "200g", unit_quantity: 5,
unit: "200g", unit_quantity: 5,
note: "bio, fairtrade, 40% Kakao, vegan",
availability: true, order_number: "2")
@ -95,7 +98,7 @@ seed_order(supplier_id: chocolate_supplier.id, starts: 0.days.ago, ends: 7.days.
apple = Article.create!(
name: "Äpfel Elstar",
supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id,
article_category_id: obst_category.id,
manufacturer: "Obsthof Bruno Brugger",
origin: "D", price: 3.49, tax: 7.0,
unit: "1kg", unit_quantity: 10,
@ -105,7 +108,7 @@ apple = Article.create!(
brokkoli = Article.create!(
name: "Brokkoli",
supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id,
article_category_id: obst_category.id,
manufacturer: "Fattoria degli Orsi",
origin: "IT", price: 2.89, tax: 7.0,
unit: "400g", unit_quantity: 6,
@ -115,17 +118,17 @@ brokkoli = Article.create!(
tomatoes = Article.create!(
name: "Tomaten",
supplier_id: nkn_supplier.id,
article_category_id: supplier_category.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 italianio, demeter",
note: "pomodori italiani, demeter",
availability: true, order_number: "7")
rice = Article.create!(
name: "Reis",
supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id,
article_category_id: reis_category.id,
manufacturer: "Finck",
origin: "D", price: 3.29, tax: 7.0,
unit: "3kg", unit_quantity: 10,
@ -135,7 +138,7 @@ rice = Article.create!(
spaghetti = Article.create!(
name: "Spaghetti",
supplier_id: nkn_supplier.id,
article_category_id: supplier_category.id,
article_category_id: nudeln_category.id,
manufacturer: "Pastificio Zanellini spa",
origin: "D", price: 2.89, tax: 7.0,
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
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;;
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;;
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;;
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;;
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;;

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