Compare commits

..

42 commits

Author SHA1 Message Date
Philipp Rothmann
9b518fbaee fix smtp incoming mail thing
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-12 17:51:37 +01:00
Philipp Rothmann
390e0d9819 merge demo
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-10 14:55:31 +01:00
Philipp Rothmann
e904168ce0 add: drone ci
Some checks failed
continuous-integration/drone/push Build is failing
fix: ci

fix: .drone docker rails version

add .drone caching

fix drone ci
2023-02-10 13:40:29 +01:00
Philipp Rothmann
9c20931ade fix: assets precompile by using terser
importmaps broke precompiliation with uglifier
see: https://github.com/rails/importmap-rails/issues/5
2023-02-10 13:40:27 +01:00
Philipp Rothmann
8eda266340 feat(messages): add html formatting to messages
This commit allows users to use the trix editor to send
messages with basic formatting and attachements.

* add active storage
* add actiontext
* add richtext field to messages
* add imageprocessing for message attachements
* add html email layout and adjust translations to use html urls
2023-02-10 12:28:40 +01:00
Philipp Rothmann
347973d139 enable ci
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-17 18:50:49 +01:00
a92c439418 adjust db to use invoices
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-17 17:43:53 +01:00
Philipp Rothmann
f519f2af1d merge
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-17 17:02:10 +01:00
Philipp Rothmann
d3b7937608 improve dockerfile caching
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-17 16:49:36 +01:00
Philipp Rothmann
30ac841664 upgrade dockerfile to rails7 2023-01-17 16:49:36 +01:00
Philipp Rothmann
cae7a47600 add: drone ci
fix: ci

fix: .drone docker rails version

add .drone caching

fix drone ci
2023-01-17 16:49:36 +01:00
b6854bc416 add authentication to group_order_invoices controller 2022-09-06 23:41:19 +02:00
0539edce82 remove unneccessary files in app/view/group_order_invoices 2022-09-06 23:39:23 +02:00
59fd8dadb1 remove footer on group order invoices showing time.now 2022-04-11 09:25:13 +02:00
f3914f3dd9 solve hover problem for ordering articles 2022-04-07 13:32:52 +02:00
aab1dcaff2 fix behavior - when link is provided in article details not clickable due to hover property 2022-04-05 13:20:06 +02:00
d37a75262d Merge branch 'master' of github.com:foodcoops/foodsoft into feature/tw_automatic_group_order_invoice_generation 2022-04-05 13:03:10 +02:00
3149e00e11 adapt tests for manually generating group order invoices for order 2022-04-05 12:05:17 +02:00
5a817a9acb add datefield default values max value cannot be blank 2022-03-30 15:49:53 +02:00
368375818e wip on generating invoices with date 2022-03-30 11:45:40 +02:00
76be8fdf1a wip on entering individual date for invoices 2022-03-29 13:17:36 +02:00
b2882d8592 Merge branch 'feature/tw_automatic_group_order_invoice_generation' 2022-03-01 13:31:29 +01:00
bb3e049630 accept changesfrom upstream and resolve merge conflict ~ automatic go invoices 2022-02-03 12:15:21 +01:00
07b058a799 Merge branch 'feature/tw_automatic_group_order_invoice_generation' of github.com:Viehlieb/foodsoft into feature/tw_automatic_group_order_invoice_generation 2022-02-03 11:43:02 +01:00
81a5194023 add vat exempt option in payment_tab ~ refs #automatic_go_invoices
add option vor vat_exempt inoices ~ refs automatic go invoices

fix order.ordergroup nil pointer error ~ automatic go invoices

fix nil pointer~ refs automatic go invoice

rubocop styling ~ refs automatic go invoice
2022-02-03 11:37:30 +01:00
bb69c3581a rubocop styling ~ refs automatic go invoice 2022-02-03 11:33:16 +01:00
81daddfb10 fix nil pointer~ refs automatic go invoice 2022-02-03 10:56:43 +01:00
1fc582319f fix order.ordergroup nil pointer error ~ automatic go invoices 2022-01-28 13:39:17 +01:00
bddaebe1a9 add option vor vat_exempt inoices ~ refs automatic go invoices 2022-01-27 16:42:22 +01:00
1de377c13a add vat exempt option in payment_tab ~ refs #automatic_go_invoices 2022-01-27 12:42:29 +01:00
Viehlieb
bafa163ce5 fix rubocop errors 2021-12-24 13:35:26 +01:00
Viehlieb
09679812af configure rubocop_todo to prevent errors 2021-12-24 13:35:08 +01:00
Viehlieb
303f9029d3 locales for prev commits 2021-12-24 13:31:46 +01:00
Viehlieb
f428450516 left over todo for naming od pdf file 2021-12-24 13:31:10 +01:00
Viehlieb
f592b27f92 fix tiny issue where flash alert not shown 2021-12-24 13:17:45 +01:00
Viehlieb
06eb56acf4 add tax_number, payment_method and automatic invoice options to be editable in admin configs 2021-12-23 01:57:23 +01:00
Viehlieb
72fa7c1123 add finally schema.rb 2021-12-23 01:57:04 +01:00
Viehlieb
298a476061 add tests (integration + model) 2021-12-23 01:52:37 +01:00
Viehlieb
eadfbd5969 add locales 2021-12-23 01:52:04 +01:00
Viehlieb
50017fefa8 add functionality in views and controllers for generation of group order invoices 2021-12-23 01:51:44 +01:00
Viehlieb
817c680c28 add mailer relevant files, add pdf relevant files and confgurations for invoice generation and sending 2021-12-23 01:50:06 +01:00
Viehlieb
0fa696cfdd add groupo order invoice and relation to group order 2021-12-23 01:48:06 +01:00
90 changed files with 1275 additions and 1497 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_BRANCH}" stack: "foodsoft_tantewandel"
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_BRANCH}" STACK_NAME: "foodsoft_tantewandel"
DOMAIN: "foodsoft.dev.local-it.cloud" DOMAIN: "tantewandel.dev.local-it.cloud"
LETS_ENCRYPT_ENV: production LETS_ENCRYPT_ENV: production
FOODCOOP_MULTI_INSTALL: true FOODCOOP_MULTI_INSTALL: true
FOODCOOP_NAME: Einkaufskooperative Foobar FOODCOOP_NAME: Tantewandel
FOODCOOP_CITY: Berlin FOODCOOP_CITY: Mechow
FOODCOOP_COUNTRY: Deutschland FOODCOOP_COUNTRY: Deutschland
FOODCOOP_EMAIL: foodsoft@local-it.org FOODCOOP_EMAIL: info@tantewandel.de
FOODCOOP_PHONE: 123456789 FOODCOOP_PHONE:
FOODCOOP_STREET: Einkaufsstraße 5 FOODCOOP_STREET:
FOODCOOP_ZIP_CODE: 12345 FOODCOOP_ZIP_CODE:
FOODCOOP_HOMEPAGE: https://foodsoft.local-it.org FOODCOOP_HOMEPAGE: https://tantewandel.de
FOODCOOP_HELP_URL: https://git.local-it.org/foodsoft/foodsoft FOODCOOP_HELP_URL: https://tantewandel.de
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://foodsoft.local-it.org/">Foodsoft</a> hosted by <a href="https://local-it.org">local-it e,V,</a>.' FOODCOOP_FOOTER: '<a href="https://example.org/">example</a> hosted by <a href="https://yourhoster.org">Your Tech Co-op</a>.'
USE_APPLE_POINTS: false USE_APPLE_POINTS: false
STOP_ORDERING_UNDER: 75 STOP_ORDERING_UNDER: 75
MINIMUM_BALANCE: 0 MINIMUM_BALANCE: 0
@ -143,3 +143,4 @@ steps:
trigger: trigger:
branch: branch:
- demo - demo
- tantewandel

View file

@ -261,6 +261,8 @@ Lint/Void:
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes. # Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
Metrics/AbcSize: Metrics/AbcSize:
Max: 143 Max: 143
Exclude:
- 'app/documents/group_order_invoice_pdf.rb'
# Offense count: 17 # Offense count: 17
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, inherit_mode. # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, inherit_mode.
@ -402,22 +404,16 @@ RSpec/BeforeAfterAll:
# Configuration parameters: EnabledMethods. # Configuration parameters: EnabledMethods.
RSpec/Capybara/FeatureMethods: RSpec/Capybara/FeatureMethods:
Exclude: Exclude:
- "spec/integration/articles_spec.rb" - 'spec/integration/articles_spec.rb'
- "spec/integration/balancing_spec.rb" - 'spec/integration/balancing_spec.rb'
- "spec/integration/config_spec.rb" - 'spec/integration/config_spec.rb'
- "spec/integration/home_spec.rb" - 'spec/integration/login_spec.rb'
- "spec/integration/login_spec.rb" - 'spec/integration/order_spec.rb'
- "spec/integration/order_spec.rb" - 'spec/integration/product_distribution_example_spec.rb'
- "spec/integration/product_distribution_example_spec.rb" - 'spec/integration/receive_spec.rb'
- "spec/integration/receive_spec.rb" - 'spec/integration/session_spec.rb'
- "spec/integration/session_spec.rb" - 'spec/integration/supplier_spec.rb'
- "spec/integration/supplier_spec.rb" - 'spec/integration/group_order_invoices_spec.rb'
# Offense count: 4
RSpec/Capybara/SpecificMatcher:
Exclude:
- "spec/integration/login_spec.rb"
- "spec/integration/session_spec.rb"
# Offense count: 27 # Offense count: 27
# Configuration parameters: Prefixes, AllowedPatterns. # Configuration parameters: Prefixes, AllowedPatterns.

View file

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

View file

@ -23,7 +23,7 @@ gem 'bootsnap', require: false
gem 'mysql2' gem 'mysql2'
gem 'prawn' gem 'prawn'
gem 'prawn-table' gem 'prawn-table'
gem 'haml', '~> 5.0' gem 'haml'
gem 'haml-rails' gem 'haml-rails'
gem 'kaminari' gem 'kaminari'
gem 'simple_form' gem 'simple_form'
@ -49,7 +49,6 @@ gem 'attribute_normalizer'
gem 'ice_cube' gem 'ice_cube'
# At time of development 01-06-2022 mmddyyyy necessary fix for config_helper.rb form builder was not in rubygems so we pull from github, see: https://github.com/gregschmit/recurring_select/pull/152 # At time of development 01-06-2022 mmddyyyy necessary fix for config_helper.rb form builder was not in rubygems so we pull from github, see: https://github.com/gregschmit/recurring_select/pull/152
gem 'recurring_select', git: 'https://github.com/gregschmit/recurring_select' gem 'recurring_select', git: 'https://github.com/gregschmit/recurring_select'
gem 'foodsoft_article_import', git: 'https://git.local-it.org/Foodsoft/foodsoft_article_import', tag: 'v1.0'
gem 'roo' gem 'roo'
gem 'roo-xls' gem 'roo-xls'
gem 'spreadsheet' gem 'spreadsheet'

View file

@ -1,11 +1,3 @@
GIT
remote: https://git.local-it.org/Foodsoft/foodsoft_article_import
revision: 49a0c1ddb3bb67a357c692c63af0cda2db7c45b0
tag: v1.0
specs:
foodsoft_article_import (1.0.0)
roo (~> 2.9.0)
GIT GIT
remote: https://github.com/gregschmit/recurring_select remote: https://github.com/gregschmit/recurring_select
revision: 29febc4c4abdd6c30636c33a7d2daecb09973ecf revision: 29febc4c4abdd6c30636c33a7d2daecb09973ecf
@ -242,8 +234,9 @@ GEM
rails (>= 4.0.0) rails (>= 4.0.0)
globalid (1.0.0) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
haml (5.2.2) haml (6.1.1)
temple (>= 0.8.0) temple (>= 0.8.2)
thor
tilt tilt
haml-rails (2.1.0) haml-rails (2.1.0)
actionpack (>= 5.1) actionpack (>= 5.1)
@ -631,7 +624,6 @@ DEPENDENCIES
exception_notification exception_notification
factory_bot_rails factory_bot_rails
faker faker
foodsoft_article_import!
foodsoft_discourse! foodsoft_discourse!
foodsoft_documents! foodsoft_documents!
foodsoft_links! foodsoft_links!
@ -639,7 +631,7 @@ DEPENDENCIES
foodsoft_polls! foodsoft_polls!
foodsoft_wiki! foodsoft_wiki!
gaffe gaffe
haml (~> 5.0) haml
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,124 +1,65 @@
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)
[Website](https://foodsoft.local-it.org) Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling).
[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).
Foodsoft ist ein Tool für [Lebensmittelkooperativen](https://de.wikipedia.org/wiki/Lebensmittelkooperative), welches selbstorganisierte gemeinsame Bestellungen in Großmengen von regionalen und ökologischen Produkten vereinfacht und transparent gestaltet. Developing
----------
Foodsoft wurde ursprünglich entwickelt und betrieben von [foodcoops.net](https://foodcoops.net/) Get foodsoft [running locally](doc/SETUP_DEVELOPMENT.md),
then visit our [Developing Guidelines](https://github.com/foodcoops/foodsoft/wiki/Developing-Guidelines)
page on the wiki.
Get a foodsoft dev-environment running in the browser with Gitpod
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/foodcoops/foodsoft)
Follow these [instructions](doc/SETUP_DEVELOPMENT_GITPOD.md) to complete setup from within the Gitpod workspace.
Deploying
---------
Setup foodsoft to [run in production](doc/SETUP_PRODUCTION.md), or join an existing
[hosting platform](https://foodcoops.net/foodsoft-hosting/).
#### Zielgruppe License
-------
Unsere Zielgruppen sind Bürger:innen, Gruppen und Vereine, die eine Einkauskooperative aufbauen wollen und eine Software, die die Bestellung, Verteilung und Abrechnung erleichtert, benötigen. Foodsoft is licensed under the [AGPL](https://www.gnu.org/licenses/agpl-3.0.html)
license (version 3 or later). Practically this means that you are free to use,
adapt and redistribute the software, as long as you publish any changes you
make to the code.
#### Vorhaben For private use, there are no restrictions, but if you give others access to
Foodsoft (like running it open to the internet), you must also make your
changes available under the same license. This can be as easy as
[forking](https://github.com/foodcoops/foodsoft/fork) the project on Github and
pushing your changes. You are not required to integrate your changes back into
the main Foodsoft version (but if you're up for it that would be very welcome).
* ✅ Technische Schuld reduzieren To make it a little easier, configuration files are exempt, so you can just
* ✅ Ruby on Rails Upgrade install and configure Foodsoft without having to publish your changes. These
* ✅ Artikel Import verbessern files are marked as public domain in the file header.
(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,13 +179,17 @@ 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: gray; } background-color: red; }
} }
} }
} }
@ -278,14 +278,13 @@ tr.order-article .article-info {
display: none; display: none;
} }
tr.order-article:focus{
background-color: #E4EED6;
}
tr.order-article:focus .article-info { tr.order-article:focus .article-info {
display: block; display: block;
} }
tr.order-article:focus {
background-color: #E9E9E9;
}
// ********* Articles // ********* Articles
tr.just-updated { tr.just-updated {

View file

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

View file

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

View file

@ -0,0 +1,13 @@
module Concerns::SendGroupOrderInvoicePdf
extend ActiveSupport::Concern
protected
def send_group_order_invoice_pdf(group_order_invoice)
invoice_data = group_order_invoice.load_data_for_invoice
invoice_data[:title] = t('documents.group_order_invoice_pdf.title', supplier: invoice_data[:supplier])
invoice_data[:no_footer] = true
pdf = GroupOrderInvoicePdf.new invoice_data
send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf'
end
end

View file

@ -5,7 +5,7 @@ class Finance::BalancingController < Finance::BaseController
def new def new
@order = Order.find(params[:order_id]) @order = Order.find(params[:order_id])
flash.now.alert = t('finance.balancing.new.alert') if @order.closed? flash.now.alert = t('finance.balancing.new.alert') if @order.closed? && flash[:alert].blank?
@comments = @order.comments @comments = @order.comments
@articles = @order.order_articles.ordered_or_member.includes(:article, :article_price, @articles = @order.order_articles.ordered_or_member.includes(:article, :article_price,
@ -81,9 +81,24 @@ class Finance::BalancingController < Finance::BaseController
@order = Order.find(params[:id]) @order = Order.find(params[:id])
@type = FinancialTransactionType.find_by_id(params.permit(:type)[:type]) @type = FinancialTransactionType.find_by_id(params.permit(:type)[:type])
@order.close!(@current_user, @type) @order.close!(@current_user, @type)
redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice') note = t('finance.balancing.close.notice')
if @order.closed?
alert = t('finance.balancing.close.alert')
if FoodsoftConfig[:group_order_invoices]&.[](:use_automatic_invoices)
@order.group_orders.each do |go|
alert = t('finance.balancing.close.settings_not_set')
goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
if goi.save!
NotifyGroupOrderInvoiceJob.perform_later(goi)
note = t('finance.balancing.close.notice_mail')
end
end
end
end
alert ||= t('finance.balancing.close.alert')
redirect_to finance_order_index_url, notice: note
rescue => error rescue => error
redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message) redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: alert, msg: error.message
end end
# Close the order directly, without automaticly updating ordergroups account balances # Close the order directly, without automaticly updating ordergroups account balances

View file

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

View file

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

View file

@ -0,0 +1,59 @@
class GroupOrderInvoicesController < ApplicationController
include Concerns::SendGroupOrderInvoicePdf
before_action :authenticate_finance
def show
begin
@group_order_invoice = GroupOrderInvoice.find(params[:id])
if FoodsoftConfig[:contact][:tax_number]
respond_to do |format|
format.pdf do
send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number]
end
end
else
raise RecordInvalid
end
rescue ActiveRecord::RecordInvalid => error
redirect_back fallback_location: root_path, notice: 'Something went wrong', alert: I18n.t('errors.general_msg', msg: "#{error} " + I18n.t('errors.check_tax_number'))
end
end
def destroy
goi = GroupOrderInvoice.find(params[:id])
@order = goi.group_order.order
goi.destroy
respond_to do |format|
format.js
format.json { head :no_content }
end
end
def create_multiple
invoice_date = params[:group_order_invoice][:invoice_date]
order_id = params[:group_order_invoice][:order_id]
@order = Order.find(order_id)
gos = GroupOrder.where("order_id = ?", order_id)
gos.each do |go|
goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
goi.invoice_date = invoice_date
goi.invoice_number = goi.generate_invoice_number(1)
goi.save!
end
respond_to do |format|
format.js
end
end
def create
go = GroupOrder.find(params[:group_order])
@order = go.order
GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
respond_to do |format|
format.js
end
redirect_back fallback_location: root_path
rescue => error
redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => error)
end
end

View file

@ -49,7 +49,7 @@ class OrdersController < ApplicationController
send_order_pdf @order, params[:document] send_order_pdf @order, params[:document]
end end
format.csv do format.csv do
send_data OrderCsv.new(@order, options= {custom_csv: params[:custom_csv]}).to_csv, filename: @order.name + '.csv', type: 'text/csv' send_data OrderCsv.new(@order).to_csv, filename: @order.name + '.csv', type: 'text/csv'
end 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,19 +57,6 @@ 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

@ -0,0 +1,202 @@
class GroupOrderInvoicePdf < RenderPdf
def filename
I18n.t('documents.group_order_invoice_pdf.filename', :number => @options[:invoice_number]) + '.pdf'
end
def title
I18n.t('documents.group_order_invoice_pdf.title', :supplier => @options[:supplier])
end
def body
contact = FoodsoftConfig[:contact].symbolize_keys
ordergroup = @options[:ordergroup]
# From paragraph
bounding_box [margin_box.right - 200, margin_box.top - 20], width: 200 do
text I18n.t('documents.group_order_invoice_pdf.invoicer')
move_down 7
text FoodsoftConfig[:name], size: fontsize(9), align: :left
move_down 5
text contact[:street], size: fontsize(9), align: :left
move_down 5
text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :left
move_down 5
unless contact[:phone].blank?
text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :left
move_down 5
end
unless contact[:email].blank?
text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :left
end
move_down 5
text I18n.t('documents.group_order_invoice_pdf.tax_number', :number => @options[:tax_number]), size: fontsize(9), align: :left
end
# Receiving Ordergroup
bounding_box [margin_box.left, margin_box.top - 20], width: 200 do
text I18n.t('documents.group_order_invoice_pdf.invoicee')
move_down 7
text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s), size: fontsize(9)
move_down 5
if ordergroup.contact_address
text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s), size: fontsize(9)
move_down 5
end
if ordergroup.contact_phone
text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s), size: fontsize(9)
move_down 5
end
end
# invoice Date and nnvoice number
bounding_box [margin_box.right - 200, margin_box.top - 150], width: 200 do
text I18n.t('documents.group_order_invoice_pdf.invoice_date', invoice_date: @options[:invoice_date].strftime(I18n.t('date.formats.default'))), align: :left
move_down 5
text I18n.t('documents.group_order_invoice_pdf.invoice_number', invoice_number: @options[:invoice_number]), align: :left
end
move_down 15
# kind of the "body" of the invoice
text I18n.t('documents.group_order_invoice_pdf.payment_method', payment_method: @options[:payment_method])
move_down 15
text I18n.t('documents.group_order_invoice_pdf.table_headline')
move_down 5
#------------- Table Data -----------------------
@group_order = GroupOrder.find(@options[:group_order].id)
if FoodsoftConfig[:group_order_invoices][:vat_exempt]
body_for_vat_exempt
else
body_with_vat
end
end
def body_for_vat_exempt
total_gross = 0
data = [I18n.t('documents.group_order_invoice_pdf.vat_exempt_rows')]
move_down 10
group_order_articles = GroupOrderArticle.where(group_order_id: @group_order.id)
group_order_articles.each do |goa|
# if no unit is received, nothing is to be charged
next if goa.result.to_i == 0
goa_total_gross = goa.result * goa.order_article.price.gross_price
data << [goa.order_article.article.name,
goa.result.to_i,
number_to_currency(goa.order_article.price.gross_price),
number_to_currency(goa.total_price)]
total_gross += goa_total_gross
end
table data, position: :left, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
table.header = true
table.position = :center
table.cells.border_width = 1
table.cells.border_color = '666666'
table.row(0).column(1).width = 40
table.row(0).border_bottom_width = 2
table.columns(1).align = :right
table.columns(1..6).align = :right
end
move_down 5
sum = []
sum << [nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay'), number_to_currency(total_gross)]
# table for sum
indent(200) do
table sum, position: :center, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
sum.length.times do |count|
table.row(count).columns(0..3).borders = []
end
table.row(sum.length - 1).columns(0..2).borders = []
table.row(sum.length - 1).border_bottom_width = 2
table.row(sum.length - 1).columns(3).borders = [:bottom]
end
end
move_down 25
text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
move_down 10
end
def body_with_vat
total_gross = 0
total_net = 0
# Articles
tax_hash_net = Hash.new(0) # for summing up article net prices grouped into vat percentage
tax_hash_gross = Hash.new(0) # same here with gross prices
marge = FoodsoftConfig[:price_markup]
# data table looks different when price_markup > 0
data = if marge == 0
[I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')]
else
[I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)]
end
goa_tax_hash = GroupOrderArticle.where(group_order_id: @group_order.id).find_each.group_by { |oat| oat.order_article.price.tax }
goa_tax_hash.each do |tax, group_order_articles|
group_order_articles.each do |goa|
# if no unit is received, nothing is to be charged
next if goa.result.to_i == 0
order_article = goa.order_article
goa_total_net = goa.result * order_article.price.price
goa_total_gross = goa.result * order_article.price.gross_price
data << [order_article.article.name,
goa.result.to_i,
number_to_currency(order_article.price.price),
number_to_currency(goa_total_net),
tax.to_s + '%',
number_to_currency(goa.total_price)]
tax_hash_net[tax.to_i] += goa_total_net
tax_hash_gross[tax.to_i] += goa_total_gross
total_net += goa_total_net
total_gross += goa_total_gross
end
end
# Two separate tables for sum and individual data
# article information + data
table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
table.header = true
table.position = :center
table.cells.border_width = 1
table.cells.border_color = '666666'
table.row(0).column(1).width = 40
table.row(0).border_bottom_width = 2
table.columns(1).align = :right
table.columns(1..6).align = :right
end
sum = []
sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_net'), number_to_currency(total_net)]
tax_hash_net.each_key.each do |tax|
sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.tax_included', tax: tax), number_to_currency(tax_hash_gross[tax] - tax_hash_net[tax])]
end
unless marge == 0
sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.markup_included', marge: marge), number_to_currency(total_gross * marge / 100.0)]
end
end_sum = total_gross * (1 + marge / 100.0)
sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(end_sum)]
# table for sum
table sum, position: :right, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
sum.length.times do |count|
table.row(count).columns(0..5).borders = []
end
table.row(sum.length - 1).columns(0..4).borders = []
table.row(sum.length - 1).border_bottom_width = 2
table.row(sum.length - 1).columns(5).borders = [:bottom]
end
if(FoodsoftConfig[:group_order_invoices][:vat_exempt])
move_down 15
text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
end
move_down 10
end
end

View file

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

View file

@ -0,0 +1,10 @@
class NotifyGroupOrderInvoiceJob < ApplicationJob
def perform(group_order_invoice)
ordergroup = group_order_invoice.group_order.ordergroup
ordergroup.users.each do |user|
Mailer.deliver_now_with_user_locale user do
Mailer.group_order_invoice(group_order_invoice, user)
end
end
end
end

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

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

View file

@ -2,8 +2,6 @@ require 'csv'
class OrderCsv < RenderCsv class OrderCsv < RenderCsv
def header def header
params = @options[:custom_csv]
arr = if params.nil?
[ [
OrderArticle.human_attribute_name(:units_to_order), OrderArticle.human_attribute_name(:units_to_order),
Article.human_attribute_name(:order_number), Article.human_attribute_name(:order_number),
@ -13,49 +11,19 @@ class OrderCsv < RenderCsv
ArticlePrice.human_attribute_name(:price), ArticlePrice.human_attribute_name(:price),
OrderArticle.human_attribute_name(:total_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 [
match_params(oa, header[0]), oa.units_to_order,
match_params(oa, header[1]), oa.article.order_number,
match_params(oa, header[2]), oa.article.name,
match_params(oa, header[3]), oa.article.unit,
match_params(oa, header[4]), oa.price.unit_quantity > 1 ? oa.price.unit_quantity : nil,
match_params(oa, header[5]), number_to_currency(oa.price.price * oa.price.unit_quantity),
match_params(oa, header[6]) number_to_currency(oa.total_price)
] ]
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

View file

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

View file

@ -20,7 +20,6 @@ class RenderCsv
end 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

@ -70,7 +70,7 @@ class RenderPdf < Prawn::Document
options[:skip_page_creation] = true options[:skip_page_creation] = true
@options = options @options = options
@first_page = true @first_page = true
no_footer = @options&.[](:no_footer) ? true : false
super(options) super(options)
# Use ttf for better utf-8 compability # Use ttf for better utf-8 compability
@ -84,11 +84,11 @@ class RenderPdf < Prawn::Document
) )
header = options[:title] || title header = options[:title] || title
footer = I18n.l(Time.now, format: :long) footer = I18n.l(Time.now, format: :long) unless no_footer
header_size = 0 header_size = 0
header_size = height_of(header, size: HEADER_FONT_SIZE, font: DEFAULT_FONT) + HEADER_SPACE if header header_size = height_of(header, size: HEADER_FONT_SIZE, font: DEFAULT_FONT) + HEADER_SPACE if header
footer_size = height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE footer_size = no_footer ? 0 : height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE
start_new_page(top_margin: TOP_MARGIN + header_size, bottom_margin: BOTTOM_MARGIN + footer_size) start_new_page(top_margin: TOP_MARGIN + header_size, bottom_margin: BOTTOM_MARGIN + footer_size)
@ -98,6 +98,8 @@ class RenderPdf < Prawn::Document
bounding_box [bounds.left, bounds.top + header_size], width: bounds.width, height: header_size do bounding_box [bounds.left, bounds.top + header_size], width: bounds.width, height: header_size do
text header, size: HEADER_FONT_SIZE, align: :center, overflow: :shrink_to_fit if header text header, size: HEADER_FONT_SIZE, align: :center, overflow: :shrink_to_fit if header
end end
unless no_footer
font_size FOOTER_FONT_SIZE do font_size FOOTER_FONT_SIZE do
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
text footer, align: :left, valign: :bottom text footer, align: :left, valign: :bottom
@ -108,6 +110,7 @@ class RenderPdf < Prawn::Document
end end
end end
end end
end
def title def title
nil nil

View file

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

View file

@ -51,6 +51,18 @@ class Mailer < ActionMailer::Base
subject: I18n.t('mailer.welcome.subject') subject: I18n.t('mailer.welcome.subject')
end end
# Sends automatically generated invoicesfor group orders to ordergroup members
def group_order_invoice(group_order_invoice, user)
@user = user
@group_order_invoice = group_order_invoice
@group_order = group_order_invoice.group_order
@supplier = @group_order.order.supplier.name
@group = @group_order.ordergroup
add_group_order_invoice_attachments(group_order_invoice)
mail to: user,
subject: I18n.t('mailer.group_order_invoice.subject', group: @group.name, supplier: @supplier)
end
# Sends order result for specific Ordergroup # Sends order result for specific Ordergroup
def order_result(user, group_order) def order_result(user, group_order)
@order = group_order.order @order = group_order.order
@ -168,6 +180,11 @@ class Mailer < ActionMailer::Base
attachments['order.csv'] = OrderCsv.new(order, options).to_csv attachments['order.csv'] = OrderCsv.new(order, options).to_csv
end end
def add_group_order_invoice_attachments(group_order_invoice)
attachment_name = group_order_invoice.name + '.pdf'
attachments[attachment_name] = GroupOrderInvoicePdf.new(group_order_invoice.load_data_for_invoice).to_pdf
end
# separate method to allow plugins to mess with the text # separate method to allow plugins to mess with the text
def additonal_welcome_text(user) def additonal_welcome_text(user)
end end

View file

@ -143,7 +143,8 @@ class Article < ApplicationRecord
new_unit = new_article.unit new_unit = new_article.unit
end end
attribute_hash = { return Article.compare_attributes(
{
:name => [self.name, new_article.name], :name => [self.name, new_article.name],
:manufacturer => [self.manufacturer, new_article.manufacturer.to_s], :manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
:origin => [self.origin, new_article.origin], :origin => [self.origin, new_article.origin],
@ -155,12 +156,7 @@ class Article < ApplicationRecord
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f], :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
:note => [self.note.to_s, new_article.note.to_s] :note => [self.note.to_s, new_article.note.to_s]
} }
if options[:update_category] == true )
new_article_category = new_article.article_category
attribute_hash[:article_category] = [self.article_category, new_article_category] unless new_article_category.blank?
end
Article.compare_attributes(attribute_hash)
end end
# Compare attributes from two different articles. # Compare attributes from two different articles.

View file

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

View file

@ -9,6 +9,7 @@ class GroupOrder < ApplicationRecord
has_many :group_order_articles, :dependent => :destroy has_many :group_order_articles, :dependent => :destroy
has_many :order_articles, :through => :group_order_articles has_many :order_articles, :through => :group_order_articles
has_one :financial_transaction has_one :financial_transaction
has_one :group_order_invoice
belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id' belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id'
validates_presence_of :order_id validates_presence_of :order_id
@ -32,8 +33,8 @@ class GroupOrder < ApplicationRecord
# Generate some data for the javascript methods in ordering view # Generate some data for the javascript methods in ordering view
def load_data def load_data
data = {} data = {}
data[:account_balance] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.account_balance data[:account_balance] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.account_balance
data[:available_funds] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.get_available_funds(self) data[:available_funds] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.get_available_funds(self)
# load prices and other stuff.... # load prices and other stuff....
data[:order_articles] = {} data[:order_articles] = {}

View file

@ -0,0 +1,58 @@
class GroupOrderInvoice < ApplicationRecord
belongs_to :group_order
validates_presence_of :group_order
validates_uniqueness_of :invoice_number
validate :tax_number_set
after_initialize :init, unless: :persisted?
def generate_invoice_number(count)
trailing_number = count.to_s.rjust(4, '0')
if GroupOrderInvoice.find_by(invoice_number: self.invoice_date.strftime("%Y%m%d") + trailing_number)
generate_invoice_number(count.to_i + 1)
else
self.invoice_date.strftime("%Y%m%d") + trailing_number
end
end
def tax_number_set
if FoodsoftConfig[:contact][:tax_number].blank?
errors.add(:group_order_invoice, "Keine Steuernummer in FoodsoftConfig :contact gesetzt")
end
end
def init
self.invoice_date = Time.now unless invoice_date
self.invoice_number = generate_invoice_number(1) unless self.invoice_number
self.payment_method = FoodsoftConfig[:group_order_invoices]&.[](:payment_method) || I18n.t('activerecord.attributes.group_order_invoice.payment_method') unless self.payment_method
end
def name
I18n.t('activerecord.attributes.group_order_invoice.name') + "_#{invoice_number}"
end
def load_data_for_invoice
invoice_data = {}
order = group_order.order
invoice_data[:supplier] = order.supplier.name
invoice_data[:ordergroup] = group_order.ordergroup
invoice_data[:group_order] = group_order
invoice_data[:invoice_number] = invoice_number
invoice_data[:invoice_date] = invoice_date
invoice_data[:tax_number] = FoodsoftConfig[:contact][:tax_number]
invoice_data[:payment_method] = payment_method
invoice_data[:order_articles] = {}
group_order.order_articles.each do |order_article|
# Get the result of last time ordering, if possible
goa = group_order.group_order_articles.detect { |tmp_goa| tmp_goa.order_article_id == order_article.id }
# Build hash with relevant data
invoice_data[:order_articles][order_article.id] = {
:price => order_article.article.fc_price,
:quantity => (goa ? goa.quantity : 0),
:total_price => (goa ? goa.total_price : 0),
:tax => order_article.article.tax
}
end
invoice_data
end
end

View file

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

View file

@ -7,4 +7,5 @@
= config_input c, :country, as: :string, input_html: {class: 'input-xlarge'} = config_input c, :country, as: :string, input_html: {class: 'input-xlarge'}
= config_input c, :email, required: true, input_html: {class: 'input-xlarge'} = config_input c, :email, required: true, input_html: {class: 'input-xlarge'}
= config_input c, :phone, input_html: {class: 'input-medium'} = config_input c, :phone, input_html: {class: 'input-medium'}
= config_input c, :tax_number, input_html: {class: 'input-medium'}
= config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'} = config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'}

View file

@ -13,6 +13,11 @@
= config_input form, :charge_members_manually, as: :boolean = config_input form, :charge_members_manually, as: :boolean
= config_input form, :use_iban, as: :boolean = config_input form, :use_iban, as: :boolean
= config_input form, :use_self_service, as: :boolean = config_input form, :use_self_service, as: :boolean
%h4= t '.group_order_invoices'
= form.fields_for :group_order_invoices do |field|
= config_input field, :use_automatic_invoices, as: :boolean
= config_input field, :vat_exempt, as: :boolean
= config_input field, :payment_method, as: :string, input_html: {class: 'input-medium'}
%h4= t '.schedule_title' %h4= t '.schedule_title'
= form.simple_fields_for :order_schedule do |fields| = form.simple_fields_for :order_schedule do |fields|

View file

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

View file

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

View file

@ -9,6 +9,8 @@
%th= t('.end') %th= t('.end')
%th= t('.state') %th= t('.state')
%th= heading_helper Order, :updated_by %th= heading_helper Order, :updated_by
%th= heading_helper GroupOrderInvoice, :name
%th
%th %th
%tbody %tbody
- @orders.each do |order| - @orders.each do |order|
@ -17,6 +19,14 @@
%td=h format_time(order.ends) unless order.ends.nil? %td=h format_time(order.ends) unless order.ends.nil?
%td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended') %td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended')
%td= show_user(order.updated_by) %td= show_user(order.updated_by)
%td{id: "generate-invoice#{order.id}"}
- if order.closed?
-if FoodsoftConfig[:contact][:tax_number] && order.ordergroups.present?
= render :partial => 'group_order_invoices/links', locals:{order: order}
-else
= I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set')
- else
= t('orders.index.not_closed')
%td %td
- unless order.closed? - unless order.closed?
- if current_user.role_orders? - if current_user.role_orders?

View file

@ -1,5 +1,5 @@
- title t('.title') - title t('.title')
- puts params
- content_for :actionbar do - content_for :actionbar do
- if FoodsoftConfig[:charge_members_manually] - if FoodsoftConfig[:charge_members_manually]
= link_to t('.close_all_direct_with_invoice'), close_all_direct_with_invoice_finance_order_index_path, method: :post, class: 'btn' = link_to t('.close_all_direct_with_invoice'), close_all_direct_with_invoice_finance_order_index_path, method: :post, class: 'btn'

View file

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

View file

@ -0,0 +1,25 @@
.row
.column.small-12
- show_generate_with_date = true
- order.group_orders.each do |go|
- if go.group_order_invoice.present?
- show_generate_with_date = false
- if show_generate_with_date
= form_for :group_order_invoice, url: url_for('group_order_invoice#create_multiple'), remote: true do |f|
= f.label :invoice_date, I18n.t('activerecord.attributes.group_order_invoice.links.invoice_date')
= f.date_field :invoice_date, {value: Date.today, max: Date.today, required: true}
= f.hidden_field :order_id, value: order.id
= f.submit I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'), class: 'btn btn small'
- order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go|
.row
.column.small-3
= label_tag go.ordergroup.name
- if go.group_order_invoice
.column.small-3
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), group_order_invoice_path(go.group_order_invoice, :format => 'pdf'), class: 'btn btn-small'
.column.small-3
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-danger btn-small', remote: true
- else
= button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), group_order_invoices_path(:method => :post, group_order: go) ,class: 'btn btn-small', params: {id: order.id}, remote: true

View file

@ -0,0 +1 @@
$("#generate-invoice<%= params[:id] %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");

View file

@ -0,0 +1 @@
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");

View file

@ -0,0 +1 @@
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");

View file

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

View file

@ -22,36 +22,49 @@
- 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
.span2 .well.pull-left
.well = close_button :alert
= render 'switch_order', current_order: @order %h2= @order.name
.well %dl.dl-horizontal
= render 'explanations' - unless @order.note.blank?
.well.span9 %dt= heading_helper Order, :note
%h2.span9= t '.sub_title', order_name: @order.name %dd= @order.note
.span3 %dt= heading_helper Order, :created_by
%table.table-condensed %dd= show_user_link(@order.created_by)
-if @order.ends %dt= heading_helper Order, :ends
%tr %dd= format_time(@order.ends)
%td= heading_helper(Order, :ends) + ': ' %dt= heading_helper Order, :pickup
%td= format_time(@order.ends) %dd= format_date(@order.pickup)
- unless @order.stockit? or @order.supplier.min_order_quantity.blank? - unless @order.stockit? or @order.supplier.min_order_quantity.blank?
%tr %dt= heading_helper Supplier, :min_order_quantity, short: true
%td= heading_helper(Supplier, :min_order_quantity) %dd= @order.supplier.min_order_quantity
%td= number_to_currency(@order.supplier.min_order_quantity) %dt= t '.sum_amount'
%tr %dd= number_to_currency @order.sum
%td= t('group_orders.form.sum_amount') + ':' - unless @group_order.new_record?
%td= number_to_currency(@order.sum) %dt= heading_helper GroupOrder, :updated_by
%hr %dd
.form-search.pull-right = show_user(@group_order.updated_by)
(#{format_time(@group_order.updated_on)})
%dt= heading_helper Ordergroup, :account_balance
%dd= number_to_currency(@ordering_data[:account_balance])
- unless FoodsoftConfig[:charge_members_manually]
%dt= heading_helper Ordergroup, :available_funds
%dd= number_to_currency(@ordering_data[:available_funds])
.well.pull-right
= close_button :alert
= render 'switch_order', current_order: @order
.row-fluid
.well.clear
.form-search
.input-append .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
@ -64,7 +77,6 @@
%th{style: 'width:120px'}= heading_helper StockArticle, :supplier %th{style: 'width:120px'}= heading_helper StockArticle, :supplier
%th{style: "width:13px;"} %th{style: "width:13px;"}
%th{style: "width:4.5em;"}= t '.price' %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 %th{style: "width:4.5em;"}= heading_helper Article, :unit
- unless @order.stockit? - unless @order.stockit?
%th{style: "width:70px;"}= heading_helper OrderArticle, :missing_units, short: true %th{style: "width:70px;"}= heading_helper OrderArticle, :missing_units, short: true
@ -75,7 +87,7 @@
%th#col_required= heading_helper GroupOrderArticle, :quantity %th#col_required= heading_helper GroupOrderArticle, :quantity
%th{style: "width:15px;"}= heading_helper GroupOrderArticle, :total_price %th{style: "width:15px;"}= heading_helper GroupOrderArticle, :total_price
%tbody.list %tbody.list
- @order.articles_grouped_by_category.each do | category, order_articles| - @order.articles_grouped_by_category.each do |category, order_articles|
%tr.list-heading.article-category %tr.list-heading.article-category
%td %td
= category = category
@ -88,7 +100,6 @@
%td= truncate order_article.article.supplier.name, length: 15 %td= truncate order_article.article.supplier.name, length: 15
%td= h order_article.article.origin %td= h order_article.article.origin
%td= number_to_currency(@ordering_data[:order_articles][order_article.id][:price]) %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= order_article.article.unit
%td %td
- if @order.stockit? - if @order.stockit?
@ -97,16 +108,15 @@
%span{id: "missing_units_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:missing_units] %span{id: "missing_units_#{order_article.id}"}= @ordering_data[:order_articles][order_article.id][:missing_units]
%td.quantity %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?)}/ %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.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] %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 .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} %a.btn.btn-ordering{'data-increase_quantity' => order_article.id}
%i.icon-plus %i.icon-plus
%a.btn.btn-ordering{'data-decrease_quantity' => order_article.id}
%i.icon-minus
%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?)}/
@ -115,10 +125,10 @@
+ +
%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-decrease_tolerance' => order_article.id}
%i.icon-minus
%a.btn.btn-ordering{'data-increase_tolerance' => order_article.id} %a.btn.btn-ordering{'data-increase_tolerance' => order_article.id}
%i.icon-plus %i.icon-plus
%a.btn.btn-ordering{'data-decrease_tolerance' => order_article.id}
%i.icon-minus
%td{id: "td_price_#{order_article.id}", style: "text-align:right; padding-right:10px; width:4em"} %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])
@ -144,7 +154,25 @@
#order-footer #order-footer
#info-box #info-box
#total-sum #total-sum
= render '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 #order-button
= submit_tag( t('.action_save'), id: 'submit_button', class: 'btn btn-primary' ) = submit_tag( t('.action_save'), id: 'submit_button', class: 'btn btn-primary' )
#{link_to t('ui.or_cancel'), group_orders_path} #{link_to t('ui.or_cancel'), group_orders_path}

View file

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

View file

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

View file

@ -18,13 +18,10 @@
%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)
.row-fluid = render :partial => "shared/open_orders", :locals => {:ordergroup => @ordergroup}
.span9
= render :partial => "shared/open_orders", :locals => {:ordergroup => @ordergroup} // finished orders
// finished orders
- unless @finished_not_closed_orders_including_group_order.empty? - unless @finished_not_closed_orders_including_group_order.empty?
.row-fluid
.span9
%section %section
%h2= t '.finished_orders.title' %h2= t '.finished_orders.title'
= render partial: 'orders', locals: {orders: @finished_not_closed_orders_including_group_order, pagination: false} = render partial: 'orders', locals: {orders: @finished_not_closed_orders_including_group_order, pagination: false}
@ -35,8 +32,6 @@
// closed orders // closed orders
- unless @closed_orders_including_group_order.empty? - unless @closed_orders_including_group_order.empty?
.row-fluid
.span9
%section %section
%h2= t '.closed_orders.title' %h2= t '.closed_orders.title'
= render partial: 'orders', locals: {orders: @closed_orders_including_group_order, pagination: false} = render partial: 'orders', locals: {orders: @closed_orders_including_group_order, pagination: false}

View file

@ -7,52 +7,44 @@
- title t('.title', order: @order.name) - title t('.title', order: @order.name)
.row-fluid .row-fluid
.well.pull-left
.well.span2 // Order summary
= 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
// Order Ends %dt= heading_helper Order, :note
%dd= @order.note
%dt= heading_helper Order, :ends %dt= heading_helper Order, :ends
%dd= format_time(@order.ends) %dd= format_time(@order.ends)
// Pickup
- unless @order.pickup.blank?
%dt= heading_helper Order, :pickup %dt= heading_helper Order, :pickup
%dd= format_date(@order.pickup) %dd= format_date(@order.pickup)
// Min Order Quantity %dt= heading_helper GroupOrder, :price
- 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 %dd
= show_user(@group_order.updated_by) - if @group_order
(#{format_time(@group_order.updated_on)}) = number_to_currency(@group_order.price)
// Closed By - else
= t '.not_ordered'
- if @group_order && @group_order.transport
%dt= heading_helper GroupOrder, :transport
%dd= number_to_currency(@group_order.transport)
%dt= heading_helper GroupOrder, :total
%dd= number_to_currency(@group_order.total)
- if @order.closed? - 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
// Note %p= link_to t('.comment'), "#comments"
- unless @order.note.blank?
%dt= heading_helper Order, :note
%dd= @order.note
// Article box .well.pull-right
%section = close_button :alert
= render 'switch_order', current_order: @order
// Article box
%section
%h2= t '.articles.title'
.column_content#result .column_content#result
- if @group_order - if @group_order
%p= link_to t('.articles.show_hide'), '#', 'data-toggle-this' => 'tr.ignored' %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 %table.table.table-hover
%thead %thead
%tr %tr
@ -105,17 +97,17 @@
%th= number_to_currency(@group_order.total) %th= number_to_currency(@group_order.total)
%br/ %br/
= link_to_top = 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 - else
- if @order.open? - if @order.open?
= t '.articles.not_ordered_msg' = t '.articles.not_ordered_msg'
= link_to t('.articles.order_now'), action: "order", id: @order = link_to t('.articles.order_now'), action: "order", id: @order
- else - else
= t '.articles.order_closed_msg' = t '.articles.order_closed_msg'
// Comments box // Comments box
%hr %section
%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 @@
= raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name]

View file

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

View file

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

View file

@ -9,7 +9,6 @@
%thead %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'
@ -18,23 +17,21 @@
- total = 0 - total = 0
- orders.each do |order| - orders.each do |order|
%tr %tr
%td %td= link_to_ordering(order)
= 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="4") %th(colspan="3")
%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,4 +10,3 @@
- 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

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

View file

@ -90,6 +90,17 @@ de:
tolerance: Toleranz tolerance: Toleranz
total_price: Summe total_price: Summe
unit_price: Preis/Einheit unit_price: Preis/Einheit
group_order_invoice:
name: Bestellgruppenrechnung
links:
delete: Rechnung löschen
download: Rechnung herunterladen
generate: Rechnung erzeugen
invoice_date: Datum der Bestellgruppenrechnung
generate_with_date: setzen & erzeugen
payment_method: Guthaben
tax_number_not_set: Steuernummer in den Einstellungen nicht gesetzt
invoice: invoice:
amount: Betrag amount: Betrag
attachment: Anhang attachment: Anhang
@ -318,6 +329,7 @@ de:
emails_title: E-Mails versenden emails_title: E-Mails versenden
tab_payment: tab_payment:
schedule_title: Bestellschema schedule_title: Bestellschema
group_order_invoices: Bestellgruppenrechnungen
tab_security: tab_security:
default_roles_title: Zugriff auf default_roles_title: Zugriff auf
default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche. default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche.
@ -568,7 +580,6 @@ de:
options: options:
convert_units: Derzeitige Einheiten beibehalten, berechne Mengeneinheit und Preis (wie Synchronisieren). convert_units: Derzeitige Einheiten beibehalten, berechne Mengeneinheit und Preis (wie Synchronisieren).
outlist_absent: Artikel löschen, die nicht in der hochgeladenen Datei sind. outlist_absent: Artikel löschen, die nicht in der hochgeladenen Datei sind.
update_category: Kategorien aus der Datei übernehmen und erstellen.
sample: sample:
juices: Säfte juices: Säfte
nuts: Nüsse nuts: Nüsse
@ -603,6 +614,10 @@ de:
email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen. email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen.
email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest. email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest.
email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden. email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden.
group_order_invoices:
use_automativ_go_invoices: Es werden auf die Bestellgruppen zugeschnittene Rechnungen für die jeweilige Bestellung beim Klicken auf "abrechnen" an alle Bestellgruppenmitglieder per Mail versendet.
payment_method: Zahlungsart wird auf der Bestellgruppenrechnung deklariert
vat_exempt: Eine Auflistung der Rechnungsartikel erfolgt ohne explizite Ausweisung der MwSt. und die Rechnung erhält den notwendigen Zusatz bzgl. der Kleinunternehmerregelung §19 (FoodCoop Marge ebenfalls nicht in Rechnung enthalten)
help_url: Link zur Dokumentationsseite help_url: Link zur Dokumentationsseite
homepage: Webseite der Foodcoop homepage: Webseite der Foodcoop
ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat. ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat.
@ -645,6 +660,7 @@ de:
phone: Telefon phone: Telefon
street: Straße street: Straße
zip_code: Postleitzahl zip_code: Postleitzahl
tax_number: Steuernummer
currency_space: Leerzeichen hinzufügen currency_space: Leerzeichen hinzufügen
currency_unit: Währung currency_unit: Währung
custom_css: Angepasstes CSS custom_css: Angepasstes CSS
@ -659,6 +675,10 @@ de:
email_from: Absenderadresse email_from: Absenderadresse
email_replyto: Antwortadresse email_replyto: Antwortadresse
email_sender: Senderadresse email_sender: Senderadresse
group_order_invoices:
use_automatic_invoices: Automatisch bei Abrechnung per Mail versenden
payment_method: Zahlungsart
vat_exempt: Diese Foodcoop ist MwSt. befreit
help_url: URL Dokumentation help_url: URL Dokumentation
homepage: Webseite homepage: Webseite
ignore_browser_locale: Browsersprache ignorieren ignore_browser_locale: Browsersprache ignorieren
@ -744,6 +764,46 @@ de:
update: update:
notice: Lieferung wurde aktualisiert. notice: Lieferung wurde aktualisiert.
documents: documents:
group_order_invoice_pdf:
filename: Rechnung%{number}
invoicer: Rechnungsteller*in
invoicee: Rechnungsempfänger*in
invoice_date: 'Rechnungsdatum: %{invoice_date}'
invoice_number: 'Rechnungsnummer: %{invoice_number}'
markup_included: zzgl. Foodcoop Marge auf brutto Preis %{marge}%
ordergroup:
contact_phone: 'Telefonnummer: %{contact_phone}'
contact_address: 'Adresse : %{contact_address}'
name: Bestellgruppe %{ordergroup}
payment_method: 'Zahlungsart: %{payment_method}'
sum_to_pay: Zu zahlen gesamt
sum_to_pay_net: Zu zahlen gesamt (netto)
sum_to_pay_gross: Zu zahlen gesamt (brutto)
small_business_regulation: Als Kleinunternehmer*in im Sinne von §19 Abs. 1 Umsatzsteuergesetz (UStG) wird keine Umsatzsteuer berechnet.
table_headline: 'Für die Bestellung fallen folgende Posten an:'
tax_excluded: exkl. MwSt.
tax_included: zzgl. Gesamtsumme MwSt. %{tax}%
tax_number: 'Steuernummer: %{number}'
title: Rechnung für die Bestellung bei %{supplier}
vat_exempt_rows:
- Name
- Anzahl
- Einzelpreis
- Artikel Gesamtpreis
no_price_markup_rows:
- Name
- Anzahl
- Einzelpreis (netto)
- Artikel Gesamtpreis (netto)
- MwSt.
- Artikel Gesamtpreis (brutto)
price_markup_rows:
- Name
- Anzahl
- Einzelpreis (netto)
- Artikel Gesamtpreis (netto)
- MwSt.
- Artikel Gesamtpreis (brutto) inkl. Foodcoopmarge %{marge}%
order_by_articles: order_by_articles:
filename: Bestellung %{name}-%{date} - Artikelsortierung filename: Bestellung %{name}-%{date} - Artikelsortierung
title: 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}' title: 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}'
@ -767,6 +827,7 @@ de:
heading: Artikelübersicht (%{count}) heading: Artikelübersicht (%{count})
title: 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}' title: 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}'
errors: errors:
check_tax_number: Überprüft, ob die Steuernummer der Foodcoop richtig gesetzt ist
general: Ein Problem ist aufgetreten. general: Ein Problem ist aufgetreten.
general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen. general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen.
general_msg: 'Ein Fehler ist aufgetreten: %{msg}' general_msg: 'Ein Fehler ist aufgetreten: %{msg}'
@ -790,6 +851,8 @@ de:
close: close:
alert: 'Ein Fehler ist beim Abrechnen aufgetreten: %{message}' alert: 'Ein Fehler ist beim Abrechnen aufgetreten: %{message}'
notice: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. notice: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert.
notice_mail: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. Außerdem wurden automatisch Rechnungen an die Bestellgruppenmitglieder geschickt.
settings_not_set: Keine Emails mit Bestellgruppenrechnungen versendet. Bitte überprüfe die Einstellungen. Steuernummer gesetzt?
close_all_direct_with_invoice: close_all_direct_with_invoice:
notice: 'Es wurden %{count} Bestellung abgerechnet.' notice: 'Es wurden %{count} Bestellung abgerechnet.'
close_direct: close_direct:
@ -854,6 +917,7 @@ de:
ended: beendet ended: beendet
name: Lieferantin name: Lieferantin
no_closed_orders: Derzeit gibt es keine beendeten Bestellungen. no_closed_orders: Derzeit gibt es keine beendeten Bestellungen.
state: Status state: Status
summary: summary:
changed: Daten wurden verändert! changed: Daten wurden verändert!
@ -1046,33 +1110,17 @@ 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
@ -1116,6 +1164,7 @@ 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.
@ -1281,6 +1330,15 @@ de:
feedback: feedback:
header: "%{user} schrieb am %{date}:" header: "%{user} schrieb am %{date}:"
subject: Feedback zur Foodsoft subject: Feedback zur Foodsoft
group_order_invoice:
subject: Bestellgruppenrechnung für %{group} bei %{supplier}
text: |
Liebe Bestellgruppe %{group},
Die Sammelbestellung bei %{supplier} wurde soeben abgerechnet und für die jeweiligen Bestellgruppen Rechnungen angelegt.
Im Anhang befindet sich daher eure Rechnung.
Viele Grüße von %{foodcoop}
invite: invite:
subject: Einladung in die Foodcoop subject: Einladung in die Foodcoop
text: | text: |
@ -1480,9 +1538,6 @@ 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:
@ -1516,6 +1571,7 @@ de:
orders_finished: Beendet orders_finished: Beendet
orders_open: Laufend orders_open: Laufend
orders_settled: Abgerechnet orders_settled: Abgerechnet
not_closed: Bestellung noch nicht abgerechnet
title: Bestellungen verwalten title: Bestellungen verwalten
model: model:
close_direct_message: Die Bestellung wurde abgechlossen, ohne die Mitgliederkonten zu belasten. close_direct_message: Die Bestellung wurde abgechlossen, ohne die Mitgliederkonten zu belasten.

View file

@ -90,6 +90,16 @@ en:
tolerance: Tolerance tolerance: Tolerance
total_price: Sum total_price: Sum
unit_price: Price/Unit unit_price: Price/Unit
group_order_invoice:
name: Group order invoice
links:
delete: delete invoice
download: download invoice
invoice_date: date of group order invoice
generate: generate invoice
generate_with_date: set & generate
payment_method: Credit
tax_number_not_set: Tax number not set in configs
invoice: invoice:
amount: Amount amount: Amount
attachment: Attachment attachment: Attachment
@ -318,6 +328,7 @@ en:
emails_title: Sending email emails_title: Sending email
tab_payment: tab_payment:
schedule_title: Ordering schedule schedule_title: Ordering schedule
group_order_invoices: Group order invoices
tab_security: tab_security:
default_roles_title: Access to default_roles_title: Access to
default_roles_paragraph: By default every member of the foodcoop has access to the following areas. default_roles_paragraph: By default every member of the foodcoop has access to the following areas.
@ -569,7 +580,6 @@ en:
options: options:
convert_units: Keep current units, recompute unit quantity and price (like synchronize). convert_units: Keep current units, recompute unit quantity and price (like synchronize).
outlist_absent: Delete articles not in uploaded file. outlist_absent: Delete articles not in uploaded file.
update_category: Create and replace categories from uploaded file.
sample: sample:
juices: Juices juices: Juices
nuts: Nuts nuts: Nuts
@ -604,6 +614,9 @@ en:
email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address. email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address.
email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above. email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above.
email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain. email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain.
use_automatic_invoices: A listing of the invoice items is made without explicit display of VAT and the invoice receives the necessary addition regarding the small business regulation §19 (applies to Germany)
payment_method: Payment type is declared on the order group invoice
vat_exempt: A listing of the invoice items is made without explicit display of VAT and the invoice contains the necessary addition regarding the German Kleinunternehmerregelung §19 UStG (attention! FoodCoop marge not included in nvoice).
help_url: Documentation website. help_url: Documentation website.
homepage: Website of your foodcoop. homepage: Website of your foodcoop.
ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet. ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet.
@ -631,6 +644,8 @@ en:
tolerance_is_costly: Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order. tolerance_is_costly: Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order.
distribution_strategy: How articles should be distributed after an order has been received. distribution_strategy: How articles should be distributed after an order has been received.
use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering. use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering.
use_automatic_invoices: When an order is settled, invoices for the individual order groups are automatically sent by mail
payment_method: Payment Method for group order invoices
use_boxfill: When enabled, near end of an order, members are only able to change their order when increases the total amount ordered. This helps to fill any remaining boxes. You still need to set a box-fill date for the orders. use_boxfill: When enabled, near end of an order, members are only able to change their order when increases the total amount ordered. This helps to fill any remaining boxes. You still need to set a box-fill date for the orders.
use_iban: When enabled, supplier and user provide an additonal field for storing the international bank account number. use_iban: When enabled, supplier and user provide an additonal field for storing the international bank account number.
use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname. use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname.
@ -646,6 +661,7 @@ en:
phone: Phone phone: Phone
street: Street street: Street
zip_code: Postcode zip_code: Postcode
tax_number: Tax number
currency_space: add space currency_space: add space
currency_unit: Currency currency_unit: Currency
custom_css: Custom CSS custom_css: Custom CSS
@ -689,6 +705,10 @@ en:
first_order_first_serve: First distribute to those who ordered first first_order_first_serve: First distribute to those who ordered first
no_automatic_distribution: No automatic distribution no_automatic_distribution: No automatic distribution
use_apple_points: Apple points use_apple_points: Apple points
group_order_invoices:
use_automatic_invoices: Send automatically via mail after oder settlement
payment_method: Payment method
vat_exempt: This foodcoopis VAT exempt
use_boxfill: Box-fill phase use_boxfill: Box-fill phase
use_iban: Use IBAN use_iban: Use IBAN
use_nick: Use nicknames use_nick: Use nicknames
@ -746,6 +766,46 @@ en:
update: update:
notice: Delivery was updated. notice: Delivery was updated.
documents: documents:
group_order_invoice_pdf:
ordergroup:
contact_phone: 'Phone: %{contact_phone}'
contact_address: 'Adress : %{contact_address}'
name: 'Ordergroup: %{ordergroup}'
filename: Invoice%{number}
invoicee: Invoicee
invoicer: Invoicer
invoice_date: 'Invoice date: %{invoice_date}'
invoice_number: 'Invoice number: %{invoice_number}'
markup_included: incl Foodcoop Marge on gross price %{marge}%
payment_method: 'Payment_method: %{payment_method}'
small_business_regulation: As a small entrepreneur in the sense of §19 para. 1 of the Umsatzsteuergesetz (UStG), no value added tax is charged.
sum_to_pay: Total sum
sum_to_pay_net: Total sum (net)
sum_to_pay_gross: Total sum (gross)
table_headline: 'The following items will be charged for the order:'
tax_excluded: excl. MwSt.
tax_included: incl. VAT %{tax}%
tax_number: 'Tax number: %{number}'
title: Invoice for order at %{supplier}
vat_exempt_rows:
- Name
- Quantity
- Unit price
- Total price
no_price_markup_rows:
- Name
- Quantity
- Unit price (net)
- Total price (net)
- VAT
- Total price (gross)
price_markup_rows:
- Name
- Quantity
- Unit price (net)
- Total price (net)
- VAT
- Total price (gross) incl. foodcoop margin
order_by_articles: order_by_articles:
filename: Order %{name}-%{date} - by articles filename: Order %{name}-%{date} - by articles
title: 'Order sorted by articles: %{name}, closed at %{date}' title: 'Order sorted by articles: %{name}, closed at %{date}'
@ -769,6 +829,7 @@ en:
heading: Article overview (%{count}) heading: Article overview (%{count})
title: 'Order sorting matrix: %{name}, closed at %{date}' title: 'Order sorting matrix: %{name}, closed at %{date}'
errors: errors:
check_tax_number: Please check whether the foodcoop's tax number is set correctly.
general: A problem has occured. general: A problem has occured.
general_again: A problem has occured. Please try again. general_again: A problem has occured. Please try again.
general_msg: 'A problem has occured: %{msg}' general_msg: 'A problem has occured: %{msg}'
@ -792,6 +853,7 @@ en:
close: close:
alert: 'An error occured while accounting: %{message}' alert: 'An error occured while accounting: %{message}'
notice: Order was settled succesfully, the balance of the account was updated. notice: Order was settled succesfully, the balance of the account was updated.
settings_not_set: No emails with order group invoices sent. Please check the settings. Tax number set?
close_all_direct_with_invoice: close_all_direct_with_invoice:
notice: '%{count} orders have been settled.' notice: '%{count} orders have been settled.'
close_direct: close_direct:
@ -1048,33 +1110,17 @@ 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
@ -1118,6 +1164,7 @@ 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.
@ -1285,6 +1332,15 @@ en:
feedback: feedback:
header: "%{user} wrote at %{date}:" header: "%{user} wrote at %{date}:"
subject: Feedback for Foodsoft subject: Feedback for Foodsoft
group_order_invoice:
subject: Order group invoice for %{group} at %{supplier}
text: |
Dear order group %{group},
The collective order at %{supplier} has just been settled and invoices have been created for the respective order groups.
Attached you will find your invoice.
Best regards from %{foodcoop}
from_via_foodsoft: "%{name} via Foodsoft" from_via_foodsoft: "%{name} via Foodsoft"
invite: invite:
subject: Invitation to the Foodcoop subject: Invitation to the Foodcoop
@ -1490,9 +1546,6 @@ 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:
@ -1527,6 +1580,7 @@ en:
orders_finished: Closed orders_finished: Closed
orders_open: Open orders_open: Open
orders_settled: Settled orders_settled: Settled
not_closed: Order not yet settled
title: Manage orders title: Manage orders
model: model:
close_direct_message: Order settled without charging member accounts. close_direct_message: Order settled without charging member accounts.
@ -1646,7 +1700,6 @@ en:
who_ordered: Who ordered? who_ordered: Who ordered?
order_download_button: order_download_button:
article_pdf: Article PDF article_pdf: Article PDF
custom_csv: Custom CSV
download_file: Download file download_file: Download file
fax_csv: Fax CSV fax_csv: Fax CSV
fax_pdf: Fax PDF fax_pdf: Fax PDF

View file

@ -515,7 +515,6 @@ es:
options: options:
convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar). convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar).
outlist_absent: Borrar artículos que no están en el archivo subido. outlist_absent: Borrar artículos que no están en el archivo subido.
update_category: Toma las categorías del archivo subido.
sample: sample:
juices: Jugos juices: Jugos
nuts: Nueces nuts: Nueces
@ -930,7 +929,6 @@ 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
@ -1262,9 +1260,6 @@ 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,7 +678,6 @@ 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
@ -1012,9 +1011,6 @@ fr:
units_ordered: Unités commandées units_ordered: Unités commandées
create: create:
notice: La commande a bien été définie. notice: La commande a bien été définie.
custom_csv:
description: Veuillez choisir l'ordre des attributs ainsi que les attributs pour le fichier csv
column: colonne
edit: edit:
title: 'Modifier la commande: %{name}' title: 'Modifier la commande: %{name}'
edit_amount: edit_amount:

View file

@ -539,7 +539,6 @@ nl:
options: options:
convert_units: Bestaande eenheden behouden, herbereken groothandelseenheid en prijs (net als synchronizeren). convert_units: Bestaande eenheden behouden, herbereken groothandelseenheid en prijs (net als synchronizeren).
outlist_absent: Artikelen die niet in het bestand voorkomen, verwijderen. outlist_absent: Artikelen die niet in het bestand voorkomen, verwijderen.
upload_category: Categorieën overnemen uit bestand.
sample: sample:
juices: Sappen juices: Sappen
nuts: Noten nuts: Noten
@ -1018,7 +1017,6 @@ 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.
@ -1026,12 +1024,10 @@ 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
@ -1075,6 +1071,7 @@ 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.
@ -1442,9 +1439,6 @@ 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,7 +47,6 @@ 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
@ -143,6 +142,10 @@ Rails.application.routes.draw do
end end
end end
post 'finance/group_order_invoice', to: 'group_order_invoices#create_multiple'
resources :group_order_invoices
resources :article_categories resources :article_categories
########### Finance ########### Finance
@ -175,6 +178,7 @@ Rails.application.routes.draw do
get :unpaid, on: :collection get :unpaid, on: :collection
end end
resources :links, controller: 'financial_links', only: [:create, :show] do resources :links, controller: 'financial_links', only: [:create, :show] do
collection do collection do
get :incomplete get :incomplete

View file

@ -0,0 +1,13 @@
class CreateGroupOrderInvoices < ActiveRecord::Migration[5.2]
def change
create_table :group_order_invoices do |t|
t.integer :group_order_id
t.bigint :invoice_number, unique: true, limit: 8
t.date :invoice_date
t.string :payment_method
t.timestamps
end
add_index :group_order_invoices, :group_order_id, unique: true
end
end

View file

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

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do ActiveRecord::Schema[7.0].define(version: 2023_02_09_105256) do
create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.text "body", size: :long t.text "body", size: :long
@ -192,6 +192,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do
t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id" t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id"
end end
create_table "group_order_invoices", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_order_id"
t.bigint "invoice_number"
t.date "invoice_date"
t.string "payment_method"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["group_order_id"], name: "index_group_order_invoices_on_group_order_id", unique: true
end
create_table "group_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| create_table "group_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "ordergroup_id" t.integer "ordergroup_id"
t.integer "order_id", default: 0, null: false t.integer "order_id", default: 0, null: false
@ -292,6 +302,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_15_085312) do
create_table "messages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| create_table "messages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "sender_id" t.integer "sender_id"
t.string "subject", null: false t.string "subject", null: false
t.text "body"
t.boolean "private", default: false t.boolean "private", default: false
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.integer "reply_to" t.integer "reply_to"

View file

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

View file

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

View file

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

View file

@ -66,7 +66,6 @@ services:
environment: environment:
<<: *env <<: *env
FOODSOFT_SERVICE: app FOODSOFT_SERVICE: app
RAILS_SERVE_STATIC_FILES: 'true'
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"] test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 15s interval: 15s

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
require 'factory_bot'
FactoryBot.define do
factory :group_order_invoice do
group_order { create :group_order }
end
end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,71 @@
require_relative '../spec_helper'
feature GroupOrderInvoice, js: true do
let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] }
let(:user) { create :user, groups: [create(:ordergroup)] }
let(:article) { create :article, unit_quantity: 1 }
let(:order) { create :order, supplier: article.supplier, article_ids: [article.id], ends: Time.now } # need to ref article
let(:go) { create :group_order, order: order, ordergroup: user.ordergroup}
let(:oa) { order.order_articles.find_by_article_id(article.id) }
let(:ftt) { create :financial_transaction_type }
let(:goa) { create :group_order_article, group_order: go, order_article: oa }
include ActiveJob::TestHelper
before { login admin }
after { clear_enqueued_jobs }
it 'does not enqueue MailerJob when order is settled if tax_number or options not set' do
goa.update_quantities 2, 0
oa.update_results!
visit confirm_finance_order_path(id: order.id)
click_link_or_button I18n.t('finance.balancing.confirm.clear')
expect(NotifyGroupOrderInvoiceJob).not_to have_been_enqueued
end
it 'enqueues MailerJob when order is settled if tax_number or options are set' do
goa.update_quantities 2, 0
oa.update_results!
order.reload
FoodsoftConfig[:group_order_invoices] = { use_automatic_invoices: true }
FoodsoftConfig[:contact][:tax_number] = 12_345_678
visit confirm_finance_order_path(id: order.id, type: ftt)
expect(page).to have_selector(:link_or_button, I18n.t('finance.balancing.confirm.clear'))
click_link_or_button I18n.t('finance.balancing.confirm.clear')
expect(NotifyGroupOrderInvoiceJob).to have_been_enqueued
end
it 'generates Group Order Invoice when order is closed if tax_number is set' do
goa.update_quantities 2, 0
oa.update_results!
FoodsoftConfig[:contact][:tax_number] = 12_345_678
order.update!(state: 'closed')
order.reload
visit finance_order_index_path
expect(page).to have_selector(:link_or_button, I18n.t('activerecord.attributes.group_order_invoice.links.generate'))
click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate')
expect(GroupOrderInvoice.all.count).to eq(1)
end
it 'generates multiple Group Order Invoice for order when order is closed if tax_number is set' do
goa.update_quantities 2, 0
oa.update_results!
FoodsoftConfig[:contact][:tax_number] = 12_345_678
order.update!(state: 'closed')
order.reload
visit finance_order_index_path
expect(page).to have_selector(:link_or_button, I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'))
click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date')
expect(GroupOrderInvoice.all.count).to eq(1)
end
it 'does not generate Group Order Invoice when order is closed if tax_number not set' do
goa.update_quantities 2, 0
oa.update_results!
order.update!(state: 'closed')
order.reload
visit finance_order_index_path
expect(page).to have_content(I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set'))
end
end

View file

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

View file

@ -0,0 +1,59 @@
require_relative '../spec_helper'
describe GroupOrderInvoice do
let(:user) { create :user, groups: [create(:ordergroup)] }
let(:supplier) { create :supplier }
let(:article) { create :article, supplier: supplier }
let(:order) { create :order }
let(:group_order) { create :group_order, order: order, ordergroup: user.ordergroup }
describe 'erroneous group order invoice' do
let(:goi) { create :group_order_invoice, group_order_id: group_order.id }
it 'does not create group order invoice if tax_number not set' do
expect { goi }.to raise_error(ActiveRecord::RecordInvalid, /.*/)
end
end
describe 'valid group order invoice' do
before do
FoodsoftConfig[:contact][:tax_number] = 123_457_8
end
invoice_number1 = Time.now.strftime("%Y%m%d") + '0001'
invoice_number2 = Time.now.strftime("%Y%m%d") + '0002'
let(:user2) { create :user, groups: [create(:ordergroup)] }
let(:goi1) { create :group_order_invoice, group_order_id: group_order.id }
let(:goi2) { create :group_order_invoice, group_order_id: group_order.id }
let(:group_order2) { create :group_order, order: order, ordergroup: user2.ordergroup }
let(:goi3) { create :group_order_invoice, group_order_id: group_order2.id }
let(:goi4) { create :group_order_invoice, group_order_id: group_order2.id, invoice_number: invoice_number1 }
it 'creates group order invoice if tax_number is set' do
expect(goi1).to be_valid
end
it 'sets invoice_number according to date' do
number = Time.now.strftime("%Y%m%d") + '0001'
expect(goi1.invoice_number).to eq(number.to_i)
end
it 'fails to create if group_order_id is used multiple times for creation' do
expect(goi1.group_order.id).to eq(group_order.id)
expect { goi2 }.to raise_error(ActiveRecord::RecordNotUnique)
end
it 'creates two different group order invoice with different invoice_numbers' do
expect(goi1.invoice_number).to eq(invoice_number1.to_i)
expect(goi3.invoice_number).to eq(invoice_number2.to_i)
end
it 'fails to create two different group order invoice with same invoice_numbers' do
goi1
expect { goi4 }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end

View file

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