Merge branch 'rails3' into master

Conflicts:
	Gemfile
	Gemfile.lock
	Rakefile
	app/models/article_price.rb
	app/models/message.rb
	config/environment.rb
	vendor/plugins/acts_as_configurable/Rakefile
	vendor/plugins/acts_as_configurable/tasks/acts_as_configurable_tasks.rake
	vendor/plugins/acts_as_ordered/Rakefile
	vendor/plugins/acts_as_paranoid/Rakefile
	vendor/plugins/acts_as_tree/Rakefile
	vendor/plugins/acts_as_versioned/Rakefile
	vendor/plugins/auto_complete/Rakefile
	vendor/plugins/prawnto/Rakefile
	vendor/plugins/wikicloth/Rakefile
	vendor/plugins/will_paginate/Rakefile
This commit is contained in:
Manuel Wiedenmann 2013-05-21 20:36:07 +02:00
commit 15f1ade167
823 changed files with 13294 additions and 32555 deletions

11
.gitignore vendored
View File

@ -1,15 +1,18 @@
log/*.log
tmp/**/*
config/*.yml
config/initializers/secret_token.rb
db/*.sqlite3
nbproject/
config/environments/development.rb
capfile
config/environments/fcschinke09.rb
*.swp
*~
public/**/*_cached.*
config/initializers/session_store.rb
.idea
.rvmrc
.get-dump.yml
.sass-cache/
doc/app/
# Deployment tools
Capfile
config/deploy.rb
config/deploy/*

View File

@ -1 +0,0 @@
1.8.7-p357

67
Gemfile
View File

@ -1,18 +1,61 @@
# A sample Gemfile
source "http://rubygems.org"
#ruby "1.8.7"
source "https://rubygems.org"
ruby "1.9.3"
gem "rails", '2.3.17'
gem "rails", '~> 3.2.9'
gem 'mysql'
gem "fastercsv"
gem "prawn", '<=0.6.3'
gem 'haml', '>=2.0.6'
gem 'routing-filter', '0.0.1', :require => 'routing_filter'
gem 'sqlite3-ruby'
gem 'rdoc', '>= 2.4.2'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', :platforms => :ruby
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
gem 'mysql2'
gem 'prawn'
gem 'haml-rails'
gem 'kaminari'
gem 'client_side_validations'
gem 'simple_form'
gem 'inherited_resources'
gem 'localize_input', :git => "git://github.com/bennibu/localize_input.git"
gem 'wikicloth'
gem 'daemons'
gem 'twitter-bootstrap-rails'
gem 'simple-navigation'
gem 'simple-navigation-bootstrap'
gem 'meta_search'
gem 'acts_as_versioned', git: 'git://github.com/technoweenie/acts_as_versioned.git' # Use this instead of rubygem
gem 'acts_as_tree'
gem 'acts_as_configurable', git: 'git://github.com/bwalding/acts_as_configurable.git'
gem 'resque'
gem 'whenever', :require => false # For defining cronjobs, see config/schedule.rb
group :production do
gem 'exception_notification', :require => 'exception_notifier'
end
group :development do
gem 'annotate'
gem 'hirb'
gem 'sqlite3'
# Better error output
gem 'better_errors'
gem 'binding_of_caller'
# Re-enable rails benchmarker/profiler
gem 'ruby-prof'
gem 'test-unit'
# Get infos when not using proper eager loading
gem 'bullet'
# Hide assets requests in log
gem 'quiet_assets'
end

View File

@ -1,57 +1,256 @@
GEM
remote: http://rubygems.org/
GIT
remote: git://github.com/bennibu/localize_input.git
revision: 5eb188d2525a073d09e142cf8b0b04e6ace6e7b0
specs:
actionmailer (2.3.17)
actionpack (= 2.3.17)
actionpack (2.3.17)
activesupport (= 2.3.17)
rack (~> 1.1.0)
activerecord (2.3.17)
activesupport (= 2.3.17)
activeresource (2.3.17)
activesupport (= 2.3.17)
activesupport (2.3.17)
annotate (2.4.0)
fastercsv (1.5.4)
haml (3.0.25)
hirb (0.3.4)
json (1.7.6)
mysql (2.8.1)
prawn (0.6.3)
prawn-core (>= 0.6.3, < 0.7)
prawn-format (>= 0.2.3, < 0.3)
prawn-layout (>= 0.3.2, < 0.4)
prawn-security (>= 0.1.1, < 0.2)
prawn-core (0.6.3)
prawn-format (0.2.3)
prawn-core
prawn-layout (0.3.2)
prawn-security (0.1.1)
rack (1.1.6)
rails (2.3.17)
actionmailer (= 2.3.17)
actionpack (= 2.3.17)
activerecord (= 2.3.17)
activeresource (= 2.3.17)
activesupport (= 2.3.17)
rake (>= 0.8.3)
localize_input (0.1.0)
GIT
remote: git://github.com/bwalding/acts_as_configurable.git
revision: cdf6f6f979019275b523d10684b748f08e2dd8e8
specs:
acts_as_configurable (0.0.1)
rake
GIT
remote: git://github.com/technoweenie/acts_as_versioned.git
revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b
specs:
acts_as_versioned (0.6.0)
activerecord (>= 3.0.9)
GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.0.2)
actionmailer (3.2.13)
actionpack (= 3.2.13)
mail (~> 2.5.3)
actionpack (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
activerecord (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
activesupport (3.2.13)
i18n (= 0.6.1)
multi_json (~> 1.0)
acts_as_tree (1.2.0)
activerecord (>= 3.0.0)
arel (3.0.2)
better_errors (0.2.0)
coderay (>= 1.0.0)
erubis (>= 2.7.0)
binding_of_caller (0.6.8)
builder (3.0.4)
bullet (4.3.0)
uniform_notifier
chronic (0.9.0)
client_side_validations (3.1.4)
coderay (1.0.8)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.3.3)
commonjs (0.2.6)
daemons (1.1.9)
erubis (2.7.0)
exception_notification (2.6.1)
actionmailer (>= 3.0.4)
execjs (1.4.0)
multi_json (~> 1.0)
expression_parser (0.9.0)
haml (3.1.7)
haml-rails (0.3.5)
actionpack (>= 3.1, < 4.1)
activesupport (>= 3.1, < 4.1)
haml (~> 3.1)
railties (>= 3.1, < 4.1)
has_scope (0.5.1)
hashery (2.0.1)
hike (1.2.1)
i18n (0.6.1)
inherited_resources (1.3.1)
has_scope (~> 0.5.0)
responders (~> 0.6)
journey (1.0.4)
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
json (1.7.7)
kaminari (0.14.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
less (2.2.2)
commonjs (~> 0.2.6)
less-rails (2.2.3)
actionpack (>= 3.1)
less (~> 2.2.0)
libv8 (3.3.10.4)
mail (2.5.3)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
meta_search (1.1.3)
actionpack (~> 3.1)
activerecord (~> 3.1)
activesupport (~> 3.1)
polyamorous (~> 0.5.0)
mime-types (1.21)
multi_json (1.7.1)
mysql2 (0.3.11)
pdf-reader (1.2.0)
Ascii85 (~> 1.0.0)
hashery (~> 2.0)
ruby-rc4
polyamorous (0.5.0)
activerecord (~> 3.0)
polyglot (0.3.3)
prawn (0.12.0)
pdf-reader (>= 0.9.0)
ttfunk (~> 1.0.2)
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-protection (1.3.2)
rack
rack-ssl (1.3.3)
rack
rack-test (0.6.2)
rack (>= 1.0)
rails (3.2.13)
actionmailer (= 3.2.13)
actionpack (= 3.2.13)
activerecord (= 3.2.13)
activeresource (= 3.2.13)
activesupport (= 3.2.13)
bundler (~> 1.0)
railties (= 3.2.13)
railties (3.2.13)
actionpack (= 3.2.13)
activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.0.3)
rdoc (3.12)
rdoc (3.12.2)
json (~> 1.4)
routing-filter (0.0.1)
sqlite3-ruby (1.2.4)
redis (3.0.2)
redis-namespace (1.2.1)
redis (~> 3.0.0)
responders (0.9.3)
railties (~> 3.1)
resque (1.23.0)
multi_json (~> 1.0)
redis-namespace (~> 1.0)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
ruby-prof (0.11.2)
ruby-rc4 (0.1.5)
sass (3.2.1)
sass-rails (3.2.5)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
simple-navigation (3.9.0)
activesupport (>= 2.3.2)
simple-navigation-bootstrap (0.0.4)
simple-navigation (>= 3.7.0)
simple_form (2.0.3)
actionpack (~> 3.0)
activemodel (~> 3.0)
sinatra (1.3.3)
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.6)
test-unit (2.5.3)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thor (0.17.0)
tilt (1.3.6)
treetop (1.4.12)
polyglot
polyglot (>= 0.3.1)
ttfunk (1.0.3)
twitter-bootstrap-rails (2.1.3)
actionpack (>= 3.1)
less-rails (~> 2.2.3)
railties (>= 3.1)
therubyracer (~> 0.10.2)
tzinfo (0.3.37)
uglifier (1.3.0)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
uniform_notifier (1.1.1)
vegas (0.1.11)
rack (>= 1.0.0)
whenever (0.8.1)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
wikicloth (0.8.0)
builder
expression_parser
PLATFORMS
ruby
DEPENDENCIES
annotate
fastercsv
haml (>= 2.0.6)
hirb
mysql
prawn (<= 0.6.3)
rails (= 2.3.17)
rdoc (>= 2.4.2)
routing-filter (= 0.0.1)
sqlite3-ruby
acts_as_configurable!
acts_as_tree
acts_as_versioned!
better_errors
binding_of_caller
bullet
client_side_validations
coffee-rails (~> 3.2.1)
daemons
exception_notification
haml-rails
inherited_resources
jquery-rails
kaminari
localize_input!
meta_search
mysql2
prawn
quiet_assets
rails (~> 3.2.9)
resque
ruby-prof
sass-rails (~> 3.2.3)
simple-navigation
simple-navigation-bootstrap
simple_form
sqlite3
test-unit
therubyracer
twitter-bootstrap-rails
uglifier (>= 1.0.3)
whenever
wikicloth

View File

@ -1 +1,4 @@
TODO..
MULTI_COOP_INSTALL
------------------
TODO ...

View File

@ -38,16 +38,17 @@ Edit app_config.yml to suit your needs or just keep the defaults for now.
(4) Secret Token
-------------------
The user session is stored in a cookie. To avoid misusing the cookies and its sensitive information, rails will encrypt it with a token. So copy the config file
The user session are stored in cookies. Do avoid misusing the cookies and its sensitive information, rails
will encrypt it with a token. So copy the config file
cp config/initializers/session_store.rb.SAMPLE config/initializers/session_store.rb
cp config/initializers/secret_token.rb.SAMPLE config/initializers/secret_token.rb
and modify the token "config.action_controller.session"!
and modify the token!!
(5) Required ruby and gems
-------------------
We reccomend the using of rvm (https://rvm.beginrescueend.com/). Install rvm and get the lates ruby (1.8.7).
We reccomend the using of rvm (https://rvm.beginrescueend.com/). Install rvm and get the latest ruby (>= 1.9.3).
If installed you only need to install the gem bundler:
gem install bundler
@ -59,9 +60,7 @@ After that you get the other gems easily with (from project root):
(6) Create database (schema) and load defaults
--------------------------
rake db:create
rake db:schema:load
rake db:seed
rake db:setup
With this, you also get a ready to go user with username 'admin' and password 'secret'.
@ -69,5 +68,29 @@ With this, you also get a ready to go user with username 'admin' and password 's
(7) Try it out!
---------------
Start the WEBrick server to try it out:
script/server
bundle exec rails s
(8) (optional) Get background jobs done
---------------------------------------
We use for time intensive tasks a background job queue, at the moment resque with redis as key/value store.
Install redis (in ubuntu the package redis-server works out of the box) and start the resque worker with:
rake resque:work QUEUE=foodsoft_notifier
To have look on the current queue, failed jobs etc start the resque server with
resque-web
(9) (optional) View mails in browser instead in your logs
---------------------------------------------------------
We use mailcatcher in development mode to view all delivered mails in a browser interface.
Just install mailcatcher with gem install mailcatcher and start the service with
mailcatcher
From now on you have a smpt server listening on 1025. To see the emails go to
http://localhost:1080

View File

@ -1,10 +1,8 @@
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
require File.expand_path('../config/application', __FILE__)
require 'rake'
require 'rake/testtask'
#require 'rake/rdoctask'
require 'tasks/rails'
Foodsoft::Application.load_tasks

View File

Before

Width:  |  Height:  |  Size: 265 B

After

Width:  |  Height:  |  Size: 265 B

View File

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

View File

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 451 B

View File

Before

Width:  |  Height:  |  Size: 256 B

After

Width:  |  Height:  |  Size: 256 B

View File

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

View File

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View File

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 550 B

View File

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View File

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

View File

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 677 B

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
app/assets/images/rails.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 549 B

After

Width:  |  Height:  |  Size: 549 B

View File

Before

Width:  |  Height:  |  Size: 458 B

After

Width:  |  Height:  |  Size: 458 B

View File

@ -0,0 +1,118 @@
//= require jquery
//= require jquery-ui
//= require jquery_ujs
//= require twitter/bootstrap
//= require jquery.tokeninput
//= require bootstrap-datepicker
//= require bootstrap-datepicker.de
//= require jquery.observe_field
//= require rails.validations
//= require_self
//= require ordering
// Load following statements, when DOM is ready
$(function() {
// Show/Hide a specific DOM element
$('a[data-toggle-this]').live('click', function() {
$($(this).data('toggle-this')).toggle();
return false;
});
// Remove this item from DOM
$('a[data-remove-this]').live('click', function() {
$($(this).data('remove-this')).remove();
return false;
});
// Check/Uncheck a single checkbox
$('[data-check-this]').live('click', function() {
var checkbox = $($(this).data('check-this'));
checkbox.attr('checked', !checkbox.is(':checked'));
highlightRow(checkbox);
return false;
});
// Check/Uncheck all checkboxes for a specific form
$('input[data-check-all]').live('click', function() {
var status = $(this).is(':checked')
$($(this).data('check-all')).find('input[type="checkbox"]').each(function() {
$(this).attr('checked', status);
highlightRow($(this));
});
});
// Submit form when changing a select menu.
$('form[data-submit-onchange] select').live('change', function() {
var confirmMessage = $(this).children(':selected').data('confirm');
if (confirmMessage) {
if (confirm(confirmMessage)) {
$(this).parents('form').submit();
}
} else {
$(this).parents('form').submit();
}
return false;
});
// Submit form when changing text of an input field
// Use jquery observe_field plugin
$('form[data-submit-onchange] input[type=text]').each(function() {
$(this).observe_field(1, function() {
$(this).parents('form').submit();
});
});
// Submit form when clicking on checkbox
$('form[data-submit-onchange] input[type=checkbox]:not(input[data-ignore-onchange])').click(function() {
$(this).parents('form').submit();
});
$('[data-redirect-to]').bind('change', function() {
var newLocation = $(this).children(':selected').val();
if (newLocation != "") {
document.location.href = newLocation;
}
});
// Remote paginations
$('div.pagination[data-remote] a').live('click', function() {
$.getScript($(this).attr('href'));
return false;
});
// Show and hide loader on ajax callbacks
$('*[data-remote]').bind('ajax:beforeSend', function() {
$('#loader').show();
});
$('*[data-remote]').bind('ajax:complete', function() {
$('#loader').hide();
});
// Disable submit button on ajax forms
$('form[data-remote]').bind('ajax:beforeSend', function() {
$(this).children('input[type="submit"]').attr('disabled', 'disabled');
});
// Use bootstrap datepicker for dateinput
$('.datepicker').datepicker({format: 'yyyy-mm-dd', weekStart: 1, language: 'de'});
});
// gives the row an yellow background
function highlightRow(checkbox) {
var row = checkbox.parents('tr');
if (checkbox.is(':checked')) {
row.addClass('selected');
} else {
row.removeClass('selected');
}
}
// Use with auto_complete to set a unique id,
// e.g. when the user selects a (may not unique) name
// There must be a hidden field with the id 'hidden_field'
function setHiddenId(text, li) {
$('hidden_id').value = li.id;
}

View File

@ -0,0 +1,4 @@
jQuery ->
$("a[rel=popover]").popover()
$(".tooltip").tooltip()
$("a[rel=tooltip]").tooltip()

View File

@ -0,0 +1,187 @@
// JavaScript that handles the dynamic ordering quantities on the ordering page.
//
// In a JavaScript block on the actual view, define the article data by calls to setData().
// You should also set the available group balance through setGroupBalance(amount).
//
// Call setDecimalSeparator(char) to overwrite the default character "." with a localized value.
var modified = false // indicates if anything has been clicked on this page
var groupBalance = 0; // available group money
var decimalSeparator = "."; // default decimal separator
var toleranceIsCostly = true; // default tolerance behaviour
var isStockit = false; // Wheter the order is from stock oder normal supplier
// Article data arrays:
var price = new Array();
var unit = new Array(); // items per order unit
var itemTotal = new Array(); // total item price
var quantityOthers = new Array();
var toleranceOthers = new Array();
var itemsAllocated = new Array(); // how many items the group has been allocated and should definitely get
var quantityAvailable = new Array(); // stock_order. how many items are currently in stock
function setDecimalSeparator(character) {
decimalSeparator = character;
}
function setToleranceBehaviour(value) {
toleranceIsCostly = value;
}
function setStockit(value) {
isStockit = value;
}
function setGroupBalance(amount) {
groupBalance = amount;
}
function addData(orderArticleId, itemPrice, itemUnit, itemSubtotal, itemQuantityOthers, itemToleranceOthers, allocated, available) {
var i = orderArticleId;
price[i] = itemPrice;
unit[i] = itemUnit;
itemTotal[i] = itemSubtotal;
quantityOthers[i] = itemQuantityOthers;
toleranceOthers[i] = itemToleranceOthers;
itemsAllocated[i] = allocated;
quantityAvailable[i] = available;
}
function increaseQuantity(item) {
var value = Number($('#q_' + item).val()) + 1;
if (!isStockit || (value <= (quantityAvailable[item] + itemsAllocated[item]))) {
update(item, value, $('#t_' + item).val());
}
}
function decreaseQuantity(item) {
var value = Number($('#q_' + item).val()) - 1;
if (value >= 0) {
update(item, value, $('#t_' + item).val());
}
}
function increaseTolerance(item) {
var value = Number($('#t_' + item).val()) + 1;
update(item, $('#q_' + item).val(), value);
}
function decreaseTolerance(item) {
var value = Number($('#t_' + item).val()) - 1;
if (value >= 0) {
update(item, $('#q_' + item).val(), value);
}
}
function update(item, quantity, tolerance) {
// set modification flag
modified = true
// update hidden input fields
$('#q_' + item).val(quantity);
$('#t_' + item).val(tolerance);
// calculate how many units would be ordered in total
var units = calcUnits(unit[item], quantityOthers[item] + Number(quantity), toleranceOthers[item] + Number(tolerance));
if (unitCompletedFromTolerance(unit[item], quantityOthers[item] + Number(quantity), toleranceOthers[item] + Number(tolerance))) {
$('#units_' + item).html('<span style=\"color:grey\">' + String(units) + '</span>');
} else {
$('#units_' + item).html(String(units));
}
// update used/unused quantity
var available = Math.max(0, units * unit[item] - quantityOthers[item]);
var q_used = Math.min(available, quantity);
// ensure that at least the amout of items this group has already been allocated is used
if (quantity >= itemsAllocated[item] && q_used < itemsAllocated[item]) {
q_used = itemsAllocated[item];
}
$('#q_used_' + item).html(String(q_used));
$('#q_unused_' + item).html(String(quantity - q_used));
$('#q_total_' + item).html(String(Number(quantity) + quantityOthers[item]));
// update used/unused tolerance
if (unit[item] > 1) {
available = Math.max(0, available - q_used - toleranceOthers[item]);
t_used = Math.min(available, tolerance);
$('#t_used_' + item).html(String(t_used));
$('#t_unused_' + item).html(String(tolerance - t_used));
$('#t_total_' + item).html(String(Number(tolerance) + toleranceOthers[item]));
}
// update total price
if(toleranceIsCostly == true) {
itemTotal[item] = price[item] * (Number(quantity) + Number(tolerance));
} else {
itemTotal[item] = price[item] * (Number(quantity));
}
$('#price_' + item + '_display').html(asMoney(itemTotal[item]));
// update missing units
var missing_units = unit[item] - (((quantityOthers[item] + Number(quantity)) % unit[item]) + Number(tolerance) + toleranceOthers[item])
if (missing_units < 0) {
missing_units = 0;
}
$('#missing_units_' + item).html(String(missing_units));
// update balance
updateBalance();
}
function asMoney(amount) {
return String(amount.toFixed(2)).replace(/\./, ",");
}
function calcUnits(unitSize, quantity, tolerance) {
var units = Math.floor(quantity / unitSize)
var remainder = quantity % unitSize
return units + ((remainder > 0) && (remainder + tolerance >= unitSize) ? 1 : 0)
}
function unitCompletedFromTolerance(unitSize, quantity, tolerance) {
var remainder = quantity % unitSize
return (remainder > 0 && (remainder + tolerance >= unitSize));
}
function updateBalance() {
// update total price and order balance
var total = 0;
for (i in itemTotal) {
total += itemTotal[i];
}
$('#total_price').html(asMoney(total));
var balance = groupBalance - total;
$('#new_balance').html(asMoney(balance));
$('#total_balance').val(asMoney(balance));
// determine bgcolor and submit button state according to balance
var bgcolor = '';
if (balance < 0) {
bgcolor = '#FF0000';
$('#submit_button').attr('disabled', 'disabled')
} else {
$('#submit_button').removeAttr('disabled')
}
// update bgcolor
for (i in itemTotal) {
$('#td_price_' + i).css('background-color', bgcolor);
}
}
$(function() {
$('input[data-increase_quantity]').click(function() {
increaseQuantity($(this).data('increase_quantity'));
});
$('input[data-decrease_quantity]').click(function() {
decreaseQuantity($(this).data('decrease_quantity'));
});
$('input[data-increase_tolerance]').click(function() {
increaseTolerance($(this).data('increase_tolerance'));
});
$('input[data-decrease_tolerance]').click(function() {
decreaseTolerance($(this).data('decrease_tolerance'));
});
$('a[data-confirm_switch_order]').click(function() {
return (!modified || confirm('Änderungen an dieser Bestellung gehen verloren, wenn zu einer anderen Bestellung gewechselt wird. Möchtest Du trotzdem wechseln?'));
});
});

View File

@ -0,0 +1,4 @@
/*
*= require bootstrap_and_overrides
*= require token-input-bootstrappy
*/

View File

@ -0,0 +1,210 @@
@import "twitter/bootstrap/bootstrap";
body {
padding-top: 10px;
}
@import "twitter/bootstrap/responsive";
// Set the correct sprite paths
@iconSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings.png');
@iconWhiteSpritePath: asset-path('twitter/bootstrap/glyphicons-halflings-white.png');
// Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines)
// Note: If you use asset_path() here, your compiled boostrap_and_overrides.css will not
// have the proper paths. So for now we use the absolute path.
@fontAwesomeEotPath: '/assets/fontawesome-webfont.eot';
@fontAwesomeWoffPath: '/assets/fontawesome-webfont.woff';
@fontAwesomeTtfPath: '/assets/fontawesome-webfont.ttf';
@fontAwesomeSvgPath: '/assets/fontawesome-webfont.svg';
// Font Awesome
@import "fontawesome";
// Your custom LESS stylesheets goes here
//
// Since bootstrap was imported above you have access to its mixins which
// you may use and inherit here
//
// If you'd like to override bootstrap's own variables, you can do so here as well
// See http://twitter.github.com/bootstrap/less.html for their names and documentation
//
// Example:
// @linkColor: #ff0000;
// Bootstrap datepicker
@import "datepicker";
// Custom styles
// Fix empty dd tags in horizontal dl, see https://github.com/twitter/bootstrap/issues/4062
.dl-horizontal {
dd { .clearfix(); }
}
@mainRedColor: #ED0606;
.logo {
margin: 10px 0 0 30px;
float: left;
font-size: 35px;
font-weight: bold;
display: block;
color: @mainRedColor;
span {
padding: 2px 4px;
color: #ffffff;
background-color: @mainRedColor;
}
}
section {
padding-bottom: 30px;
margin-bottom: 30px;
border-bottom: 1px solid #d3d3d3;
}
footer {
margin-top: 50px;
padding-top: 20px;
border-top: 1px solid lightGrey;
}
table {
a.sortdown:after {
content: ' \25BC';
}
a.sortup:after {
content: ' \25B2';
}
tr.article-category {
background-color: #efefef;
td:first-child {
text-align: left;
}
}
th.numeric, td.numeric {
text-align: right;
}
td.odd {
background-color: @tableBackgroundAccent;
}
tr.selected td {
background-color: @successBackground;
}
}
// Tasks ..
.accepted {
color: #468847;
}
.unaccepted {
color: #B94A48;
}
// Wiki
#wikiContent {
.editsection {
display: none;
}
.mw-headline a {
color: @textColor;
text-decoration: none;
}
}
// ordering
span.used {
color: green;
}
span.unused {
color: red;
}
#order-footer, .article-info {
text-align: left;
z-index: 1;
position: fixed;
bottom: 0;
background-color: #E4EED6;
border-top: 2px solid #78B74E;
#total-sum {
width: 22em;
margin: .5em 2em 0 0;
float: right;
#order-button {
margin: .5em 0;
input:disabled {
background-color: red; }
}
}
}
#order-footer {
width: 100%;
right: 0;
left: 0;
}
.article-info {
z-index: 2;
width: 40em;
height: 8em;
border: none;
left: 30px;
.article-name {
text-align: center;
margin: 2px 0;
margin-bottom: 5px;
width: 100%;
font-weight: bold;
}
.pull-right {
width: 35%;
}
.pull-left {
width: 60%;
}
}
tr.order-article .article-info {
display: none;
}
tr.order-article:hover .article-info {
display: block;
}
// ********* Articles
tr.just-updated {
color: #468847;
}
tr.unavailable {
color: #999;
}
// articles edit all
.field_with_errors {
input, select {
border-color: red;
}
}
// ********* Tweaks & fixes
// need more space for supplier&order information (in German, at least)
.dl-horizontal {
dt { width: 160px; }
dd { margin-left: 170px; }
}

View File

@ -0,0 +1,147 @@
/*!
* Datepicker for Bootstrap
*
* Copyright 2012 Stefan Petre
* Improvements by Andrew Rowls
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.datepicker {
top: 0;
left: 0;
padding: 4px;
margin-top: 1px;
.border-radius(4px);
&:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-bottom-color: rgba(0,0,0,.2);
position: absolute;
top: -7px;
left: 6px;
}
&:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid @white;
position: absolute;
top: -6px;
left: 7px;
}
>div {
display: none;
}
&.days div.datepicker-days {
display: block;
}
&.months div.datepicker-months {
display: block;
}
&.years div.datepicker-years {
display: block;
}
table{
margin: 0;
}
td,
th{
text-align: center;
width: 20px;
height: 20px;
.border-radius(4px);
}
td {
&.day:hover {
background: @grayLighter;
cursor: pointer;
}
&.old,
&.new {
color: @grayLight;
}
&.disabled,
&.disabled:hover {
background: none;
color: @grayLight;
cursor: default;
}
&.today,
&.today:hover,
&.today.disabled,
&.today.disabled:hover {
@todayBackground: lighten(@orange, 30%);
.buttonBackground(@todayBackground, spin(@todayBackground, 20));
}
&.active,
&.active:hover,
&.active.disabled,
&.active.disabled:hover {
.buttonBackground(@btnPrimaryBackground, spin(@btnPrimaryBackground, 20));
color: #fff;
text-shadow: 0 -1px 0 rgba(0,0,0,.25);
}
span {
display: block;
width: 23%;
height: 54px;
line-height: 54px;
float: left;
margin: 1%;
cursor: pointer;
.border-radius(4px);
&:hover {
background: @grayLighter;
}
&.disabled,
&.disabled:hover {
background:none;
color: @grayLight;
cursor: default;
}
&.active,
&.active:hover,
&.active.disabled,
&.active.disabled:hover {
.buttonBackground(@btnPrimaryBackground, spin(@btnPrimaryBackground, 20));
color: #fff;
text-shadow: 0 -1px 0 rgba(0,0,0,.25);
}
&.old {
color: @grayLight;
}
}
}
th.switch {
width: 145px;
}
thead tr:first-child th,
tfoot tr:first-child th {
cursor: pointer;
&:hover{
background: @grayLighter;
}
}
/*.dow {
border-top: 1px solid #ddd !important;
}*/
}
.input-append,
.input-prepend {
&.date {
.add-on i {
display: block;
cursor: pointer;
width: 16px;
height: 16px;
}
}
}

View File

@ -1,12 +1,12 @@
// colors which are used in the foodsoft
!main_red = #ED0606
!hover_yellow = #ffff72
!boxContent = #e4eed6
!lightGrey = #efefef
!darkGreen = #78b74e
!lightGreen = #e4eed6
$main_red: #ED0606
$hover_yellow: #ffff72
$boxContent: #e4eed6
$lightGrey: #efefef
$darkGreen: #78b74e
$lightGreen: #e4eed6
/* General rules ...
// General rules ...
body
:background-color #fff
:color black
@ -26,27 +26,27 @@ body
:border
:width 2px
:style solid
:color = !main_red
color: $main_red
a, a:visited
:text-decoration underline
:color black
a:hover
:color = !main_red
color: $main_red
h1, h2
:color = !main_red
color: $main_red
h1
:font-size 2.2em
:line-height 0.8em
:padding 1em 0 5px 5%
:margin 0 0 1em 0
:border-bottom
:border-bottom
:width 1px
:style dotted
:color = !main_red
:style dotted
color: $main_red
h2
:font-size 1.4em
@ -88,7 +88,7 @@ option
border-top: 1px solid #D7D7D7
margin: .2em 0
span.click-me
.click-me
cursor: pointer
.left
float: left
@ -104,10 +104,15 @@ span.click-me
.hidden
display: none
.warning
background: yellow
font-weight: bold
padding: 1px 10px
// ********************************* loginpage
#login
:margin auto
:width 27em
:width 35em
:font-size 1.2em
#login #meta
@ -124,7 +129,7 @@ span.click-me
#logo
:background = !main_red
background: $main_red
:height 1.1em
:width 8em
:padding 0 20px
@ -136,17 +141,17 @@ span.click-me
:margin 0
a, a:hover
:color white
:background-color = !main_red
background-color: $main_red
:text-decoration none
a span
:color = !main_red
color: $main_red
:background #FFF
:padding-right 0.1em
:font-weight bold
:border-top
:width 2px
:style dotted
:color = !main_red
color: $main_red
#logininfo
:position absolute
@ -162,7 +167,7 @@ span.click-me
:color #737272
:font-weight bold
a:hover
:color = !main_red
color: $main_red
// ************************************* box structure
#main
@ -268,8 +273,6 @@ table
:color #008000
tr.selected, tr.active
:background-color #ffffc2
tr.click-me
:cursor pointer
tr.ignored
color: grey
tr.success
@ -290,7 +293,7 @@ table tfoot tr
:padding-top 0.8em
tr.edit_inline
:background-color = !hover_yellow
background-color: $hover_yellow
td, span
:padding 0.5em 0.2em
@ -318,15 +321,15 @@ td.currency, td.actions
:text-align right
:padding-right 0.5em
td.closed
background: url(/images/arrow_right_red.png) no-repeat center left
background: image-url('arrow_right_red.png') no-repeat center left
a
display: block
text-decoration: none
padding-left: 20px
td.open
background: url(/images/arrow_down_red.png) no-repeat center left
background: image-url('arrow_down_red.png') no-repeat center left
// ************************************* for edit formulars */
// ************************************* for edit formulars
div.edit_form
:border 2px solid #e3e3e3
:background #f5f5f5
@ -345,7 +348,7 @@ div.edit_form
:border
:width 3px
:style solid
:color = !main_red
color: $main_red
// ***************************************** other boxes */
@ -384,7 +387,7 @@ div.box_title
:font-size 1.3em
div.column_content
:background = !boxContent
background: $boxContent
:color black
:padding 10px
margin-bottom: 2em
@ -411,7 +414,7 @@ li.check div.spinner
:display block
:height 5px
:width 21px
:background-image url(/images/dots-white.gif)
:background-image image-url('dots-white.gif')
:line-height 16px
:float left
:margin-right 5px
@ -439,7 +442,7 @@ table#order
-webkit-border-radius: 3px
padding: 0
th#col_required, th#col_tolerance
:width 140px
:width 145px
th#col_packages, th#col_left_units
:width 50px
td.quantity, td.tolerance
@ -453,7 +456,7 @@ table#order
:padding-left 10px
tfoot
tr
:background-color = !lightGreen
background-color: $lightGreen
td
:padding-right 10px
#order-footer, .article-info
@ -500,6 +503,7 @@ tr.order-article .article-info
display: none
tr.order-article:hover .article-info
display: block
// ********* Comments
#newComment
:margin 1em
@ -555,6 +559,7 @@ ul.autocomplete
background-color: #fff
text-align: center
margin: 0 10px 10px 0
// *** wiki
#wiki_content
border-style: none
@ -600,7 +605,8 @@ ul.autocomplete
margin: 0.3em 0 0 3.2em
padding: 0
list-style-image: none
li margin-bottom: 0.1em
li
margin-bottom: 0.1em
a.new_wiki_link
color: grey
@ -612,7 +618,7 @@ a.new_wiki_link
margin-bottom: 2em
width: 25em
border: 1px solid grey
:background-color= !lightGrey
background-color: $lightGrey
h2
font-size: 1em
color: black
@ -626,14 +632,13 @@ a.new_wiki_link
height: 1em
color: #ED0606
a
:color = !main_red
:text-decoration = none
color: $main_red
text-decoration: none
a:hover
:text-decoration = underline
text-decoration: underline
#sidebar
float: right
width: 290px
margin-top: -60px
#sidebar-links
margin-bottom: 18px
text-align: right
@ -653,4 +658,4 @@ a.new_wiki_link
.wiki_version
#sidebar
margin-top: -23px
border: 1px solid #78b74e
border: 1px solid #78b74e

View File

@ -294,14 +294,14 @@ td.currency, td.actions {
padding-right: 0.5em; }
td.closed {
background: url(/images/arrow_right_red.png) no-repeat center left; }
background: image-url('arrow_right_red.png') no-repeat center left; }
td.closed a {
display: block;
text-decoration: none;
padding-left: 20px; }
td.open {
background: url(/images/arrow_down_red.png) no-repeat center left; }
background: image-url('arrow_down_red.png') no-repeat center left; }
div.edit_form {
border: 2px solid #e3e3e3;
@ -371,7 +371,7 @@ li.check div.spinner {
display: block;
height: 5px;
width: 21px;
background-image: url(/images/dots-white.gif);
background-image: image-url('dots-white.gif');
line-height: 16px;
float: left;
margin-right: 5px;
@ -516,25 +516,24 @@ ul.autocomplete .informal {
text-align: center;
margin: 0 10px 10px 0; }
#wiki_content {
border-style: none;
color: black;
line-height: 1.5em; }
.wiki_show, .wiki_version, .wiki_new, .wiki_edit, .wiki_all {
margin-top: 30px;
padding: 10px; }
.wiki_show h1, .wiki_version h1, .wiki_new h1, .wiki_edit h1, .wiki_all h1 {
padding-left: 0;
padding-top: 10px;
border-bottom-style: solid; }
.wiki_show .column_content, .wiki_version .column_content, .wiki_new .column_content, .wiki_edit .column_content, .wiki_all .column_content {
margin-bottom: 0; }
#wiki_content {
border: 1px solid grey;
margin-right: 300px;
padding: 10px;
color: black;
line-height: 1.5em;
min-height: 400px; }
#wiki_content span.editsection {
display: none; }
#wiki_content h1 {
padding-left: 0;
padding-top: 10px;
border: none;
margin-bottom: 10px; }
#wiki_content h2, #wiki_content h3, #wiki_content h4, #wiki_content h5, #wiki_content h6 {
background: transparent none repeat scroll 0 0;
border-bottom: 1px solid #AAAAAA;
@ -593,8 +592,7 @@ a.new_wiki_link {
#sidebar {
float: right;
width: 290px;
margin-top: -60px; }
width: 290px; }
#sidebar #sidebar-links {
margin-bottom: 18px;
text-align: right; }

View File

@ -0,0 +1,133 @@
/* Example tokeninput style #2: Facebook style */
ul.token-input-list-facebook {
overflow: hidden;
height: auto !important;
height: 1%;
width: 400px;
border: 1px solid #BBB;
cursor: text;
font-size: 12px;
font-family: Verdana, sans-serif;
min-height: 1px;
z-index: 999;
margin: 0;
padding: 0;
background-color: #fff;
list-style-type: none;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
ul.token-input-list-facebook.token-input-focused-facebook {
border-color: rgba(82, 168, 236, 0.8);
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
outline: 0;
outline: thin dotted 9;
}
ul.token-input-list-facebook li input {
border: 0;
width: 100px;
padding: 3px 8px;
background-color: white;
margin: 2px 0;
-webkit-appearance: caret;
}
li.token-input-token-facebook {
overflow: hidden;
height: auto !important;
height: 15px;
margin: 3px;
padding: 1px 3px;
background-color: #F5F5F5;
color: #555;
cursor: default;
border: 1px solid #BBB;
font-size: 11px;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
float: left;
white-space: nowrap;
}
li.token-input-token-facebook p {
display: inline;
padding: 0;
margin: 0;
}
li.token-input-token-facebook span {
color: #a6b3cf;
margin-left: 5px;
font-weight: bold;
cursor: pointer;
}
li.token-input-selected-token-facebook {
background-color: #5670a6;
border: 1px solid #3b5998;
color: #fff;
}
li.token-input-input-token-facebook {
float: left;
margin: 0;
padding: 0;
list-style-type: none;
}
div.token-input-dropdown-facebook {
position: absolute;
width: 400px;
background-color: #F5F5F5;
overflow: hidden;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
cursor: default;
font-size: 12px;
font-family: Verdana, sans-serif;
z-index: 1;
}
div.token-input-dropdown-facebook p {
margin: 0;
padding: 5px;
font-weight: bold;
color: #555;
}
div.token-input-dropdown-facebook ul {
margin: 0;
padding: 0;
}
div.token-input-dropdown-facebook ul li {
background-color: #f5f5f5;
padding: 3px;
margin: 0;
list-style-type: none;
}
div.token-input-dropdown-facebook ul li.token-input-dropdown-item-facebook {
background-color: #f5f5f5;
}
div.token-input-dropdown-facebook ul li.token-input-dropdown-item2-facebook {
background-color: #f5f5f5;
}
div.token-input-dropdown-facebook ul li em {
font-weight: bold;
font-style: normal;
}
div.token-input-dropdown-facebook ul li.token-input-selected-dropdown-item-facebook {
background-color: #08C;
color: #f5f5f5;
}

View File

@ -1,69 +1,23 @@
# encoding: utf-8
class Admin::OrdergroupsController < Admin::BaseController
inherit_resources
def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
@per_page = params[:per_page].to_i
else
@per_page = 20
@ordergroups = Ordergroup.undeleted.order('name ASC')
# if somebody uses the search field:
unless params[:query].blank?
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%")
end
# if the search field is used
conditions = "name LIKE '%#{params[:query]}%'" unless params[:query].nil?
@total = Ordergroup.without_deleted.count(:conditions => conditions )
@ordergroups = Ordergroup.without_deleted.paginate(:conditions => conditions, :page => params[:page],
:per_page => @per_page, :order => 'name')
respond_to do |format|
format.html # index.html.erb
format.js { render :partial => "ordergroups" }
end
end
def show
@ordergroup = Ordergroup.find(params[:id])
end
def new
@ordergroup = Ordergroup.new
end
def edit
@ordergroup = Ordergroup.find(params[:id])
end
def create
@ordergroup = Ordergroup.new(params[:ordergroup])
@ordergroup.account_updated = Time.now
if @ordergroup.save
flash[:notice] = 'Ordergroup was successfully created.'
redirect_to([:admin, @ordergroup])
else
render :action => "new"
end
end
def update
@ordergroup = Ordergroup.find(params[:id])
if @ordergroup.update_attributes(params[:ordergroup])
flash[:notice] = 'Ordergroup was successfully updated.'
redirect_to([:admin, @ordergroup])
else
render :action => "edit"
end
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
end
def destroy
@ordergroup = Ordergroup.find(params[:id])
@ordergroup.destroy
redirect_to(admin_ordergroups_url)
end
def memberships
@group = Ordergroup.find(params[:id])
@ordergroup.mark_as_deleted
redirect_to admin_ordergroups_url, notice: t('admin.ordergroups.destroy.notice')
rescue => error
redirect_to admin_ordergroups_url, alert: t('admin.ordergroups.destroy.error')
end
end

View File

@ -1,69 +1,15 @@
class Admin::UsersController < Admin::BaseController
inherit_resources
def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
@per_page = params[:per_page].to_i
else
@per_page = 20
@users = User.order('nick ASC')
# if somebody uses the search field:
unless params[:user_name].blank?
@users = @users.where("first_name LIKE :user_name OR last_name LIKE :user_name OR nick LIKE :user_name",
user_name: "%#{params[:user_name]}%")
end
# if the search field is used
conditions = "first_name LIKE '%#{params[:query]}%' OR last_name LIKE '%#{params[:query]}%'" unless params[:query].nil?
@total = User.count(:conditions => conditions)
@users = User.paginate :page => params[:page], :conditions => conditions, :per_page => @per_page, :order => 'nick'
respond_to do |format|
format.html # listUsers.haml
format.js do
render :update do |page|
page.replace_html 'table', :partial => "users"
end
end
end
@users = @users.page(params[:page]).per(@per_page)
end
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
flash[:notice] = 'Benutzerin wurde erfolgreich angelegt.'
redirect_to admin_users_path
else
render :action => 'new'
end
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user])
flash[:notice] = 'Änderungen wurden gespeichert.'
redirect_to [:admin, @user]
else
render :action => 'edit'
end
end
def destroy
user = User.find(params[:id])
if user.nick == @current_user.nick
# deny destroying logged-in-user
flash[:error] = 'Du darfst Dich nicht selbst löschen.'
else
user.destroy
flash[:notice] = 'Benutzer_in wurde gelöscht.'
end
redirect_to admin_users_path
end
end

View File

@ -1,68 +1,20 @@
# encoding: utf-8
class Admin::WorkgroupsController < Admin::BaseController
inherit_resources
def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
@per_page = params[:per_page].to_i
else
@per_page = 20
end
@workgroups = Workgroup.order('name ASC')
# if somebody uses the search field:
@workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].blank?
# if the search field is used
conditions = "name LIKE '%#{params[:query]}%'" unless params[:query].nil?
@total = Ordergroup.count(:conditions => conditions )
@workgroups = Workgroup.paginate(:conditions => conditions, :page => params[:page],
:per_page => @per_page, :order => 'name')
respond_to do |format|
format.html # index.html.erb
format.js { render :partial => "workgroups" }
end
end
def show
@workgroup = Workgroup.find(params[:id])
end
def new
@workgroup = Workgroup.new
end
def edit
@workgroup = Workgroup.find(params[:id])
end
def create
@workgroup = Workgroup.new(params[:workgroup])
if @workgroup.save
flash[:notice] = 'Workgroup was successfully created.'
redirect_to([:admin, @workgroup])
else
render :action => "new"
end
end
def update
@workgroup = Workgroup.find(params[:id])
if @workgroup.update_attributes(params[:workgroup])
flash[:notice] = 'Workgroup was successfully updated.'
redirect_to([:admin, @workgroup])
else
render :action => "edit"
end
@workgroups = @workgroups.page(params[:page]).per(@per_page)
end
def destroy
@workgroup = Workgroup.find(params[:id])
@workgroup.destroy
redirect_to(admin_workgroups_url)
end
def memberships
@group = Workgroup.find(params[:id])
redirect_to admin_workgroups_url, notice: t('admin.ordergroups.destroy.notice')
rescue => error
redirect_to admin_workgroups_url, alert: t('admin.ordergroups.destroy.error')
end
end

View File

@ -1,84 +1,54 @@
# encoding: utf-8
class ApplicationController < ActionController::Base
filter_parameter_logging :password, :password_confirmation # do not log passwort parameters
before_filter :select_foodcoop, :authenticate, :store_controller
protect_from_forgery
before_filter :select_language, :select_foodcoop, :authenticate, :store_controller, :items_per_page, :set_redirect_to
after_filter :remove_controller
# sends a mail, when an error occurs
# see plugins/exception_notification
include ExceptionNotifiable
# Returns the controller handling the current request.
def self.current
Thread.current[:application_controller]
end
# Use this method to call a rake task,,
# e.g. to deliver mails after there are created.
def call_rake(task, options = {})
options[:rails_env] ||= Foodsoft.env
args = options.map { |n, v| "#{n.to_s.upcase}='#{v}'" }
system "/usr/bin/rake #{task} #{args.join(' ')} --trace 2>&1 >> #{Rails.root}/log/rake.log &"
end
protected
def current_user
begin
# check if there is a valid session and return the logged-in user (its object)
if session[:user] and session[:foodcoop]
# for shared-host installations. check if the cookie-subdomain fits to request.
return User.current_user = User.find(session[:user]) if session[:foodcoop] == Foodsoft.env
end
rescue
reset_session
flash[:error]= _("An error has occurred. Please login again.")
redirect_to :controller => 'login'
# check if there is a valid session and return the logged-in user (its object)
if session[:user_id] and params[:foodcoop]
# for shared-host installations. check if the cookie-subdomain fits to request.
@current_user ||= User.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
end
end
helper_method :current_user
def current_user=(user)
session[:user], session[:foodcoop] = user.id, Foodsoft.env
end
def return_to
session['return_to']
end
def return_to=(uri)
session['return_to'] = uri
end
def deny_access
self.return_to = request.request_uri
redirect_to :controller => '/login', :action => 'denied'
return false
session[:return_to] = request.original_url
redirect_to login_url, :alert => 'Access denied!'
end
private
def authenticate(role = 'any')
# Attempt to retrieve authenticated user from controller instance or session...
if !(user = current_user)
if !current_user
# No user at all: redirect to login page.
self.return_to = request.request_uri
redirect_to :controller => '/login'
return false
session[:user_id] = nil
session[:return_to] = request.original_url
redirect_to login_url, :alert => 'Authentication required!'
else
# We have an authenticated user, now check role...
# Roles gets the user through his memberships.
hasRole = case role
when "admin" then user.role_admin?
when "finance" then user.role_finance?
when "article_meta" then user.role_article_meta?
when "suppliers" then user.role_suppliers?
when "orders" then user.role_orders?
when "admin" then current_user.role_admin?
when "finance" then current_user.role_finance?
when "article_meta" then current_user.role_article_meta?
when "suppliers" then current_user.role_suppliers?
when "orders" then current_user.role_orders?
when "any" then true # no role required
else false # any unknown role will always fail
end
if hasRole
@current_user = user
current_user
else
deny_access
end
@ -110,12 +80,7 @@ class ApplicationController < ActionController::Base
def authenticate_membership_or_admin
@group = Group.find(params[:id])
unless @group.member?(@current_user) or @current_user.role_admin?
flash[:error] = "Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!"
if request.xml_http_request?
render(:update) {|page| page.redirect_to root_path }
else
redirect_to root_path
end
redirect_to root_path, alert: "Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!"
end
end
@ -138,23 +103,47 @@ class ApplicationController < ActionController::Base
# It uses the subdomain to select the appropriate section in the config files
# Use this method as a before filter (first filter!) in ApplicationController
def select_foodcoop
if Foodsoft.config[:multi_coop_install]
if !params[:foodcoop].blank?
if FoodsoftConfig[:multi_coop_install]
if params[:foodcoop].present?
begin
# Set Config
Foodsoft.env = params[:foodcoop]
# Set database-connection
ActiveRecord::Base.establish_connection(Foodsoft.database)
# Set Config and database connection
FoodsoftConfig.select_foodcoop params[:foodcoop]
rescue => error
flash[:error] = error.to_s
redirect_to root_path
redirect_to root_url, alert: error.message
end
else
redirect_to root_path
redirect_to root_url
end
else
# Deactivate routing filter
RoutingFilter::Foodcoop.active = false
end
end
def items_per_page
if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100
@per_page = params[:per_page].to_i
else
@per_page = 20
end
end
def set_redirect_to
session[:redirect_to] = params[:redirect_to] if params[:redirect_to]
end
def back_or_default_path(default = root_path)
if session[:redirect_to].present?
default = session[:redirect_to]
session[:redirect_to] = nil
end
default
end
# Always stay in foodcoop url scope
def default_url_options(options = {})
{foodcoop: FoodsoftConfig.scope}
end
# Used to prevent accidently switching to :en in production mode.
def select_language
I18n.locale = :de
end
end

View File

@ -1,71 +1,27 @@
class ArticleCategoriesController < ApplicationController
inherit_resources # Build default REST Actions via plugin
before_filter :authenticate_article_meta
def index
@article_categories = ArticleCategory.all :order => 'name'
end
def new
@article_category = ArticleCategory.new
render :update do |page|
page['category_form'].replace_html :partial => 'article_categories/form'
page['category_form'].show
end
end
def edit
@article_category = ArticleCategory.find(params[:id])
render :update do |page|
page['category_form'].replace_html :partial => 'article_categories/form'
page['category_form'].show
end
end
def create
@article_category = ArticleCategory.new(params[:article_category])
if @article_category.save
render :update do |page|
page['category_form'].hide
page['category_list'].replace_html :partial => 'article_categories/list'
page['category_'+@article_category.id.to_s].visual_effect(:highlight,
:duration => 2)
end
else
render :update do |page|
page['category_form'].replace_html :partial => 'article_categories/form'
end
end
create!(:notice => I18n.t('article_categories.create.notice')) { article_categories_path }
end
def update
@article_category = ArticleCategory.find(params[:id])
if @article_category.update_attributes(params[:article_category])
render :update do |page|
page['category_form'].hide
page['category_list'].replace_html :partial => 'article_categories/list'
page['category_'+@article_category.id.to_s].visual_effect(:highlight,
:duration => 2)
end
else
render :update do |page|
page['category_form'].replace_html :partial => 'article_categories/form'
end
end
update!(:notice => I18n.t('article_categories.update.notice')) { article_categories_path }
end
def destroy
@article_category = ArticleCategory.find(params[:id])
@article_category.destroy
if @article_category.destroy
render :update do |page|
page['category_'+@article_category.id.to_s].visual_effect :drop_out
end
end
destroy!
rescue => error
redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: error.message)
end
protected
def collection
@article_categories = ArticleCategory.order('name')
end
end

View File

@ -1,92 +1,53 @@
# encoding: utf-8
class ArticlesController < ApplicationController
before_filter :authenticate_article_meta, :find_supplier
def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500)
@per_page = params[:per_page].to_i
else
@per_page = 30
end
if params['sort']
sort = case params['sort']
when "name" then "articles.name"
when "unit" then "articles.unit"
when "category" then "article_categories.name"
when "note" then "articles.note"
when "availability" then "articles.availability"
when "name_reverse" then "articles.name DESC"
when "unit_reverse" then "articles.unit DESC"
when "category_reverse" then "article_categories.name DESC"
when "note_reverse" then "articles.note DESC"
when "availability_reverse" then "articles.availability DESC"
end
sort = case params['sort']
when "name" then "articles.name"
when "unit" then "articles.unit"
when "category" then "article_categories.name"
when "note" then "articles.note"
when "availability" then "articles.availability"
when "name_reverse" then "articles.name DESC"
when "unit_reverse" then "articles.unit DESC"
when "category_reverse" then "article_categories.name DESC"
when "note_reverse" then "articles.note DESC"
when "availability_reverse" then "articles.availability DESC"
end
else
sort = "article_categories.name, articles.name"
end
# if somebody uses the search field:
conditions = ["articles.name LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?
@articles = Article.undeleted.where(supplier_id: @supplier, :type => nil).includes(:article_category).order(sort)
@articles = @articles.where('articles.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil?
@total = @supplier.articles.without_deleted.count(:conditions => conditions)
@articles = @supplier.articles.without_deleted.paginate(
:order => sort,
:conditions => conditions,
:page => params[:page],
:per_page => @per_page,
:include => :article_category
)
@articles = @articles.page(params[:page]).per(@per_page)
respond_to do |format|
format.html # list.haml
format.js do
render :update do |page|
page.replace_html 'table', :partial => "articles"
end
end
format.html
format.js { render :layout => false }
end
end
def new
@article = @supplier.articles.build(:tax => 7.0)
render :update do |page|
page["edit_article"].replace_html :partial => 'new'
page["edit_article"].show
end
render :layout => false
end
def create
@article = Article.new(params[:article])
if @article.valid? and @article.save
render :update do |page|
page.Element.hide('edit_article')
page.insert_html :top, 'listbody', :partial => 'new_article_row'
page[@article.id.to_s].visual_effect(:highlight,
:duration => 2)
# highlights article
if !@article.availability
page[@article.id.to_s].addClassName 'unavailable'
else
page[@article.id.to_s].addClassName 'just_updated'
end
end
render :layout => false
else
render :update do |page|
page.replace_html 'edit_article', :partial => "new"
end
end
render :action => 'new', :layout => false
end
end
# edit an article and its price
def edit
@article = Article.find(params[:id])
render :update do |page|
page["edit_article"].replace_html :partial => 'edit'
page["edit_article"].show
end
#render :partial => "quick_edit", :layout => false
render :action => 'new', :layout => false
end
# Updates one Article and highlights the line if succeded
@ -94,112 +55,79 @@ class ArticlesController < ApplicationController
@article = Article.find(params[:id])
if @article.update_attributes(params[:article])
render :update do |page|
page["edit_article"].hide
page[@article.id.to_s].replace_html :partial => 'article_row'
# hilights an updated article if the article ist available
page[@article.id.to_s].addClassName 'just_updated' if @article.availability
# highlights an available article and de-highlights in other case
if !@article.availability
page[@article.id.to_s].addClassName 'unavailable'
# remove updated-class
page[@article.id.to_s].removeClassName 'just_updated'
else
page[@article.id.to_s].removeClassName 'unavailable'
end
page[@article.id.to_s].visual_effect(:highlight, :delay => 0.5, :duration => 2)
end
render :layout => false
else
render :update do |page|
page["edit_article"].replace_html :partial => "edit"
end
render :action => 'new', :layout => false
end
end
# Deletes article from database. send error msg, if article is used in a current order
def destroy
@article = Article.find(params[:id])
@order = @article.in_open_order # If article is in an active Order, the Order will be returned
if @order
render :update do |page|
page.insert_html :after, @article.id.to_s, :partial => 'destroyActiveArticle'
end
else
@article.destroy
render :update do |page|
page[@article.id.to_s].remove
end
end
@article.mark_as_deleted unless @order = @article.in_open_order # If article is in an active Order, the Order will be returned
render :layout => false
end
# Renders a form for editing all articles from a supplier
def edit_all
@articles = @supplier.articles.without_deleted
@articles = @supplier.articles.undeleted
end
# Updates all article of specific supplier
# deletes all articles from params[outlisted_articles]
def update_all
currentArticle = nil # used to find out which article caused a validation exception
invalid_articles = false
begin
Article.transaction do
unless params[:articles].blank?
# Update other article attributes...
for id, attributes in params[:articles]
currentArticle = Article.find(id)
currentArticle.update_attributes!(attributes)
@articles = Article.find(params[:articles].keys)
@articles.each do |article|
unless article.update_attributes(params[:articles][article.id.to_s])
invalid_articles = true unless invalid_articles # Remember that there are validation errors
end
end
end
# delete articles
if params[:outlisted_articles]
params[:outlisted_articles].keys.each {|id| Article.find(id).destroy }
end
end
# Successfully done.
flash[:notice] = 'Alle Artikel und Preise wurden aktalisiert'
redirect_to supplier_articles_path(@supplier)
rescue => e
# An error has occurred, transaction has been rolled back.
if currentArticle
@failedArticle = currentArticle
flash[:error] = "Es trat ein Fehler beim Aktualisieren des Artikels '#{currentArticle.name}' auf: #{e.message}"
params[:sync] ? redirect_to(supplier_articles_path(@supplier)) : render(:action => 'edit_all')
else
flash[:error] = "Es trat ein Fehler beim Aktualisieren der Artikel auf: #{e.message}"
redirect_to supplier_articles_path(@supplier)
raise ActiveRecord::Rollback if invalid_articles # Rollback all changes
end
end
end
if invalid_articles
# An error has occurred, transaction has been rolled back.
flash.now.alert = I18n.t('articles.controller.error_invalid')
render :edit_all
else
# Successfully done.
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_all.notice')
end
end
# makes different actions on selected articles
def update_selected
raise 'Du hast keine Artikel ausgewählt' if params[:selected_articles].nil?
raise I18n.t('articles.controller.error_nosel') if params[:selected_articles].nil?
articles = Article.find(params[:selected_articles])
case params[:selected_action]
when 'destroy'
articles.each {|a| a.destroy }
flash[:notice] = 'Alle gewählten Artikel wurden gelöscht'
when 'setNotAvailable'
articles.each {|a| a.update_attribute(:availability, false) }
flash[:notice] = 'Alle gewählten Artikel wurden auf "nicht verfügbar" gesetzt'
when 'setAvailable'
articles.each {|a| a.update_attribute(:availability, true) }
flash[:notice] = 'Alle gewählten Artikel wurden auf "verfügbar" gesetzt'
else
flash[:error] = 'Keine Aktion ausgewählt!'
Article.transaction do
case params[:selected_action]
when 'destroy'
articles.each(&:mark_as_deleted)
flash[:notice] = I18n.t('articles.controller.update_sel.notice_destroy')
when 'setNotAvailable'
articles.each {|a| a.update_attribute(:availability, false) }
flash[:notice] = I18n.t('articles.controller.update_sel.notice_unavail')
when 'setAvailable'
articles.each {|a| a.update_attribute(:availability, true) }
flash[:notice] = I18n.t('articles.controller.update_sel.notice_avail')
else
flash[:alert] = I18n.t('articles.controller.update_sel.notice_noaction')
end
end
# action succeded
redirect_to supplier_articles_path(@supplier, :per_page => params[:per_page])
rescue => e
flash[:error] = 'Ein Fehler ist aufgetreten: ' + e
redirect_to supplier_articles_path(@supplier, :per_page => params[:per_page])
redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page])
rescue => error
redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page]),
:alert => I18n.t('errors.general_msg', :msg => error)
end
# lets start with parsing articles from uploaded file, yeah
@ -232,14 +160,13 @@ class ArticlesController < ApplicationController
:tax => row[:tax])
# stop parsing, when an article isn't valid
unless article.valid?
raise article.errors.full_messages.join(", ") + " ..in line " + (articles.index(row) + 2).to_s
raise I18n.t('articles.controller.error_parse', :msg => article.errors.full_messages.join(", "), :line => (articles.index(row) + 2).to_s)
end
@articles << article
end
flash.now[:notice] = @articles.size.to_s + " articles are parsed successfully."
rescue => e
flash[:error] = "An error has occurred: " + e.message
redirect_to upload_supplier_articles_path(@supplier)
flash.now[:notice] = I18n.t('articles.controller.parse_upload.notice', :count => @articles.size)
rescue => error
redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
end
end
@ -247,58 +174,40 @@ class ArticlesController < ApplicationController
def create_from_upload
begin
Article.transaction do
for article_attributes in params[:articles]
@supplier.articles.create!(article_attributes)
invalid_articles = false
@articles = []
params[:articles].each do |_key, article_attributes|
@articles << (article = @supplier.articles.build(article_attributes))
invalid_articles = true unless article.save
end
raise I18n.t('articles.controller.error_invalid') if invalid_articles
end
# Successfully done.
flash[:notice] = "The articles are saved successfully"
redirect_to supplier_articles_path(@supplier)
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.create_from_upload.notice', :count => @articles.size)
rescue => error
# An error has occurred, transaction has been rolled back.
flash[:error] = "An error occured: #{error.message}"
redirect_to upload_supplier_articles_path(@supplier)
flash.now[:error] = I18n.t('errors.general_msg', :msg => error.message)
render :parse_upload
end
end
# renders a view to import articles in local database
#
def shared
conditions = []
conditions << "supplier_id = #{@supplier.shared_supplier.id}"
# check for keywords
conditions << params[:import_query].split(' ').collect { |keyword| "name LIKE '%#{keyword}%'" }.join(' AND ') unless params[:import_query].blank?
# check for regional articles
conditions << "origin = 'REG'" if params[:regional]
@articles = SharedArticle.paginate :page => params[:page], :per_page => 10, :conditions => conditions.join(" AND ")
render :update do |page|
page.replace_html 'search_results', :partial => "import_search_results"
end
# build array of keywords, required for meta search _all suffix
params[:search][:name_contains_all] = params[:search][:name_contains_all].split(' ') if params[:search]
# Build search with meta search plugin
@search = @supplier.shared_supplier.shared_articles.search(params[:search])
@articles = @search.page(params[:page]).per(10)
render :layout => false
end
# fills a form whith values of the selected shared_article
def import
shared_article = SharedArticle.find(params[:shared_article_id])
@article = Article.new(
:supplier_id => params[:supplier_id],
:name => shared_article.name,
:unit => shared_article.unit,
:note => shared_article.note,
:manufacturer => shared_article.manufacturer,
:origin => shared_article.origin,
:price => shared_article.price,
:tax => shared_article.tax,
:deposit => shared_article.deposit,
:unit_quantity => shared_article.unit_quantity,
:order_number => shared_article.number,
# convert to db-compatible-string
:shared_updated_on => shared_article.updated_on.to_formatted_s(:db))
render :update do |page|
page["edit_article"].replace_html :partial => 'new'
page["edit_article"].show
end
@article = SharedArticle.find(params[:shared_article_id]).build_new_article
render :action => 'new', :layout => false
end
# sync all articles with the external database
@ -306,16 +215,43 @@ class ArticlesController < ApplicationController
def sync
# check if there is an shared_supplier
unless @supplier.shared_supplier
flash[:error]= "#{@supplier.name} ist nicht mit einer externen Datenbank verknüpft."
redirect_to supplier_articles_path(@supplier)
redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
end
# sync articles against external database
@updated_articles, @outlisted_articles = @supplier.sync_all
# convert to db-compatible-string
@updated_articles.each {|a, b| a.shared_updated_on = a.shared_updated_on.to_formatted_s(:db)}
if @updated_articles.empty? && @outlisted_articles.empty?
flash[:notice] = "Der Katalog ist aktuell."
redirect_to supplier_articles_path(@supplier)
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
end
end
end
# Updates, deletes articles when sync form is submitted
def update_synchronized
begin
Article.transaction do
# delete articles
if params[:outlisted_articles]
Article.find(params[:outlisted_articles].keys).each(&:mark_as_deleted)
end
# Update articles
params[:articles].each do |id, attrs|
Article.find(id).update_attributes! attrs
end
end
# Successfully done.
redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice')
rescue ActiveRecord::RecordInvalid => invalid
# An error has occurred, transaction has been rolled back.
redirect_to supplier_articles_path(@supplier),
alert: I18n.t('articles.controller.error_update', :article => invalid.record.name, :msg => invalid.record.errors.full_messages)
rescue => error
redirect_to supplier_articles_path(@supplier),
alert: I18n.t('errors.general_msg', :msg => error.message)
end
end
end

View File

@ -1,3 +1,4 @@
# encoding: utf-8
class DeliveriesController < ApplicationController
before_filter :find_supplier, :exclude => :fill_new_stock_article_form
@ -34,7 +35,7 @@ class DeliveriesController < ApplicationController
respond_to do |format|
if @delivery.save
flash[:notice] = 'Lieferung wurde erstellt. Bitte nicht vergessen die Rechnung anzulegen!'
flash[:notice] = I18n.t('deliveries.create.notice')
format.html { redirect_to([@supplier,@delivery]) }
format.xml { render :xml => @delivery, :status => :created, :location => @delivery }
else
@ -53,7 +54,7 @@ class DeliveriesController < ApplicationController
respond_to do |format|
if @delivery.update_attributes(params[:delivery])
flash[:notice] = 'Lieferung wurde aktualisiert.'
flash[:notice] = I18n.t('deliveries.update.notice')
format.html { redirect_to([@supplier,@delivery]) }
format.xml { head :ok }
else
@ -67,7 +68,7 @@ class DeliveriesController < ApplicationController
@delivery = Delivery.find(params[:id])
@delivery.destroy
flash[:notice] = "Lieferung wurde gelöscht."
flash[:notice] = I18n.t('deliveries.destroy.notice')
respond_to do |format|
format.html { redirect_to(supplier_deliveries_url(@supplier)) }
format.xml { head :ok }
@ -92,11 +93,7 @@ class DeliveriesController < ApplicationController
end
def add_stock_change
render :update do |page|
page.insert_html :bottom, 'stock_changes', :partial => 'stock_change',
:locals => {:stock_change => StockChange.new, :supplier => @supplier}
end
render :layout => false
end
def fill_new_stock_article_form

View File

@ -1,19 +1,14 @@
class FeedbackController < ApplicationController
def new
render :update do |page|
page.replace_html :ajax_box, :partial => "new"
page.show :ajax_box
end
end
def create
unless params[:message].blank?
Mailer.deliver_feedback(current_user, params[:message])
end
render :update do |page|
page.replace_html :ajax_box, :partial => "success"
if params[:message].present?
Mailer.feedback(current_user, params[:message]).deliver
redirect_to root_url, notice: t('feedback.create.notice')
else
render :action => 'new'
end
end

View File

@ -1,283 +1,50 @@
class Finance::BalancingController < ApplicationController
before_filter :authenticate_finance
verify :method => :post, :only => [:close, :close_direct]
def index
@financial_transactions = FinancialTransaction.find(:all, :order => "created_on DESC", :limit => 8)
@orders = Order.finished_not_closed
@unpaid_invoices = Invoice.unpaid
end
# encoding: utf-8
class Finance::BalancingController < Finance::BaseController
def list
@orders = Order.finished.paginate :page => params[:page], :per_page => 10, :order => 'ends DESC'
def index
@orders = Order.finished.page(params[:page]).per(@per_page).order('ends DESC')
end
def new
@order = Order.find(params[:id])
@order = Order.find(params[:order_id])
flash.now.alert = t('finance.balancing.new.alert') if @order.closed?
@comments = @order.comments
if params['sort']
sort = case params['sort']
when "name" then "articles.name"
when "order_number" then "articles.order_number"
when "name_reverse" then "articles.name DESC"
when "order_number_reverse" then "articles.order_number DESC"
end
else
sort = "id"
end
@articles = @order.order_articles.ordered.includes(:article, :article_price,
group_order_articles: {group_order: :ordergroup})
@articles = @order.order_articles.ordered.find(
:all,
:include => :article,
:order => sort
)
if params[:sort] == "order_number"
@articles = @articles.sort { |a,b| a.article.order_number.gsub(/[^[:digit:]]/, "").to_i <=> b.article.order_number.gsub(/[^[:digit:]]/, "").to_i }
elsif params[:sort] == "order_number_reverse"
@articles = @articles.sort { |a,b| b.article.order_number.gsub(/[^[:digit:]]/, "").to_i <=> a.article.order_number.gsub(/[^[:digit:]]/, "").to_i }
end
sort_param = params['sort'] || 'name'
@articles = case sort_param
when 'name' then
OrderArticle.sort_by_name(@articles)
when 'name_reverse' then
OrderArticle.sort_by_name(@articles).reverse
when 'order_number' then
OrderArticle.sort_by_order_number(@articles)
when 'order_number_reverse' then
OrderArticle.sort_by_order_number(@articles).reverse
else
@articles
end
view = params[:view]
params[:view] = nil
render layout: false if request.xhr?
end
case view
when 'editResults'
render :partial => 'edit_results_by_articles' and return
when 'groupsOverview'
render :partial => 'shared/articles_by_groups', :locals => {:order => @order} and return
when 'articlesOverview'
render :partial => 'shared/articles_by_articles', :locals => {:order => @order} and return
end
def update_summary
@order = Order.find(params[:id])
end
def edit_note
@order = Order.find(params[:id])
render :partial => 'edit_note'
render :layout => false
end
def update_note
@order = Order.find(params[:id])
render :update do |page|
if @order.update_attributes(params[:order])
page["note"].replace_html simple_format(@order.note)
page["edit_box"].hide
else
page["results"].replace_html :partial => "edit_note"
end
end
end
def new_order_article
@order = Order.find(params[:id])
render :update do |page|
page["edit_box"].replace_html :partial => "new_order_article"
page["edit_box"].show
end
end
def auto_complete_for_article_name
order = Order.find(params[:order_id])
find_params = {
:conditions => ["LOWER(articles.name) LIKE ?", "%#{params[:article][:name].downcase}%" ],
:order => 'articles.name ASC',
:limit => 8
}
@articles = if order.stockit?
StockArticle.all find_params
if @order.update_attributes(params[:order])
render :layout => false
else
order.supplier.articles.all find_params
end
render :partial => 'shared/auto_complete_articles'
end
def create_order_article
@order = Order.find(params[:order_id])
article = Article.find(params[:order_article][:article_id])
order_article = @order.order_articles.find_by_article_id(article.id)
unless order_article
# Article wasn't already assigned with this order, lets create a new one
order_article = @order.order_articles.build(params[:order_article])
order_article.article_price = order_article.article.article_prices.first
end
# Set units to order to 1, so the article is visible on page
order_article.units_to_order = 1
render :update do |page|
if order_article.save
page["edit_box"].hide
page.insert_html :top, "result_table", :partial => "order_article_result", :locals => {:order_article => order_article}
page["order_article_#{order_article.id}"].visual_effect :highlight, :duration => 2
page["group_order_articles_#{order_article.id}"].show
else
page["edit_box"].replace_html :partial => "new_order_article"
end
end
rescue
render :update do |page|
page.replace_html "edit_box", :text => "<b>Keinen Artikel gefunden. Bitte erneut versuchen.</b>"
page.insert_html :bottom, "edit_box", :partial => "new_order_article"
end
end
def edit_order_article
@order_article = OrderArticle.find(params[:id])
render :update do |page|
page["edit_box"].replace_html :partial => 'edit_order_article'
page["edit_box"].show
end
end
# Update this article and creates a new articleprice if neccessary
def update_order_article
@order_article = OrderArticle.find(params[:id])
begin
@order_article.update_article_and_price!(params[:article], params[:price], params[:order_article], params[:price_global])
render :update do |page|
page["edit_box"].hide
page["summary"].replace_html :partial => 'summary', :locals => {:order => @order_article.order}
page["summary"].visual_effect :highlight, :duration => 2
page["order_article_#{@order_article.id}"].replace_html :partial => 'order_article', :locals => {:order_article => @order_article}
page["order_article_#{@order_article.id}"].visual_effect :highlight, :delay => 0.5, :duration => 2
page["group_order_articles_#{@order_article.id}"].replace_html :partial => "group_order_articles", :locals => {:order_article => @order_article}
end
rescue => @error
render :update do |page|
page['edit_box'].replace_html :partial => 'edit_order_article'
end
end
end
def destroy_order_article
order_article = OrderArticle.find(params[:id])
order_article.destroy
# Updates ordergroup values
order_article.group_order_articles.each { |goa| goa.group_order.update_price! }
render :update do |page|
page["order_article_#{order_article.id}"].remove
page["group_order_articles_#{order_article.id}"].remove
page["summary"].replace_html :partial => 'summary', :locals => {:order => order_article.order}
page["summary"].visual_effect :highlight, :duration => 2
end
end
def new_group_order_article
goa = OrderArticle.find(params[:id]).group_order_articles.build
render :update do |page|
page["edit_box"].replace_html :partial => "new_group_order_article",
:locals => {:group_order_article => goa}
page["edit_box"].show
end
end
# Creates a new GroupOrderArticle
# If the the chosen Ordergroup hasn't ordered yet, a GroupOrder will also be created
#FIXME: Clean up this messy code !
def create_group_order_article
goa = GroupOrderArticle.new(params[:group_order_article])
order_article = goa.order_article
order = order_article.order
# creates a new GroupOrder if necessary
group_order = GroupOrder.first :conditions => {:order_id => order.id, :ordergroup_id => goa.ordergroup_id}
unless group_order
goa.create_group_order(:order_id => order.id, :ordergroup_id => goa.ordergroup_id)
else
goa.group_order = group_order
end
# If there is an GroupOrderArticle already, only update result attribute.
if group_order_article = GroupOrderArticle.first(:conditions => {:group_order_id => goa.group_order, :order_article_id => goa.order_article})
goa = group_order_article
goa.result = params[:group_order_article]["result"]
end
render :update do |page|
if goa.save
goa.group_order.update_price! # Update the price attribute of new GroupOrder
order_article.update_results! if order_article.article.is_a?(StockArticle) # Update units_to_order of order_article
page["edit_box"].hide
page["order_article_#{order_article.id}"].replace_html :partial => 'order_article', :locals => {:order_article => order_article}
page["group_order_articles_#{order_article.id}"].replace_html :partial => 'group_order_articles',
:locals => {:order_article => order_article}
page["group_order_article_#{goa.id}"].visual_effect :highlight, :duration => 2
page["summary"].replace_html :partial => 'summary', :locals => {:order => order}
page["order_profit"].visual_effect :highlight, :duration => 2
else
page["edit_box"].replace_html :partial => "new_group_order_article",
:locals => {:group_order_article => goa}
end
end
end
def edit_group_order_article
group_order_article = GroupOrderArticle.find(params[:id])
render :partial => 'edit_group_order_article',
:locals => {:group_order_article => group_order_article}
end
def update_group_order_article
goa = GroupOrderArticle.find(params[:id])
render :update do |page|
if goa.update_attributes(params[:group_order_article])
goa.group_order.update_price! # Update the price attribute of new GroupOrder
goa.order_article.update_results! if goa.order_article.article.is_a?(StockArticle) # Update units_to_order of order_article
page["edit_box"].hide
page["order_article_#{goa.order_article.id}"].replace_html :partial => 'order_article', :locals => {:order_article => goa.order_article}
page["group_order_articles_#{goa.order_article.id}"].replace_html :partial => 'group_order_articles',
:locals => {:order_article => goa.order_article}
page["summary"].replace_html :partial => 'summary', :locals => {:order => goa.order_article.order}
page["order_profit"].visual_effect :highlight, :duration => 2
else
page["edit_box"].replace_html :partial => 'edit_group_order_article'
end
end
end
def update_group_order_article_result
goa = GroupOrderArticle.find(params[:id])
if params[:modifier] == '-'
goa.update_attributes({:result => goa.result - 1})
elsif params[:modifier] == '+'
goa.update_attributes({:result => goa.result + 1})
end
render :update do |page|
goa.group_order.update_price! # Update the price attribute of new GroupOrder
goa.order_article.update_results! if goa.order_article.article.is_a?(StockArticle) # Update units_to_order of order_article
page["order_article_#{goa.order_article.id}"].replace_html :partial => 'order_article', :locals => {:order_article => goa.order_article}
page["group_order_articles_#{goa.order_article.id}"].replace_html :partial => 'group_order_articles',
:locals => {:order_article => goa.order_article}
page["summary"].replace_html :partial => 'summary', :locals => {:order => goa.order_article.order}
page["order_profit"].visual_effect :highlight, :duration => 2
end
end
def destroy_group_order_article
goa = GroupOrderArticle.find(params[:id])
goa.destroy
goa.group_order.update_price! # Updates the price attribute of new GroupOrder
goa.order_article.update_results! if goa.order_article.article.is_a?(StockArticle) # Update units_to_order of order_article
render :update do |page|
page["edit_box"].hide
page["order_article_#{goa.order_article.id}"].replace_html :partial => 'order_article', :locals => {:order_article => goa.order_article}
page["group_order_articles_#{goa.order_article.id}"].replace_html :partial => 'group_order_articles',
:locals => {:order_article => goa.order_article}
page["summary"].replace_html :partial => 'summary', :locals => {:order => goa.order_article.order}
page["order_profit"].visual_effect :highlight, :duration => 2
render :action => :edit_note, :layout => false
end
end
@ -289,27 +56,20 @@ class Finance::BalancingController < ApplicationController
# Balances the Order, Update of the Ordergroup.account_balances
def close
@order = Order.find(params[:id])
begin
@order.close!(@current_user)
flash[:notice] = "Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert."
redirect_to :action => "index"
rescue => e
flash[:error] = "Ein Fehler ist beim Abrechnen aufgetreten: " + e
redirect_to :action => "new", :id => @order
end
@order.close!(@current_user)
redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice')
rescue => error
redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message)
end
# Close the order directly, without automaticly updating ordergroups account balances
def close_direct
@order = Order.find(params[:id])
if @order.finished?
@order.update_attributes(:state => 'closed', :updated_by => @current_user)
flash[:notice] = 'Die Bestellung wurde auf "gebucht" gesetzt.'
redirect_to :action => 'listOrders', :id => @order
else
flash[:error] = 'Die Bestellung ist noch nicht beendet.'
redirect_to :action => 'listOrders', :id => @order
end
@order.close_direct!(@current_user)
redirect_to finance_order_index_url, notice: t('finance.balancing.close_direct.notice')
rescue => error
redirect_to finance_order_index_url, alert: t('finance.balancing.close_direct.alert', message: error.message)
end
end

View File

@ -0,0 +1,11 @@
class Finance::BaseController < ApplicationController
before_filter :authenticate_finance
def index
@financial_transactions = FinancialTransaction.order('created_on DESC').limit(8)
@orders = Order.finished_not_closed
@unpaid_invoices = Invoice.unpaid
render template: 'finance/index'
end
end

View File

@ -0,0 +1,65 @@
# encoding: utf-8
class Finance::FinancialTransactionsController < ApplicationController
before_filter :authenticate_finance
before_filter :find_ordergroup, :except => [:new_collection, :create_collection]
inherit_resources
# belongs_to :ordergroup
def index
if params['sort']
sort = case params['sort']
when "date" then "created_on"
when "note" then "note"
when "amount" then "amount"
when "date_reverse" then "created_on DESC"
when "note_reverse" then "note DESC"
when "amount_reverse" then "amount DESC"
end
else
sort = "created_on DESC"
end
@financial_transactions = @ordergroup.financial_transactions.includes(:user).order(sort).
page(params[:page]).per(@per_page)
if params[:query].present?
@financial_transactions = @financial_transactions.where('note LIKE ?', "%#{params[:query]}%")
end
end
def new
@financial_transaction = @ordergroup.financial_transactions.build
end
def create
@financial_transaction = FinancialTransaction.new(params[:financial_transaction])
@financial_transaction.user = current_user
@financial_transaction.add_transaction!
redirect_to finance_ordergroup_transactions_url(@ordergroup), notice: t('finance.financial_transactions.create.notice')
rescue ActiveRecord::RecordInvalid => error
flash.now[:alert] = error.message
render :action => :new
end
def new_collection
end
def create_collection
raise "Notiz wird benötigt!" if params[:note].blank?
params[:financial_transactions].each do |trans|
# ignore empty amount fields ...
unless trans[:amount].blank?
Ordergroup.find(trans[:ordergroup_id]).add_financial_transaction!(trans[:amount], params[:note], @current_user)
end
end
redirect_to finance_ordergroups_url, notice: t('finance.create_collection.create.notice')
rescue => error
redirect_to finance_new_transaction_collection_url, alert: t('finance.create_collection.create.alert', error: error.to_s)
end
protected
def find_ordergroup
@ordergroup = Ordergroup.find(params[:ordergroup_id])
end
end

View File

@ -0,0 +1,83 @@
class Finance::GroupOrderArticlesController < ApplicationController
before_filter :authenticate_finance
layout false # We only use this controller to server js snippets, no need for layout rendering
def new
@order_article = OrderArticle.find(params[:order_article_id])
@group_order_article = GroupOrderArticle.new(order_article: @order_article)
end
def create
@group_order_article = GroupOrderArticle.new(params[:group_order_article])
@order_article = @group_order_article.order_article
# As we hide group_order_articles with a result of 0, we should not complain, when an existing group_order_article is found
goa = GroupOrderArticle.where(group_order_id: @group_order_article.group_order_id,
order_article_id: @order_article.id).first
if goa and goa.update_attributes(params[:group_order_article])
@group_order_article = goa
update_summaries(@group_order_article)
render :update
elsif @group_order_article.save
update_summaries(@group_order_article)
render :update
else # Validation failed, show form
render :new
end
end
def edit
@group_order_article = GroupOrderArticle.find(params[:id])
@order_article = @group_order_article.order_article
end
def update
@group_order_article = GroupOrderArticle.find(params[:id])
@order_article = @group_order_article.order_article
if @group_order_article.update_attributes(params[:group_order_article])
update_summaries(@group_order_article)
else
render :edit
end
end
def update_result
group_order_article = GroupOrderArticle.find(params[:id])
@order_article = group_order_article.order_article
if params[:modifier] == '-'
group_order_article.update_attribute :result, group_order_article.result - 1
elsif params[:modifier] == '+'
group_order_article.update_attribute :result, group_order_article.result + 1
end
update_summaries(group_order_article)
render :update
end
def destroy
group_order_article = GroupOrderArticle.find(params[:id])
group_order_article.destroy
update_summaries(group_order_article)
@order_article = group_order_article.order_article
render :update
end
protected
def update_summaries(group_order_article)
# Update the price attribute of new GroupOrder
group_order_article.group_order.update_price!
# Update units_to_order of order_article
group_order_article.order_article.update_results! if group_order_article.order_article.article.is_a?(StockArticle)
end
end

View File

@ -1,47 +1,31 @@
class Finance::InvoicesController < ApplicationController
def index
@invoices = Invoice.find(:all, :order => "date DESC")
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @invoices }
end
@invoices = Invoice.includes(:supplier, :delivery, :order).order('date DESC').page(params[:page]).per(@per_page)
end
def show
@invoice = Invoice.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @invoice }
end
end
def new
@invoice = Invoice.new :supplier_id => params[:supplier_id],
:delivery_id => params[:delivery_id], :order_id => params[:order_id]
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @invoice }
end
:delivery_id => params[:delivery_id],
:order_id => params[:order_id]
end
def edit
@invoice = Invoice.find(params[:id])
end
# POST /invoices
# POST /invoices.xml
def create
@invoice = Invoice.new(params[:invoice])
if @invoice.save
flash[:notice] = "Rechnung wurde erstellt."
flash[:notice] = I18n.t('finance.create.notice')
if @invoice.order
# Redirect to balancing page
redirect_to :controller => 'balancing', :action => 'new', :id => @invoice.order
redirect_to new_finance_order_url(order_id: @invoice.order.id)
else
redirect_to [:finance, @invoice]
end
@ -50,32 +34,20 @@ class Finance::InvoicesController < ApplicationController
end
end
# PUT /invoices/1
# PUT /invoices/1.xml
def update
@invoice = Invoice.find(params[:id])
respond_to do |format|
if @invoice.update_attributes(params[:invoice])
flash[:notice] = 'Invoice was successfully updated.'
format.html { redirect_to([:finance, @invoice]) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @invoice.errors, :status => :unprocessable_entity }
end
if @invoice.update_attributes(params[:invoice])
redirect_to [:finance, @invoice], notice: I18n.t('finance.update.notice')
else
render :edit
end
end
# DELETE /invoices/1
# DELETE /invoices/1.xml
def destroy
@invoice = Invoice.find(params[:id])
@invoice.destroy
respond_to do |format|
format.html { redirect_to(finance_invoices_path) }
format.xml { head :ok }
end
redirect_to finance_invoices_url
end
end

View File

@ -0,0 +1,39 @@
class Finance::OrderArticlesController < ApplicationController
before_filter :authenticate_finance
layout false # We only use this controller to serve js snippets, no need for layout rendering
def new
@order = Order.find(params[:order_id])
@order_article = @order.order_articles.build
end
def create
@order = Order.find(params[:order_id])
@order_article = @order.order_articles.build(params[:order_article])
unless @order_article.save
render action: :new
end
end
def edit
@order = Order.find(params[:order_id])
@order_article = OrderArticle.find(params[:id])
end
def update
@order = Order.find(params[:order_id])
@order_article = OrderArticle.find(params[:id])
begin
@order_article.update_article_and_price!(params[:order_article], params[:article], params[:article_price])
rescue
render action: :edit
end
end
def destroy
@order_article = OrderArticle.find(params[:id])
@order_article.destroy
end
end

View File

@ -0,0 +1,20 @@
class Finance::OrdergroupsController < Finance::BaseController
def index
if params["sort"]
sort = case params["sort"]
when "name" then "name"
when "account_balance" then "account_balance"
when "name_reverse" then "name DESC"
when "account_balance_reverse" then "account_balance DESC"
end
else
sort = "name"
end
@ordergroups = Ordergroup.undeleted.order(sort)
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].nil?
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
end
end

View File

@ -1,103 +0,0 @@
class Finance::TransactionsController < ApplicationController
before_filter :authenticate_finance
def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
@per_page = params[:per_page].to_i
else
@per_page = 20
end
if params["sort"]
sort = case params["sort"]
when "name" then "name"
when "account_balance" then "account_balance"
when "name_reverse" then "name DESC"
when "account_balance_reverse" then "account_balance DESC"
end
else
sort = "name"
end
conditions = "name LIKE '%#{params[:query]}%'" unless params[:query].nil?
@total = Ordergroup.without_deleted.count(:conditions => conditions)
@groups = Ordergroup.without_deleted.paginate :conditions => conditions,
:page => params[:page], :per_page => @per_page, :order => sort
respond_to do |format|
format.html
format.js { render :partial => "ordergroups" }
end
end
def list
@group = Ordergroup.find(params[:id])
if params['sort']
sort = case params['sort']
when "date" then "created_on"
when "note" then "note"
when "amount" then "amount"
when "date_reverse" then "created_on DESC"
when "note_reverse" then "note DESC"
when "amount_reverse" then "amount DESC"
end
else
sort = "created_on DESC"
end
conditions = ["note LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?
@total = @group.financial_transactions.count(:conditions => conditions)
@financial_transactions = @group.financial_transactions.paginate(
:page => params[:page],
:per_page => 10,
:conditions => conditions,
:order => sort)
respond_to do |format|
format.html
format.js { render :partial => "list" }
end
end
def new
@group = Ordergroup.find(params[:id])
@financial_transaction = @group.financial_transactions.build
end
def create
@group = Ordergroup.find(params[:financial_transaction][:ordergroup_id])
amount = params[:financial_transaction][:amount]
note = params[:financial_transaction][:note]
begin
@group.add_financial_transaction(amount, note, @current_user)
flash[:notice] = 'Transaktion erfolgreich angelegt.'
redirect_to :action => 'list', :id => @group
rescue => e
@financial_transaction = FinancialTransaction.new(params[:financial_transaction])
flash.now[:error] = 'Transaktion konnte nicht angelegt werden!' + ' (' + e.message + ')'
render :action => 'new'
end
end
def new_collection
end
def create_collection
note = params[:note]
raise "Notiz wird benötigt!" if note.blank?
params[:financial_transactions].each do |trans|
# ignore empty amount fields ...
unless trans[:amount].blank?
Ordergroup.find(trans[:ordergroup_id]).add_financial_transaction trans[:amount], note, @current_user
end
end
flash[:notice] = "Alle Transaktionen wurden gespeichert."
redirect_to :action => 'index'
rescue => error
flash[:error] = "Ein Fehler ist aufgetreten: " + error.to_s
redirect_to :action => 'new_collection'
end
end

View File

@ -1,29 +1,21 @@
class Foodcoop::OrdergroupsController < ApplicationController
def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
@per_page = params[:per_page].to_i
else
@per_page = 20
@ordergroups = Ordergroup.undeleted.order('name DESC')
unless params[:name].blank? # Search by name
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%")
end
if (params[:only_active].to_i == 1)
if (! params[:query].blank?)
conditions = ["orders.starts >= ? AND name LIKE ?", Time.now.months_ago(3), "%#{params[:query]}%"]
else
conditions = ["orders.starts >= ?", Time.now.months_ago(3)]
end
else
# if somebody uses the search field:
conditions = ["name LIKE ?", "%#{params[:query]}%"] unless params[:query].blank?
if params[:only_active] # Select only active groups
@ordergroups = @ordergroups.joins(:orders).where("orders.starts >= ?", Time.now.months_ago(3)).uniq
end
@total = Ordergroup.count(:conditions => conditions, :include => "orders")
@ordergroups = Ordergroup.paginate(:page => params[:page], :per_page => @per_page, :conditions => conditions, :order => "name", :include => "orders")
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
respond_to do |format|
format.html # index.html.erb
format.js { render :partial => "ordergroups" }
format.js { render :layout => false }
end
end
end

View File

@ -1,36 +1,23 @@
class Foodcoop::UsersController < ApplicationController
def index
# sort by ordergroups
if params[:sort_by_ordergroups]
@users = []
Ordergroup.find(:all, :order => "name").each do |group|
group.users.each do |user|
@users << user
end
end
@total = @users.size
else
# sort by nick, thats default
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
@per_page = params[:per_page].to_i
else
@per_page = 20
end
@users = User.order('nick ASC')
# if somebody uses the search field:
unless params[:query].blank?
conditions = ["first_name LIKE ? OR last_name LIKE ? OR nick LIKE ?",
"%#{params[:query]}%", "%#{params[:query]}%", "%#{params[:query]}%"]
end
# if somebody uses the search field:
unless params[:user_name].blank?
@users = @users.where("first_name LIKE :user_name OR last_name LIKE :user_name OR nick LIKE :user_name",
user_name: "%#{params[:user_name]}%")
end
@total = User.count(:conditions => conditions)
@users = User.paginate(:page => params[:page], :per_page => @per_page, :conditions => conditions, :order => "nick", :include => :groups)
if params[:ordergroup_name]
@users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", "%#{params[:ordergroup_name]}%")
end
respond_to do |format|
format.html # index.html.erb
format.js { render :partial => "users" }
end
@users = @users.page(params[:page]).per(@per_page).order('users.nick ASC')
respond_to do |format|
format.html # index.html.haml
format.js { render :layout => false } # index.js.erb
end
end

View File

@ -14,13 +14,9 @@ class Foodcoop::WorkgroupsController < ApplicationController
def update
@workgroup = Workgroup.find(params[:id])
if @workgroup.update_attributes(params[:workgroup])
flash[:notice] = "Arbeitsgruppe wurde aktualisiert"
redirect_to foodcoop_workgroups_url
redirect_to foodcoop_workgroups_url, :notice => I18n.t('workgroups.update.notice')
else
render :action => 'edit'
end
end
def memberships
end
end

View File

@ -0,0 +1,99 @@
# Controller for all ordering-actions that are performed by a user who is member of an Ordergroup.
# Management actions that require the "orders" role are handled by the OrdersController.
class GroupOrdersController < ApplicationController
# Security
before_filter :ensure_ordergroup_member
before_filter :ensure_open_order, :only => [:new, :create, :edit, :update, :order, :stock_order, :saveOrder]
before_filter :ensure_my_group_order, only: [:show, :edit, :update]
before_filter :enough_apples?, only: [:new, :create]
# Index page.
def index
end
def new
@group_order = @order.group_orders.build(:ordergroup => @ordergroup, :updated_by => current_user)
@ordering_data = @group_order.load_data
end
def create
@group_order = GroupOrder.new(params[:group_order])
begin
@group_order.save_ordering!
redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.create.notice')
rescue ActiveRecord::StaleObjectError
redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_stale')
rescue => exception
logger.error('Failed to update order: ' + exception.message)
redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_general')
end
end
def show
@order= @group_order.order
end
def edit
@ordering_data = @group_order.load_data
end
def update
@group_order.attributes = params[:group_order]
begin
@group_order.save_ordering!
redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.update.notice')
rescue ActiveRecord::StaleObjectError
redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_stale')
rescue => exception
logger.error('Failed to update order: ' + exception.message)
redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_general')
end
end
# Shows all Orders of the Ordergroup
# if selected, it shows all orders of the foodcoop
def archive
# get only orders belonging to the ordergroup
@closed_orders = Order.closed.page(params[:page]).per(10)
respond_to do |format|
format.html # archive.html.haml
format.js # archive.js.erb
end
end
private
# Returns true if @current_user is member of an Ordergroup.
# Used as a :before_filter by OrderingController.
def ensure_ordergroup_member
@ordergroup = @current_user.ordergroup
if @ordergroup.nil?
redirect_to root_url, :alert => I18n.t('group_orders.errors.no_member')
end
end
def ensure_open_order
@order = Order.find((params[:order_id] || params[:group_order][:order_id]),
:include => [:supplier, :order_articles])
unless @order.open?
flash[:notice] = I18n.t('group_orders.error_closed')
redirect_to :action => 'index'
end
end
def ensure_my_group_order
@group_order = @ordergroup.group_orders.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
end
def enough_apples?
if @ordergroup.not_enough_apples?
redirect_to group_orders_url,
alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples,
stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
end
end
end

View File

@ -1,34 +1,24 @@
# encoding: utf-8
class HomeController < ApplicationController
helper :messages
def index
@currentOrders = Order.open
@ordergroup = @current_user.ordergroup
# unaccepted tasks
@unaccepted_tasks = @current_user.unaccepted_tasks
@unaccepted_tasks = Task.unaccepted_tasks_for(current_user)
# task in next week
@next_tasks = @current_user.next_tasks
@messages = Message.public.all :order => 'created_at DESC', :limit => 5
@next_tasks = Task.next_assigned_tasks_for(current_user)
# count tasks with no responsible person
# tasks for groups the current user is not a member are ignored
tasks = Task.find(:all, :conditions => ["assigned = ? and done = ?", false, false])
@unassigned_tasks_number = 0
for task in tasks
(@unassigned_tasks_number += 1) unless task.workgroup && !current_user.member_of?(task.workgroup)
end
@unassigned_tasks = Task.unassigned_tasks_for(current_user)
end
def profile
@user = @current_user
end
def update_profile
@user = @current_user
if @user.update_attributes(params[:user])
flash[:notice] = 'Änderungen wurden gespeichert.'
redirect_to :action => 'profile'
if @current_user.update_attributes(params[:user])
redirect_to my_profile_url, notice: I18n.t('home.changes_saved')
else
render :action => 'profile'
render :profile
end
end
@ -37,10 +27,6 @@ class HomeController < ApplicationController
@ordergroup = @user.ordergroup
unless @ordergroup.nil?
@ordergroup_column_names = ["Description", "Actual Size", "Balance", "Updated"]
@ordergroup_columns = ["description", "account_balance", "account_updated"]
#listing the financial transactions with ajax...
if params['sort']
sort = case params['sort']
@ -55,21 +41,11 @@ class HomeController < ApplicationController
sort = "created_on DESC"
end
# or if somebody uses the search field:
conditions = ["note LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?
@financial_transactions = @ordergroup.financial_transactions.page(params[:page]).per(@per_page).order(sort)
@financial_transactions = @financial_transactions.where("note LIKE ?", "%#{params[:query]}%") if params[:query].present?
@total = @ordergroup.financial_transactions.count(:conditions => conditions)
@financial_transactions = @ordergroup.financial_transactions.paginate(:page => params[:page],
:per_page => 10,
:conditions => conditions,
:order => sort)
respond_to do |format|
format.html # myOrdergroup.haml
format.js { render :partial => "finance/transactions/list" }
end
else
flash[:error] = "Leider bist Du kein Mitglied einer Bestellgruppe"
redirect_to root_path
redirect_to root_path, :alert => I18n.t('home.no_ordergroups')
end
end
@ -78,9 +54,9 @@ class HomeController < ApplicationController
membership = Membership.find(params[:membership_id])
if membership.user == current_user
membership.destroy
flash[:notice] = "Du bist jetzt kein Mitglied der Gruppe #{membership.group.name} mehr."
flash[:notice] = I18n.t('home.ordergroup_cancelled', :group => membership.group.name)
else
flash[:error] = "Ein Problem ist aufgetreten."
flash[:error] = I18n.t('errors.general')
end
redirect_to my_profile_path
end

View File

@ -1,26 +1,26 @@
class InvitesController < ApplicationController
before_filter :authenticate_membership_or_admin, :only => [:new]
#TODO: auhtorize also for create action.
#TODO: authorize also for create action.
def new
@invite = Invite.new(:user => @current_user, :group => @group)
render :update do |page|
page.replace_html :edit_box, :partial => "new"
page.show :edit_box
end
end
def create
@invite = Invite.new(params[:invite])
if @invite.save
Mailer.invite(@invite).deliver
render :update do |page|
if @invite.save
page.replace_html :edit_box, :partial => "success"
else
page.replace_html :edit_box, :partial => "new"
respond_to do |format|
format.html do
redirect_to back_or_default_path, notice: I18n.t('invites.success')
end
format.js { render layout: false }
end
else
render action: :new
end
end
end

View File

@ -1,69 +1,33 @@
# encoding: utf-8
class LoginController < ApplicationController
skip_before_filter :authenticate # no authentication since this is the login page
before_filter :validate_token, :only => [:password, :update_password]
before_filter :validate_token, :only => [:new_password, :update_password]
verify :method => :post, :only => [:login, :reset_password, :new], :redirect_to => { :action => :index }
# Redirects to the login action.
def index
render :action => 'login'
end
# Logout the current user and deletes the session
def logout
self.return_to = nil
current_user = nil
reset_session
flash[:notice] = "Abgemeldet"
render :action => 'login'
end
# Displays a "denied due to insufficient privileges" message and provides the login form.
def denied
flash[:error] = "Du bist nicht berechtigt diese Seite zu besuchen. Bitte als berechtige Benutzerin anmelden oder zurück gehen."
render :action => 'login'
end
# Login to the foodsoft.
def login
user = User.find_by_nick(params[:login][:user])
if user && user.has_password(params[:login][:password])
# Set last_login to Now()
user.update_attribute(:last_login, Time.now)
self.current_user = user
if (redirect = return_to)
self.return_to = nil
redirect_to redirect
else
redirect_to root_path
end
else
current_user = nil
flash[:error] = "Tschuldige, die Anmeldung war nicht erfolgreich. Bitte erneut versuchen."
end
end
# Display the form to enter an email address requesting a token to set a new password.
def forgot_password
@user = User.new
end
# Sends an email to a user with the token that allows setting a new password through action "password".
def reset_password
if (user = User.find_by_email(params[:login][:email]))
if request.get? || params[:user].nil? # Catch for get request and give better error message.
redirect_to forgot_password_url, alert: 'Ein Problem ist aufgetreten. Bitte erneut versuchen' and return
end
if (user = User.find_by_email(params[:user][:email]))
user.reset_password_token = user.new_random_password(16)
user.reset_password_expires = Time.now.advance(:days => 2)
if user.save
email = Mailer.deliver_reset_password(user)
Mailer.reset_password(user).deliver
logger.debug("Sent password reset email to #{user.email}.")
end
end
flash[:notice] = "Wenn Deine E-Mail hier registiert ist bekommst Du jetzt eine Nachricht mit einem Passwort-Zurücksetzen-Link."
render :action => 'login'
redirect_to login_url, :notice => I18n.t('login.controller.reset_password.notice')
end
# Set a new password with a token from the password reminder email.
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
def password
def new_password
end
# Sets a new password.
@ -74,38 +38,32 @@ class LoginController < ApplicationController
@user.reset_password_token = nil
@user.reset_password_expires = nil
@user.save
flash[:notice] = "Dein Passwort wurde aktualisiert. Du kannst Dich jetzt anmelden."
render :action => 'login'
redirect_to login_url, :notice => I18n.t('login.controller.update_password.notice')
else
render :action => 'password'
render :new_password
end
end
# Invited users.
def invite
@invite = Invite.find_by_token(params[:id])
if (@invite.nil? || @invite.expires_at < Time.now)
flash[:error] = "Deine Einladung ist nicht (mehr) gültig."
render :action => 'login'
# For invited users.
def accept_invitation
@invite = Invite.find_by_token(params[:token])
if @invite.nil? || @invite.expires_at < Time.now
redirect_to login_url, alert: I18n.t('login.controller.error_invite_invalid')
elsif @invite.group.nil?
flash[:error] = "Die Gruppe, in die Du eingeladen wurdest, existiert leider nicht mehr."
render :action => 'login'
elsif (request.post?)
redirect_to login_url, alert: I18n.t('login.controller.error_group_invalid')
elsif request.post?
User.transaction do
@user = User.new(params[:user])
@user.email = @invite.email
if @user.save
Membership.new(:user => @user, :group => @invite.group).save!
@invite.destroy
flash[:notice] = "Herzlichen Glückwunsch, Dein Account wurde erstellt. Du kannst Dich nun einloggen."
render :action => 'login'
redirect_to login_url, notice: I18n.t('login.controller.accept_invitation.notice')
end
end
else
@user = User.new(:email => @invite.email)
end
rescue
flash[:error] = "Ein Fehler ist aufgetreten. Bitte erneut versuchen."
end
protected
@ -113,8 +71,7 @@ class LoginController < ApplicationController
def validate_token
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
if (@user.nil? || @user.reset_password_expires < Time.now)
flash[:error] = "Ungültiger oder abgelaufener Token. Bitte versuch es erneut."
render :action => 'forgot_password'
redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid')
end
end
end

View File

@ -1,76 +1,34 @@
class MessagesController < ApplicationController
# Renders the "inbox" action.
def index
@messages = Message.public.paginate :page => params[:page], :per_page => 20, :order => 'created_at DESC'
@messages = Message.public.page(params[:page]).per(@per_page).order('created_at DESC').includes(:sender)
end
# Creates a new message object.
def new
@message = Message.new
@message = Message.new(params[:message])
if @message.reply_to and not @message.reply_to.is_readable_for?(current_user)
redirect_to new_message_url, alert: 'Nachricht ist privat!'
end
end
# Creates a new message.
def create
@message = @current_user.send_messages.new(params[:message])
if @message.save
#FIXME: Send Mails wit ID instead of using message.state ...
call_rake :send_emails
flash[:notice] = "Nachricht ist gespeichert und wird versendet."
redirect_to messages_path
Resque.enqueue(UserNotifier, FoodsoftConfig.scope, 'message_deliver', @message.id)
redirect_to messages_url, :notice => I18n.t('messages.create.notice')
else
render :action => 'new'
end
end
# Shows a single message.
def show
@message = Message.find(params[:id])
end
# Replys to the message specified through :id.
def reply
message = Message.find(params[:id])
@message = Message.new(:recipient => message.sender, :subject => "Re: #{message.subject}")
@message.body = "#{message.sender.nick} schrieb am #{I18n.l(message.created_at.to_date)} um #{I18n.l(message.created_at, :format => :time)}:\n"
message.body.each_line{|l| @message.body += "> #{l}"}
render :action => 'new'
end
# Shows new-message form with the recipient user specified through :id.
def user
if (recipient = User.find(params[:id]))
@message = Message.new(:recipient => recipient)
render :action => 'new'
else
flash[:error] = 'Unbekannte_r EmpfängerIn.'
redirect_to :action=> 'index'
unless @message.is_readable_for?(current_user)
redirect_to messages_url, alert: 'Nachricht ist privat!'
end
end
# Shows new-message form with the recipient user specified through :id.
def group
group = Group.find(params[:id], :include => :memberships)
if (group && !group.memberships.empty?)
@message = Message.new(:group_id => group.id)
render :action => 'new'
else
flash[:error] = 'Empfängergruppe ist unbekannt oder hat keine Mitglieder.'
redirect_to :action=> 'index'
end
end
# Auto-complete for recipient user list.
def auto_complete_for_message_recipients_nicks
@users = User.find(:all,
:conditions => ['LOWER(nick) LIKE ?', '%' + params[:message][:recipients_nicks].downcase + '%'],
:order => :nick, :limit => 8)
render :partial => '/shared/auto_complete_users'
end
# Returns list of all users as auto-completion hint.
def user_list
@users = User.find(:all, :order => :nick)
render :partial => '/shared/auto_complete_users'
end
end

View File

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

View File

@ -1,209 +0,0 @@
# Controller for all ordering-actions that are performed by a user who is member of an Ordergroup.
# Management actions that require the "orders" role are handled by the OrdersController.
class OrderingController < ApplicationController
# Security
before_filter :ensure_ordergroup_member
before_filter :ensure_open_order, :only => [:order, :stock_order, :saveOrder]
verify :method => :post, :only => [:saveOrder], :redirect_to => {:action => :index}
# Index page.
def index
end
# Edit a current order.
def order
redirect_to :action => 'stock_order', :id => @order if @order.stockit?
# Load order article data...
@articles_grouped_by_category = @order.articles_grouped_by_category
# save results of earlier orders in array
ordered_articles = Array.new
@group_order = @order.group_orders.find(:first,
:conditions => "ordergroup_id = #{@ordergroup.id}", :include => :group_order_articles)
if @group_order
# Group has already ordered, so get the results...
for goa in @group_order.group_order_articles
ordered_articles[goa.order_article_id] = {:quantity => goa.quantity,
:tolerance => goa.tolerance,
:quantity_result => goa.result(:quantity),
:tolerance_result => goa.result(:tolerance)}
end
@version = @group_order.lock_version
@availableFunds = @ordergroup.get_available_funds(@group_order)
else
@version = 0
@availableFunds = @ordergroup.get_available_funds
end
# load prices ....
@price = Array.new; @unit = Array.new;
@others_quantity = Array.new; @quantity = Array.new; @quantity_result = Array.new; @used_quantity = Array.new; @unused_quantity = Array.new
@others_tolerance = Array.new; @tolerance = Array.new; @tolerance_result = Array.new; @used_tolerance = Array.new; @unused_tolerance = Array.new
i = 0;
@articles_grouped_by_category.each do |category_name, order_articles|
for order_article in order_articles
# price/unit size
@price[i] = order_article.article.fc_price
@unit[i] = order_article.article.unit_quantity
# quantity
@quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:quantity] : 0)
@others_quantity[i] = order_article.quantity - @quantity[i]
@used_quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:quantity_result] : 0)
# tolerance
@tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:tolerance] : 0)
@others_tolerance[i] = order_article.tolerance - @tolerance[i]
@used_tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:tolerance_result] : 0)
i += 1
end
end
end
def stock_order
# Load order article data...
@articles_grouped_by_category = @order.articles_grouped_by_category
# save results of earlier orders in array
ordered_articles = Array.new
@group_order = @order.group_orders.find(:first,
:conditions => "ordergroup_id = #{@ordergroup.id}", :include => :group_order_articles)
if @group_order
# Group has already ordered, so get the results...
for goa in @group_order.group_order_articles
ordered_articles[goa.order_article_id] = {:quantity => goa.quantity,
:tolerance => goa.tolerance,
:quantity_result => goa.result(:quantity),
:tolerance_result => goa.result(:tolerance)}
end
@version = @group_order.lock_version
@availableFunds = @ordergroup.get_available_funds(@group_order)
else
@version = 0
@availableFunds = @ordergroup.get_available_funds
end
# load prices ....
@price = Array.new; @quantity_available = Array.new
@others_quantity = Array.new; @quantity = Array.new; @quantity_result = Array.new; @used_quantity = Array.new; @unused_quantity = Array.new
i = 0;
@articles_grouped_by_category.each do |category_name, order_articles|
for order_article in order_articles
# price/unit size
@price[i] = order_article.article.fc_price
@quantity_available[i] = order_article.article.quantity_available(@order)
# quantity
@quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:quantity] : 0)
@others_quantity[i] = order_article.quantity - @quantity[i]
@used_quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id][:quantity_result] : 0)
i += 1
end
end
end
# Update changes to a current order.
def saveOrder
if (params[:total_balance].to_i < 0) #TODO: Better use a real test on sufficiant funds
flash[:error] = 'Der Bestellwert übersteigt das verfügbare Guthaben.'
redirect_to :action => 'order'
elsif (ordered = params[:ordered])
begin
Order.transaction do
# Try to find group_order
group_order = @order.group_orders.first :conditions => "ordergroup_id = #{@ordergroup.id}",
:include => [:group_order_articles]
# Create group order if necessary...
unless group_order.nil?
# check for conflicts well ahead
if (params[:version].to_i != group_order.lock_version)
raise ActiveRecord::StaleObjectError
end
else
group_order = @ordergroup.group_orders.create!(:order => @order, :updated_by => @current_user, :price => 0)
end
# Create/update group_order_articles...
for order_article in @order.order_articles
# Find the group_order_article, create a new one if necessary...
group_order_article = group_order.group_order_articles.detect { |v| v.order_article_id == order_article.id }
if group_order_article.nil?
group_order_article = group_order.group_order_articles.create(:order_article_id => order_article.id)
end
# Get ordered quantities and update group_order_articles/_quantities...
quantities = ordered.fetch(order_article.id.to_s, {:quantity => 0, :tolerance => 0})
group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i)
# Also update results for the order_article
order_article.update_results!
end
group_order.update_price!
group_order.update_attribute(:updated_by, @current_user)
end
flash[:notice] = 'Die Bestellung wurde gespeichert.'
rescue ActiveRecord::StaleObjectError
flash[:error] = 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.'
rescue => exception
logger.error('Failed to update order: ' + exception.message)
flash[:error] = 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.'
end
redirect_to :action => 'my_order_result', :id => @order
end
end
# Shows the Result for the Ordergroup the current user belongs to
# this method decides between finished and unfinished orders
def my_order_result
@order= Order.find(params[:id])
@group_order = @order.group_order(@ordergroup)
end
# Shows all Orders of the Ordergroup
# if selected, it shows all orders of the foodcoop
def myOrders
# get only orders belonging to the ordergroup
@closed_orders = Order.paginate :page => params[:page], :per_page => 10,
:conditions => { :state => 'closed' }, :order => "orders.ends DESC"
respond_to do |format|
format.html # myOrders.haml
format.js { render :partial => "orders", :locals => {:orders => @closed_orders, :pagination => true} }
end
end
# adds a Comment to the Order
def add_comment
order = Order.find(params[:id])
comment = order.comments.build(params[:comment])
comment.user = @current_user
if !comment.text.blank? and comment.save
flash[:notice] = "Kommentar wurde erstellt."
else
flash[:error] = "Kommentar konnte nicht erstellt werden. Leerer Kommentar?"
end
redirect_to :action => 'my_order_result', :id => order
end
private
# Returns true if @current_user is member of an Ordergroup.
# Used as a :before_filter by OrderingController.
def ensure_ordergroup_member
@ordergroup = @current_user.ordergroup
if @ordergroup.nil?
flash[:notice] = 'Sie gehören keiner Bestellgruppe an.'
redirect_to :controller => root_path
end
end
def ensure_open_order
@order = Order.find(params[:id], :include => [:supplier, :order_articles])
unless @order.open?
flash[:notice] = 'Diese Bestellung ist bereits abgeschlossen.'
redirect_to :action => 'index'
end
end
end

View File

@ -1,13 +1,11 @@
# encoding: utf-8
#
# Controller for managing orders, i.e. all actions that require the "orders" role.
# Normal ordering actions of members of order groups is handled by the OrderingController.
class OrdersController < ApplicationController
before_filter :authenticate_orders
# Define layout exceptions for PDF actions:
layout "application", :except => [:faxPdf, :matrixPdf, :articlesPdf, :groupsPdf]
prawnto :prawn => { :page_size => 'A4' }
# List orders
def index
@open_orders = Order.open
@ -22,18 +20,7 @@ class OrdersController < ApplicationController
else
sort = "ends DESC"
end
@orders = Order.paginate :page => params[:page], :per_page => @per_page,
:order => sort, :conditions => "state != 'open'",
:include => :supplier
respond_to do |format|
format.html
format.js do
render :update do |page|
page.replace_html 'orders_table', :partial => "orders"
end
end
end
@orders = Order.page(params[:page]).per(@per_page).order(sort).where("state != 'open'").includes(:supplier)
end
# Gives a view for the results to a specific order
@ -41,13 +28,26 @@ class OrdersController < ApplicationController
def show
@order= Order.find(params[:id])
if params[:view] # Articles-list will be replaced
partial = case params[:view]
when 'normal' then "articles"
when 'groups'then 'shared/articles_by_groups'
when 'articles'then 'shared/articles_by_articles'
respond_to do |format|
format.html
format.js do
@partial = case params[: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
format.pdf do
pdf = case params[:document]
when 'groups' then OrderByGroups.new(@order)
when 'articles' then OrderByArticles.new(@order)
when 'fax' then OrderFax.new(@order)
when 'matrix' then OrderMatrix.new(@order)
end
send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf'
end
render :partial => partial, :locals => {:order => @order} if partial
end
end
@ -60,10 +60,12 @@ class OrdersController < ApplicationController
# order_articles will be saved in Order.article_ids=()
def create
@order = Order.new(params[:order])
@order.created_by = current_user
if @order.save
flash[:notice] = "Die Bestellung wurde erstellt."
flash[:notice] = I18n.t('orders.create.notice')
redirect_to @order
else
logger.debug "[debug] order errors: #{@order.errors.messages}"
render :action => 'new'
end
end
@ -78,7 +80,7 @@ class OrdersController < ApplicationController
def update
@order = Order.find params[:id]
if @order.update_attributes params[:order]
flash[:notice] = "Die Bestellung wurde aktualisiert."
flash[:notice] = I18n.t('orders.update.notice')
redirect_to :action => 'show', :id => @order
else
render :action => 'edit'
@ -95,31 +97,7 @@ class OrdersController < ApplicationController
def finish
order = Order.find(params[:id])
order.finish!(@current_user)
call_rake "foodsoft:finished_order_tasks", :order_id => order.id
flash[:notice] = "Die Bestellung wurde beendet."
redirect_to order
end
# Renders the groups-orderd PDF.
def groupsPdf
@order = Order.find(params[:id])
prawnto :filename => "#{Date.today}_#{@order.name}_GruppenSortierung.pdf"
end
# Renders the articles-orderd PDF.
def articlesPdf
@order = Order.find(params[:id])
prawnto :filename => "#{Date.today}_#{@order.name}_ArtikelSortierung.pdf",
:prawn => { :left_margin => 48,
:right_margin => 48,
:top_margin => 48,
:bottom_margin => 48 }
end
# Renders the fax PDF.
def faxPdf
@order = Order.find(params[:id])
prawnto :filename => "#{Date.today}_#{@order.name}_FAX.pdf"
redirect_to order, notice: I18n.t('orders.finish.notice')
end
# Renders the fax-text-file
@ -127,15 +105,15 @@ class OrdersController < ApplicationController
def text_fax_template
order = Order.find(params[:id])
supplier = order.supplier
contact = Foodsoft.config[:contact].symbolize_keys
text = "Bestellung für" + " #{Foodsoft.config[:name]}"
text += "\n" + "Kundennummer" + ": #{supplier.customer_number}" unless supplier.customer_number.blank?
text += "\n" + "Liefertag" + ": "
text += "\n\n#{supplier.name}\n#{supplier.address}\nFAX: #{supplier.fax}\n\n"
text += "****** " + "Versandadresse" + "\n\n"
text += "#{Foodsoft.config[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n"
text += "****** " + "Artikel" + "\n\n"
text += "Nummer" + " " + "Menge" + " " + "Name" + "\n"
contact = FoodsoftConfig[:contact].symbolize_keys
text = I18n.t('orders.fax.heading', :name => FoodsoftConfig[:name])
text += "\n" + I18n.t('orders.fax.customer_number') + ': #{supplier.customer_number}' unless supplier.customer_number.blank?
text += "\n" + I18n.t('orders.fax.delivery_day')
text += "\n\n#{supplier.name}\n#{supplier.address}\n" + I18n.t('simple_form.labels.supplier.fax') + ": #{supplier.fax}\n\n"
text += "****** " + I18n.t('orders.fax.to_address') + "\n\n"
text += "#{FoodsoftConfig[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n"
text += "****** " + I18n.t('orders.fax.articles') + "\n\n"
text += I18n.t('orders.fax.number') + " " + I18n.t('orders.fax.amount') + " " + I18n.t('orders.fax.name') + "\n"
# now display all ordered articles
order.order_articles.ordered.all(:include => [:article, :article_price]).each do |oa|
number = oa.article.order_number
@ -148,28 +126,4 @@ class OrdersController < ApplicationController
:type => 'text/plain; charset=utf-8; header=present',
:disposition => "attachment; filename=#{order.name}"
end
# Renders the matrix PDF.
def matrixPdf
@order = Order.find(params[:id])
unless @order.order_articles.ordered.empty?
prawnto :filename => "#{Date.today}_#{@order.name}_Matrix.pdf"
else
flash[:error] = "Es sind keine Artikel bestellt worden."
redirect_to @order
end
end
# adds a Comment to the Order
def add_comment
order = Order.find(params[:id])
comment = order.comments.build(params[:comment])
comment.user = @current_user
if !comment.text.empty? and comment.save
flash[:notice] = "Kommentar wurde erstellt."
else
flash[:error] = "Kommentar konnte nicht erstellt werden. Leerer Kommentar?"
end
redirect_to order
end
end

View File

@ -1,3 +1,4 @@
# encoding: utf-8
class PagesController < ApplicationController
def index
@ -16,7 +17,7 @@ class PagesController < ApplicationController
elsif params[:id]
page = Page.find_by_id(params[:id])
if page.nil?
flash[:error] = "Seite existiert nicht!"
flash[:error] = I18n.t('pages.cshow.error_noexist')
redirect_to all_pages_path and return
else
redirect_to wiki_page_path(page.permalink) and return
@ -28,7 +29,7 @@ class PagesController < ApplicationController
elsif @page.redirect?
page = Page.find_by_id(@page.redirect)
unless page.nil?
flash[:notice] = "Weitergeleitet von #{@page.title} ..."
flash[:notice] = I18n.t('pages.cshow.redirect_notice', :page => @page.title)
redirect_to wiki_page_path(page.permalink)
end
end
@ -56,7 +57,7 @@ class PagesController < ApplicationController
render :action => 'new'
else
if @page.save
flash[:notice] = 'Seite wurde angelegt.'
flash[:notice] = I18n.t('pages.create.notice')
redirect_to(wiki_page_path(@page.permalink))
else
render :action => "new"
@ -75,7 +76,7 @@ class PagesController < ApplicationController
if @page.save
@page.parent_id = parent_id if (!params[:parent_id].blank? \
and params[:parent_id] != @page_id)
flash[:notice] = 'Seite wurde aktualisiert.'
flash[:notice] = I18n.t('pages.update.notice')
redirect_to wiki_page_path(@page.permalink)
else
render :action => "edit"
@ -83,7 +84,7 @@ class PagesController < ApplicationController
end
rescue ActiveRecord::StaleObjectError
flash[:error] = "Achtung, die Seite wurde gerade von jemand anderes bearbeitet. Bitte versuche es erneut."
flash[:error] = I18n.t('pages.error_stale_object')
redirect_to wiki_page_path(@page.permalink)
end
@ -91,25 +92,27 @@ class PagesController < ApplicationController
@page = Page.find(params[:id])
@page.destroy
flash[:notice] = "Die Seite '#{@page.title}' und alle Unterseiten wurden erfolgreich gelöscht."
flash[:notice] = I18n.t('pages.destroy.notice', :page => @page.title)
redirect_to wiki_path
end
def all
@recent_pages = Page.non_redirected.all :order => 'updated_at DESC'
@pages = Page.non_redirected.all :order => 'title'
@top_pages = Page.no_parent.non_redirected.all :order => 'created_at'
@pages = Page.non_redirected
@partial = params[:view] || 'recent_changes'
view = params[:view]
params[:view] = nil
case view
when 'recentChanges'
render :partial => 'recent_changes' and return
when 'siteMap'
render :partial => 'site_map' and return
when 'titleList'
render :partial => 'title_list' and return
if params[:name]
@pages = @pages.where("title LIKE ?", "%#{params[:name]}%").limit(20).order('updated_at DESC')
@partial = 'title_list'
else
order = case @partial
when 'recent_changes' then
'updated_at DESC'
when 'site_map' then
'created_at DESC'
when 'title_list' then
'title DESC'
end
@pages.order(order)
end
end

View File

@ -0,0 +1,31 @@
class SessionsController < ApplicationController
skip_before_filter :authenticate
layout 'login'
def new
end
def create
user = User.authenticate(params[:nick], params[:password])
if user
session[:user_id] = user.id
session[:scope] = FoodsoftConfig.scope # Save scope in session to not allow switching between foodcoops with one account
if session[:return_to].present?
redirect_to_url = session[:return_to]
session[:return_to] = nil
else
redirect_to_url = root_url
end
redirect_to redirect_to_url, :notice => I18n.t('sessions.logged_in')
else
flash.now.alert = I18n.t('sessions.login_invalid')
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to login_url, :notice => I18n.t('sessions.logged_out')
end
end

View File

@ -1,76 +1,21 @@
class StockTakingsController < ApplicationController
inherit_resources
def index
@stock_takings = StockTaking.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @stock_takings }
end
end
def show
@stock_taking = StockTaking.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @stock_taking }
end
@stock_takings = StockTaking.order('date DESC').page(params[:page]).per(@per_page)
end
def new
@stock_taking = StockTaking.new
StockArticle.without_deleted.each { |a| @stock_taking.stock_changes.build(:stock_article => a) }
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @stock_taking }
end
end
def edit
@stock_taking = StockTaking.find(params[:id])
StockArticle.undeleted.each { |a| @stock_taking.stock_changes.build(:stock_article => a) }
end
def create
@stock_taking = StockTaking.new(params[:stock_taking])
respond_to do |format|
if @stock_taking.save
flash[:notice] = 'StockTaking was successfully created.'
format.html { redirect_to(@stock_taking) }
format.xml { render :xml => @stock_taking, :status => :created, :location => @stock_taking }
else
format.html { render :action => "new" }
format.xml { render :xml => @stock_taking.errors, :status => :unprocessable_entity }
end
end
create!(:notice => I18n.t('stock_takings.create.notice'))
end
def update
@stock_taking = StockTaking.find(params[:id])
respond_to do |format|
if @stock_taking.update_attributes(params[:stock_taking])
flash[:notice] = 'StockTaking was successfully updated.'
format.html { redirect_to(@stock_taking) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @stock_taking.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@stock_taking = StockTaking.find(params[:id])
@stock_taking.destroy
respond_to do |format|
format.html { redirect_to(stock_takings_url) }
format.xml { head :ok }
end
update!(:notice => I18n.t('stock_takings.update.notice'))
end
def fill_new_stock_article_form
@ -107,6 +52,4 @@ class StockTakingsController < ApplicationController
page.visual_effect :DropOut, "stock_change_#{stock_change.id}"
end
end
end

View File

@ -1,10 +1,8 @@
class StockitController < ApplicationController
def index
@stock_articles = StockArticle.without_deleted.all(
:include => [:supplier, :article_category],
:order => 'suppliers.name, article_categories.name, articles.name'
)
@stock_articles = StockArticle.undeleted.includes(:supplier, :article_category).
order('suppliers.name, article_categories.name, articles.name')
end
def new
@ -14,8 +12,7 @@ class StockitController < ApplicationController
def create
@stock_article = StockArticle.new(params[:stock_article])
if @stock_article.save
flash[:notice] = "Lagerartikel wurde gespeichert."
redirect_to stock_articles_path
redirect_to stock_articles_path, :notice => I18n.t('stockit.stock_create.notice')
else
render :action => 'new'
end
@ -28,31 +25,25 @@ class StockitController < ApplicationController
def update
@stock_article = StockArticle.find(params[:id])
if @stock_article.update_attributes(params[:stock_article])
flash[:notice] = "Lagerartikel wurde gespeichert."
redirect_to stock_articles_path
redirect_to stock_articles_path, :notice => I18n.t('stockit.stock_update.notice')
else
render :action => 'edit'
end
end
def destroy
StockArticle.find(params[:id]).destroy
redirect_to stock_articles_path
@article = StockArticle.find(params[:id])
@article.mark_as_deleted
render :layout => false
rescue => error
flash[:error] = "Ein Fehler ist aufgetreten: " + error.message
redirect_to stock_articles_path
render :partial => "destroy_fail", :layout => false,
:locals => { :fail_msg => I18n.t('errors.general_msg', :msg => error.message) }
end
def auto_complete_for_article_name
conditions = [ "LOWER(articles.name) LIKE ?", '%' + params[:article][:name].downcase + '%' ]
if params[:supplier_id]
@supplier = Supplier.find(params[:supplier_id])
@articles = @supplier.articles.without_deleted.all(:conditions => conditions, :limit => 8)
else
@articles = Article.without_deleted.not_in_stock.all(:conditions => conditions, :limit => 8)
end
render :partial => 'shared/auto_complete_articles'
#TODO: Fix this!!
def articles_search
@articles = Article.not_in_stock.limit(8).where('name LIKE ?', "%#{params[:term]}%")
render :json => @articles.map(&:name)
end
def fill_new_stock_article_form

View File

@ -1,9 +1,10 @@
# encoding: utf-8
class SuppliersController < ApplicationController
before_filter :authenticate_suppliers, :except => [:index, :list]
helper :deliveries
def index
@suppliers = Supplier.without_deleted :order => 'name'
@suppliers = Supplier.undeleted.order(:name)
@deliveries = Delivery.recent
end
@ -26,7 +27,7 @@ class SuppliersController < ApplicationController
def create
@supplier = Supplier.new(params[:supplier])
if @supplier.save
flash[:notice] = "Lieferant wurde erstellt"
flash[:notice] = I18n.t('suppliers.create.notice')
redirect_to suppliers_path
else
render :action => 'new'
@ -40,7 +41,7 @@ class SuppliersController < ApplicationController
def update
@supplier = Supplier.find(params[:id])
if @supplier.update_attributes(params[:supplier])
flash[:notice] = 'Lieferant wurde aktualisiert'
flash[:notice] = I18n.t('suppliers.update.notice')
redirect_to @supplier
else
render :action => 'edit'
@ -49,11 +50,11 @@ class SuppliersController < ApplicationController
def destroy
@supplier = Supplier.find(params[:id])
@supplier.destroy
flash[:notice] = "Lieferant wurde gelöscht"
@supplier.mark_as_deleted
flash[:notice] = I18n.t('suppliers.destroy.notice')
redirect_to suppliers_path
rescue => e
flash[:error] = "Ein Fehler ist aufgetreten: " + e.message
flash[:error] = I18n.t('errors.general_msg', :msg => e.message)
redirect_to @supplier
end

View File

@ -1,29 +1,25 @@
# encoding: utf-8
class TasksController < ApplicationController
#auto_complete_for :user, :nick
def index
@non_group_tasks = Task.non_group
@groups = Workgroup.all
@non_group_tasks = Task.non_group.includes(assignments: :user)
@groups = Workgroup.includes(open_tasks: {assignments: :user})
end
def user
@unaccepted_tasks = @current_user.unaccepted_tasks
@accepted_tasks = @current_user.accepted_tasks
@unaccepted_tasks = Task.unaccepted_tasks_for(current_user)
@accepted_tasks = Task.accepted_tasks_for(current_user)
end
def new
@task = Task.new
@task = Task.new(current_user_id: current_user.id)
end
def create
@task = Task.new(params[:task])
if @task.errors.empty? && @task.save
flash[:notice] = "Aufgabe wurde erstellt"
if @task.workgroup
redirect_to :action => "workgroup", :id => @task.workgroup
else
redirect_to :action => "index"
end
if @task.save
redirect_to tasks_url, :notice => I18n.t('tasks.create.notice')
else
render :template => "tasks/new"
end
@ -35,17 +31,18 @@ class TasksController < ApplicationController
def edit
@task = Task.find(params[:id])
@task.current_user_id = current_user.id
end
def update
@task = Task.find(params[:id])
@task.attributes=(params[:task])
if @task.errors.empty? && @task.save
flash[:notice] = "Aufgabe wurde aktualisiert"
flash[:notice] = I18n.t('tasks.update.notice')
if @task.workgroup
redirect_to :action => "workgroup", :id => @task.workgroup
redirect_to workgroup_tasks_url(workgroup_id: @task.workgroup_id)
else
redirect_to :action => "index"
redirect_to tasks_url
end
else
render :template => "tasks/edit"
@ -53,18 +50,13 @@ class TasksController < ApplicationController
end
def destroy
Task.find(params[:id]).destroy
flash[:notice] = "Aufgabe wurde gelöscht"
redirect_to :action => "index"
end
# Delete an given Assignment
# currently used in edit-view
def drop_assignment
ass = Assignment.find(params[:id])
task = ass.task
ass.destroy
redirect_to :action => "show", :id => task
task = Task.find(params[:id])
# Save user_ids to update apple statistics after destroy
user_ids = task.user_ids
task.destroy
task.update_ordergroup_stats(user_ids)
redirect_to tasks_url, :notice => I18n.t('tasks.destroy.notice')
end
# assign current_user to the task and set the assignment to "accepted"
@ -76,8 +68,7 @@ class TasksController < ApplicationController
else
task.assignments.create(:user => current_user, :accepted => true)
end
flash[:notice] = "Du hast die Aufgabe übernommen"
redirect_to user_tasks_path
redirect_to user_tasks_path, :notice => I18n.t('tasks.accept.notice')
end
# deletes assignment between current_user and given task
@ -86,34 +77,21 @@ class TasksController < ApplicationController
redirect_to :action => "index"
end
def update_status
Task.find(params[:id]).update_attribute("done", params[:task][:done])
flash[:notice] = "Aufgabenstatus wurde aktualisiert"
redirect_to :action => "index"
def set_done
Task.find(params[:id]).update_attribute :done, true
redirect_to tasks_url, :notice => I18n.t('tasks.set_done.notice')
end
# Shows all tasks, which are already done
def archive
@tasks = Task.done.paginate :page => params[:page], :per_page => 30
@tasks = Task.done.page(params[:page]).per(@per_page).order('tasks.updated_on DESC').includes(assignments: :user)
end
# shows workgroup (normal group) to edit weekly_tasks_template
def workgroup
@group = Group.find(params[:id])
@group = Group.find(params[:workgroup_id])
if @group.is_a? Ordergroup
flash[:error] = "Keine Arbeitsgruppe gefunden"
redirect_to :action => "index"
redirect_to tasks_url, :alert => I18n.t('tasks.error_not_found')
end
end
# this method is uses for the auto_complete-function from script.aculo.us
def auto_complete_for_task_user_list
@users = User.find(
:all,
:conditions => [ 'LOWER(nick) LIKE ?', '%' + params[:task][:user_list].downcase + '%' ],
:order => 'nick ASC',
:limit => 8
)
render :partial => '/shared/auto_complete_users'
end
end

View File

@ -0,0 +1,11 @@
class UsersController < ApplicationController
# Currently used to display users nick and ids for autocomplete
def index
@users = User.where("nick LIKE ?", "%#{params[:q]}%")
respond_to do |format|
format.json { render :json => @users.map { |u| u.token_attributes } }
end
end
end

View File

@ -0,0 +1,34 @@
# encoding: utf-8
class OrderByArticles < OrderPdf
def filename
I18n.t('documents.order_by_articles.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf'
end
def title
I18n.t('documents.order_by_articles.title', :name => @order.name,
:date => @order.ends.strftime(I18n.t('date.formats.default')))
end
def body
@order.order_articles.ordered.each do |order_article|
text "#{order_article.article.name} (#{order_article.article.unit} | #{order_article.price.unit_quantity.to_s} | #{number_with_precision(order_article.price.fc_price, precision: 2)})",
style: :bold, size: 10
rows = []
rows << I18n.t('documents.order_by_articles.rows')
for goa in order_article.group_order_articles
rows << [goa.group_order.ordergroup.name,
goa.result,
number_with_precision(order_article.price.fc_price * goa.result, precision: 2)]
end
table rows, column_widths: [200,40,40], cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.columns(1..2).align = :right
table.cells.border_width = 1
table.cells.border_color = '666666'
end
move_down 10
end
end
end

View File

@ -0,0 +1,52 @@
# encoding: utf-8
class OrderByGroups < OrderPdf
def filename
I18n.t('documents.order_by_groups.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf'
end
def title
I18n.t('documents.order_by_groups.title', :name => @order.name,
:date => @order.ends.strftime(I18n.t('date.formats.default')))
end
def body
# Start rendering
@order.group_orders.each do |group_order|
text group_order.ordergroup.name, size: 9, style: :bold
total = 0
rows = []
rows << I18n.t('documents.order_by_groups.rows') # Table Header
group_order_articles = group_order.group_order_articles.ordered
group_order_articles.each do |goa|
price = goa.order_article.price.fc_price
sub_total = price * goa.result
total += sub_total
rows << [goa.order_article.article.name,
goa.result,
number_with_precision(price, precision: 2),
goa.order_article.price.unit_quantity,
goa.order_article.article.unit,
number_with_precision(sub_total, precision: 2)]
end
rows << [ I18n.t('documents.order_by_groups.sum'), nil, nil, nil, nil, number_with_precision(total, precision: 2)]
table rows, column_widths: [250,50,50,50,50,50], cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
# borders
table.cells.borders = []
table.row(0).borders = [:bottom]
table.row(group_order_articles.size).borders = [:bottom]
table.cells.border_width = 1
table.cells.border_color = '666666'
table.columns(1..3).align = :right
table.columns(5).align = :right
end
move_down 15
end
end
end

View File

@ -0,0 +1,72 @@
# encoding: utf-8
class OrderFax < OrderPdf
def filename
I18n.t('documents.order_fax.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf'
end
def title
false
end
def body
contact = FoodsoftConfig[:contact].symbolize_keys
# From paragraph
bounding_box [margin_box.right-200,margin_box.top], width: 200 do
text FoodsoftConfig[:name], size: 9, align: :right
move_down 5
text contact[:street], size: 9, align: :right
move_down 5
text "#{contact[:zip_code]} #{contact[:city]}", size: 9, align: :right
move_down 5
text "#{I18n.t('simple_form.labels.supplier.customer_number')}: #{@order.supplier.try(:customer_number)}", size: 9, align: :right
move_down 5
text "#{I18n.t('simple_form.labels.supplier.phone')}: #{contact[:phone]}", size: 9, align: :right
move_down 5
text "#{I18n.t('simple_form.labels.supplier.email')}: #{contact[:email]}", size: 9, align: :right
end
# Recipient
bounding_box [margin_box.left,margin_box.top-60], width: 200 do
text @order.name
move_down 5
text @order.supplier.try(:address).to_s
move_down 5
text "#{I18n.t('simple_form.labels.supplier.fax')}: #{@order.supplier.try(:fax)}"
end
move_down 5
text Date.today.strftime(I18n.t('date.formats.default')), align: :right
move_down 10
text "#{I18n.t('simple_form.labels.delivery.delivered_on')}:"
move_down 10
text "#{I18n.t('simple_form.labels.supplier.contact_person')}: #{@order.supplier.try(:contact_person)}"
move_down 10
# Articles
data = [I18n.t('documents.order_fax.rows')]
data = @order.order_articles.ordered.all(include: :article).collect do |a|
[a.article.order_number,
a.units_to_order,
a.article.name,
a.price.unit_quantity,
a.article.unit,
a.price.price]
end
table data, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.cells.border_width = 1
table.cells.border_color = '666666'
table.columns(1).align = :right
table.columns(3..5).align = :right
end
#font_size: 8,
#vertical_padding: 3,
#border_style: :grid,
#headers: ["BestellNr.", "Menge","Name", "Gebinde", "Einheit","Preis/Einheit"],
#align: {0 => :left}
end
end

View File

@ -0,0 +1,87 @@
# encoding: utf-8
class OrderMatrix < OrderPdf
MAX_ARTICLES_PER_PAGE = 16 # How many order_articles shoud written on a page
def filename
I18n.t('documents.order_matrix.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf'
end
def title
I18n.t('documents.order_matrix.title', :name => @order.name,
:date => @order.ends.strftime(I18n.t('date.formats.default')))
end
def body
order_articles = @order.order_articles.ordered
text I18n.t('documents.order_matrix.heading'), style: :bold
move_down 5
text I18n.t('documents.order_matrix.total', :count => order_articles.size), size: 8
move_down 10
order_articles_data = [I18n.t('documents.order_matrix.rows')]
order_articles.each do |a|
order_articles_data << [a.article.name,
a.article.unit,
a.price.unit_quantity,
number_with_precision(a.price.fc_price, precision: 2),
a.units_to_order]
end
table order_articles_data, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.cells.border_width = 1
table.cells.border_color = '666666'
end
page_number = 0
total_num_order_articles = order_articles.size
while page_number * MAX_ARTICLES_PER_PAGE < total_num_order_articles do # Start page generating
page_number += 1
start_new_page(layout: :landscape)
# Collect order_articles for this page
current_order_articles = order_articles.select do |a|
order_articles.index(a) >= (page_number-1) * MAX_ARTICLES_PER_PAGE and
order_articles.index(a) < page_number * MAX_ARTICLES_PER_PAGE
end
# Make order_articles header
header = [""]
for header_article in current_order_articles
name = header_article.article.name.gsub(/[-\/]/, " ").gsub(".", ". ")
name = name.split.collect { |w| w.truncate(8) }.join(" ")
header << name.truncate(30)
end
# Collect group results
groups_data = [header]
@order.group_orders.includes(:ordergroup).all.each do |group_order|
group_result = [group_order.ordergroup.name.truncate(20)]
for order_article in current_order_articles
# get the Ordergroup result for this order_article
goa = order_article.group_order_articles.first conditions: { group_order_id: group_order.id }
group_result << ((goa.nil? || goa.result == 0) ? "" : goa.result.to_i)
end
groups_data << group_result
end
# Make table
column_widths = [85]
(MAX_ARTICLES_PER_PAGE + 1).times { |i| column_widths << 41 unless i == 0 }
table groups_data, column_widths: column_widths, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.cells.border_width = 1
table.cells.border_color = '666666'
table.row_colors = ['ffffff','ececec']
end
end
end
end

View File

@ -1,3 +1,5 @@
# encoding: utf-8
#
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
@ -18,97 +20,76 @@ module ApplicationHelper
end
# Creates ajax-controlled-links for pagination
# see also the plugin "will_paginate"
def pagination_links_remote(collection, options = {})
per_page = options[:per_page] || @per_page
params = options[:params] || {}
update = options[:update] || nil
# Translations
previous_label = '&laquo; ' + "Vorherige"
next_label = "Nächste" + ' &raquo;'
# Merge other url-options for will_paginate
params = params.merge({:per_page => per_page})
will_paginate collection, { :params => params, :remote => true, :update => update,
:previous_label => previous_label, :next_label => next_label, }
paginate collection, :params => params, :remote => true
end
# Link-collection for per_page-options when using the pagination-plugin
def items_per_page(options = {})
per_page_options = options[:per_page_options] || [20, 50, 100]
current = options[:current] || @per_page
action = options[:action] || controller.action_name
update = options[:update] || nil
params = params || {}
links = []
per_page_options.each do |per_page|
unless per_page == current
links << link_to_remote(
per_page,
{ :url => { :action => action, :params => {:per_page => per_page}},
:before => "Element.show('loader')",
:success => "Element.hide('loader')",
:method => :get, :update => update } )
else
links << per_page
end
links = per_page_options.map do |per_page|
params.merge!({:per_page => per_page})
link_class = 'btn'
link_class << ' disabled' if per_page == current
link_to(per_page, params, :remote => true, class: link_class)
end
return "Pro Seite: " + links.join(" ")
content_tag :div, class: 'btn-group pull-right' do
links.join.html_safe
end
end
def sort_td_class_helper(param)
result = 'class="sortup"' if params[:sort] == param
result = 'class="sortdown"' if params[:sort] == param + "_reverse"
return result
end
def sort_link_helper(text, key, options = {})
per_page = options[:per_page] || 10
action = options[:action] || "list"
# Hmtl options
remote = options[:remote].nil? ? true : options[:remote]
key += "_reverse" if params[:sort] == key
link_options = {
:url => url_for(:params => params.merge({:sort => key, :page => nil, :per_page => per_page})),
:before => "Element.show('loader')",
:success => "Element.hide('loader')",
:method => :get
}
class_name = case params[:sort]
when key then
'sortup'
when key + '_reverse' then
'sortdown'
else
nil
end
html_options = {
:title => _("Nach #{text} sortieren"),
:href => url_for(:action => action, :params => params.merge({:sort => key, :page => nil, :per_page => per_page}))
:title => I18n.t('helpers.application.sort_by', text: text),
:remote => remote,
:class => class_name
}
if remote
link_to_remote(text, link_options, html_options)
else
link_to(text, link_options[:url], html_options)
end
# Url options
key += "_reverse" if params[:sort] == key
per_page = options[:per_page] || @per_page
url_options = params.merge(per_page: per_page, sort: key)
url_options.merge!({page: params[:page]}) if params[:page]
url_options.merge!({query: params[:query]}) if params[:query]
link_to(text, url_for(url_options), html_options)
end
# Generates a link to the top of the website
def link_to_top
link_to image_tag("arrow_up_red.png", :size => "16x16", :border => "0", :alt => "Nach oben"), "#"
link_to '#' do
content_tag :i, nil, class: 'icon-arrow-up icon-large'
end
end
# Returns the weekday. 0 is sunday, 1 is monday and so on
def weekday(dayNumber)
weekdays = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"]
weekdays = I18n.t('date.day_names')
return weekdays[dayNumber]
end
# highlights a phrase in given text
# based on the rails text-helper 'highlight'
def highlight_phrase(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
unless phrase.blank? || text.nil?
phrase.split(' ').each {|keyword| text.gsub!(/(#{Regexp.escape(keyword)})/i, highlighter)}
end
return text
end
# to set a title for both the h1-tag and the title in the header
def title(page_title, show_title = true)
@content_for_title = page_title.to_s
content_for(:title) { page_title.to_s }
@show_title = show_title
end
@ -121,8 +102,11 @@ module ApplicationHelper
end
def icon(name, options={})
icons = { :delete => { :file => 'b_drop.png', :alt => 'Löschen'},
:edit => { :file => 'b_edit.png', :alt => 'Bearbeiten'}}
icons = {
:delete => { :file => 'b_drop.png', :alt => I18n.t('ui.delete')},
:edit => { :file => 'b_edit.png', :alt => I18n.t('ui.edit')},
:members => { :file => 'b_users.png', :alt => I18n.t('helpers.application.edit_user')}
}
options[:alt] ||= icons[name][:alt]
options[:title] ||= icons[name][:title]
options.merge!({:size => '16x16',:border => "0"})
@ -137,28 +121,42 @@ module ApplicationHelper
:success => "Element.hide('loader')",
:method => :get
}
link_to_remote(text, remote_options.merge(options))
link_to(text, options[:url], remote_options.merge(options))
end
def format_roles(record)
roles = []
roles << 'Admin' if record.role_admin?
roles << 'Finanzen' if record.role_finance?
roles << 'Lieferanten' if record.role_suppliers?
roles << 'Artikel' if record.role_article_meta?
roles << 'Bestellung' if record.role_orders?
roles << I18n.t('helpers.application.role_admin') if record.role_admin?
roles << I18n.t('helpers.application.role_finance') if record.role_finance?
roles << I18n.t('helpers.application.role_suppliers') if record.role_suppliers?
roles << I18n.t('helpers.application.role_article_meta') if record.role_article_meta?
roles << I18n.t('helpers.application.role_orders') if record.role_orders?
roles.join(', ')
end
def link_to_gmaps(address)
link_to h(address), "http://maps.google.de/?q=#{h(address)}", :title => "Show it on google maps",
link_to h(address), "http://maps.google.com/?q=#{h(address)}", :title => I18n.t('helpers.application.show_google_maps'),
:target => "_blank"
end
# offers a link for writing message to user
# checks for nil (useful for relations)
def link_to_user_message_if_valid(user)
user.nil? ? '??' : ( link_to user.nick, user_message_path(user), :title => _('Nachricht schreiben') )
user.nil? ? '??' : link_to(user.nick, new_message_path('message[mail_to]' => user.id),
:title => I18n.t('helpers.application.write_message'))
end
def bootstrap_flash
flash_messages = []
flash.each do |type, message|
type = :success if type == :notice
type = :error if type == :alert
text = content_tag(:div,
content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => "close", "data-dismiss" => "alert") +
message, :class => "alert fade in alert-#{type}")
flash_messages << text if message
end
flash_messages.join("\n").html_safe
end
end

View File

@ -6,9 +6,14 @@ module ArticlesHelper
end
def row_classes(article)
classes = " click-me"
classes += " unavailable" if !article.availability
classes += " just_updated" if @article.recently_updated && @article.availability
classes
classes = []
classes << "unavailable" if !article.availability
classes << "just-updated" if article.recently_updated && article.availability
classes.join(" ")
end
# Flatten search params, used in import from external database
def search_params
Hash[params[:search].map { |k,v| [k, (v.is_a?(Array) ? v.join(" ") : v)] }]
end
end

View File

@ -3,14 +3,15 @@ module DeliveriesHelper
def link_to_invoice(delivery)
if delivery.invoice
link_to number_to_currency(delivery.invoice.amount), [:finance, delivery.invoice],
:title => "Rechnung anzeigen"
title: I18n.t('helpers.deliveries.show_invoice')
else
link_to "Rechnung anlegen", new_finance_invoice_path(:supplier_id => delivery.supplier.id, :delivery_id => delivery.id)
link_to I18n.t('helpers.deliveries.new_invoice'), new_finance_invoice_path(supplier_id: delivery.supplier.id, delivery_id: delivery.id),
class: 'btn btn-mini'
end
end
def stock_articles_for_select(supplier)
supplier.stock_articles.without_deleted.collect {|a| ["#{a.name} (#{number_to_currency a.price}/#{a.unit})", a.id] }
supplier.stock_articles.undeleted.map {|a| ["#{a.name} (#{number_to_currency a.price}/#{a.unit})", a.id] }
end
end

View File

@ -0,0 +1,13 @@
module Finance::BalancingHelper
def balancing_view_partial
view = params[:view] || 'edit_results'
case view
when 'edit_results' then
'edit_results_by_articles'
when 'groups_overview' then
'shared/articles_by_groups'
when 'articles_overview' then
'shared/articles_by_articles'
end
end
end

View File

@ -0,0 +1,10 @@
module Finance::OrderArticlesHelper
def new_order_articles_collection
if @order.stockit?
StockArticle.order('articles.name')
else
@order.supplier.articles.order('articles.name')
end
end
end

View File

@ -0,0 +1,2 @@
module Finance::OrdergroupsHelper
end

View File

@ -0,0 +1,39 @@
module GroupOrdersHelper
def data_to_js(ordering_data)
ordering_data[:order_articles].map { |id, data|
[id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance], data[:used_quantity], data[:quantity_available]]
}.map { |row|
"addData(#{row.join(', ')});"
}.join("\n")
end
def link_to_ordering(order, options = {})
path = if group_order = order.group_order(current_user.ordergroup)
edit_group_order_path(group_order, :order_id => order.id)
else
new_group_order_path(:order_id => order.id)
end
link_to order.name, path, options
end
# Return css class names for order result table
def order_article_class_name(quantity, tolerance, result)
if (quantity + tolerance > 0)
result > 0 ? 'success' : 'failed'
else
'ignored'
end
end
def get_order_results(order_article, group_order_id)
goa = order_article.group_order_articles.detect { |goa| goa.group_order_id == group_order_id }
quantity, tolerance, result, sub_total = if goa.present?
[goa.quantity, goa.tolerance, goa.result, goa.total_price(order_article)]
else
[0, 0, 0, 0]
end
{group_order_article: goa, quantity: quantity, tolerance: tolerance, result: result, sub_total: sub_total}
end
end

View File

@ -1,16 +1,4 @@
module MessagesHelper
def groups_for_select
groups = [[" -- Arbeitsgruppen -- ", ""]]
groups += Workgroup.find(:all, :order => 'name', :include => :memberships).reject{ |g| g.memberships.empty? }.collect do |g|
[g.name, g.id]
end
groups += [[" -- Bestellgruppen -- ", ""]]
groups += Ordergroup.without_deleted(:order => 'name', :include => :memberships).reject{ |g| g.memberships.empty? }.collect do |g|
[g.name, g.id]
end
groups
end
def format_subject(message, length)
if message.subject.length > length
subject = truncate(message.subject, :length => length)
@ -19,6 +7,14 @@ module MessagesHelper
subject = message.subject
body = truncate(message.body, :length => length - subject.length)
end
"<b>#{link_to(h(subject), message)}</b> <span style='color:grey'>#{h(body)}</span>"
"<b>#{link_to(h(subject), message)}</b> <span style='color:grey'>#{h(body)}</span>".html_safe
end
def link_to_new_message(options = {})
messages_params = options[:message_params] || nil
link_text = content_tag :id, nil, class: 'icon-envelope'
link_text << " #{options[:text]}" if options[:text].present?
link_to(link_text.html_safe, new_message_path(message: messages_params), class: 'btn',
title: I18n.t('helpers.submit.message.create'))
end
end

View File

@ -0,0 +1,2 @@
module OrderCommentsHelper
end

View File

@ -1,19 +1,18 @@
# encoding: utf-8
module OrdersHelper
def update_articles_link(order, text, view)
link_to_remote text, :url => order_path(order, :view => view),
:update => 'articles', :before => "Element.show('loader')", :success => "Element.hide('loader')",
:method => :get
link_to text, order_path(order, view: view), remote: true
end
def link_to_pdf(order, action)
link_to image_tag("save_pdf.png", :size => "16x16", :border => "0", :alt => "PDF erstellen"),
{ :action => action, :id => order, :format => :pdf }, { :title => "PDF erstellen" }
def order_pdf(order, document, text)
link_to text, order_path(order, document: document, format: :pdf), title: I18n.t('helpers.orders.order_pdf')
end
def options_for_suppliers_to_select
suppliers = Supplier.without_deleted.collect {|s| [ s.name, url_for(:action => "new", :supplier_id => s)] }
stockit = [["Lager", url_for(:action => 'new', :supplier_id => 0)]]
options_for_select(stockit + suppliers)
options = [[I18n.t('helpers.orders.option_choose')]]
options += Supplier.all.map {|s| [ s.name, url_for(action: "new", supplier_id: s)] }
options += [[I18n.t('helpers.orders.option_stock'), url_for(action: 'new', supplier_id: 0)]]
options_for_select(options)
end
end

View File

@ -2,14 +2,14 @@ module PagesHelper
include WikiCloth
def wikified_body(body, title = nil)
WikiCloth.new({:data => body+"\n", :link_handler => Wikilink.new, :params => {:referer => title}}).to_html
WikiCloth.new({:data => body+"\n", :link_handler => Wikilink.new, :params => {:referer => title}}).to_html.html_safe
end
def link_to_wikipage(page, text = nil)
if text == nil
link_to page.title, wiki_page_path(page.permalink)
link_to page.title, wiki_page_path(:permalink => page.permalink)
else
link_to text, wiki_page_path(page.permalink)
link_to text, wiki_page_path(:permalink => page.permalink)
end
end
@ -41,11 +41,11 @@ module PagesHelper
unless toc.blank?
toc = WikiCloth.new({:data => toc, :link_handler => Wikilink.new}).to_html
section_count = 0
toc.gsub(/<li>([^<>\n]*)/) do
section_count += 1
"<li><a href='#section-#{section_count}'>#{$1}</a>"
end
name = $1
anchor = name.gsub(/\s/, '_').gsub(/[^a-zA-Z_]/, '')
"<li><a href='##{anchor}'>#{name.truncate(20)}</a>"
end.html_safe
end
end

View File

@ -0,0 +1,2 @@
module SessionsHelper
end

View File

@ -1,2 +1,7 @@
module StockitHelper
def stock_article_classes(article)
class_names = []
class_names << "unavailable" if article.quantity_available <= 0
class_names.join(" ")
end
end

View File

@ -1,10 +1,16 @@
module TasksHelper
def task_assignments(task)
task.assignments.map do |ass|
content_tag :span, ass.user.nick, :class => (ass.accepted? ? 'accepted' : 'unaccepted')
end.join(", ").html_safe
end
# generate colored number of still required users
def highlighted_required_users(task)
unless task.enough_users_assigned?
still_required = task.required_users - task.assignments.select { |ass| ass.accepted }.size
"<small style='color:red;font-weight:bold'>(#{still_required})</small>"
content_tag :span, task.still_required_users, class: 'badge badge-important',
title: I18n.t('helpers.tasks.required_users', :count => task.still_required_users)
end
end
end

View File

@ -0,0 +1,2 @@
module UsersHelper
end

View File

@ -0,0 +1,5 @@
class DatePickerInput < SimpleForm::Inputs::StringInput
def input
@builder.text_field(attribute_name, input_html_options.merge({class: 'datepicker'}))
end
end

101
app/mailers/mailer.rb Normal file
View File

@ -0,0 +1,101 @@
# encoding: utf-8
# ActionMailer class that handles all emails for the FoodSoft.
class Mailer < ActionMailer::Base
layout 'email' # Use views/layouts/email.txt.erb
default from: "FoodSoft <#{FoodsoftConfig[:email_sender]}>",
sender: FoodsoftConfig[:email_sender],
errors_to: FoodsoftConfig[:email_sender]
# Sends an email copy of the given internal foodsoft message.
def foodsoft_message(message, recipient)
set_foodcoop_scope
@message = message
mail subject: "[#{FoodsoftConfig[:name]}] " + message.subject,
to: recipient.email,
from: "#{message.sender.nick} <#{message.sender.email}>"
end
# Sends an email with instructions on how to reset the password.
# Assumes user.setResetPasswordToken has been successfully called already.
def reset_password(user)
set_foodcoop_scope
@user = user
@link = new_password_url(id: @user.id, token: @user.reset_password_token)
mail :to => @user.email,
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.reset_password.subject', :username => @user.nick)
end
# Sends an invite email.
def invite(invite)
set_foodcoop_scope
@invite = invite
@link = accept_invitation_url(token: @invite.token)
mail :to => @invite.email,
:subject => I18n.t('mailer.invite.subject')
end
# Notify user of upcoming task.
def upcoming_tasks(user, task)
set_foodcoop_scope
@user = user
@task = task
mail :to => user.email,
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.upcoming_tasks.subject')
end
# Sends order result for specific Ordergroup
def order_result(user, group_order)
set_foodcoop_scope
@order = group_order.order
@group_order = group_order
mail :to => user.email,
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.order_result.subject', :name => group_order.order.name)
end
# Notify user if account balance is less than zero
def negative_balance(user,transaction)
set_foodcoop_scope
@group = user.ordergroup
@transaction = transaction
mail :to => user.email,
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.negative_balance')
end
def feedback(user, feedback)
set_foodcoop_scope
@user = user
@feedback = feedback
mail :to => FoodsoftConfig[:notification]["error_recipients"],
:from => "#{user.nick} <#{user.email}>",
:sender => FoodsoftConfig[:notification]["sender_address"],
:errors_to => FoodsoftConfig[:notification]["sender_address"],
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.feedback.subject', :email => user.email)
end
def not_enough_users_assigned(task, user)
set_foodcoop_scope
@task = task
@user = user
mail :to => user.email,
:subject => "[#{FoodsoftConfig[:name]}] " + I18n.t('mailer.not_enough_users_assigned.subject', :task => task.name)
end
private
def set_foodcoop_scope(foodcoop = FoodsoftConfig.scope)
ActionMailer::Base.default_url_options[:protocol] = FoodsoftConfig[:protocol]
ActionMailer::Base.default_url_options[:host] = FoodsoftConfig[:host]
ActionMailer::Base.default_url_options[:foodcoop] = foodcoop
end
end

View File

@ -1,67 +1,31 @@
# == Schema Information
#
# Table name: articles
#
# id :integer not null, primary key
# name :string(255) default(""), not null
# supplier_id :integer default(0), not null
# article_category_id :integer default(0), not null
# unit :string(255) default(""), not null
# note :string(255)
# availability :boolean default(TRUE), not null
# manufacturer :string(255)
# origin :string(255)
# shared_updated_on :datetime
# price :decimal(, )
# tax :float
# deposit :decimal(, ) default(0.0)
# unit_quantity :integer default(1), not null
# order_number :string(255)
# created_at :datetime
# updated_at :datetime
# quantity :integer default(0)
# deleted_at :datetime
# type :string(255)
#
# encoding: utf-8
class Article < ActiveRecord::Base
acts_as_paranoid # Avoid deleting the article for consistency of order-results
extend ActiveSupport::Memoizable # Ability to cache method results. Use memoize :expensive_method
# Replace numeric seperator with database format
localize_input_of :price, :tax, :deposit
# Associations
belongs_to :supplier
belongs_to :article_category
has_many :article_prices, :order => "created_at DESC"
named_scope :available, :conditions => {:availability => true}
named_scope :not_in_stock, :conditions => {:type => nil}
scope :undeleted, -> { where(deleted_at: nil) }
scope :available, -> { undeleted.where(availability: true) }
scope :not_in_stock, :conditions => {:type => nil}
# Validations
validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category_id
validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category
validates_length_of :name, :in => 4..60
validates_length_of :unit, :in => 2..15
validates_numericality_of :price, :unit_quantity, :greater_than => 0
validates_numericality_of :price, :greater_than_or_equal_to => 0
validates_numericality_of :unit_quantity, :greater_than => 0
validates_numericality_of :deposit, :tax
validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type]
# Callbacks
before_save :update_price_history
before_destroy :check_article_in_use
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def price=(price)
self[:price] = String.delocalized_decimal(price)
end
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def tax=(tax)
self[:tax] = String.delocalized_decimal(tax)
end
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def deposit=(deposit)
self[:deposit] = String.delocalized_decimal(deposit)
end
# The financial gross, net plus tax and deposti
def gross_price
@ -70,7 +34,7 @@ class Article < ActiveRecord::Base
# The price for the foodcoop-member.
def fc_price
(gross_price * (Foodsoft.config[:price_markup] / 100 + 1)).round(2)
(gross_price * (FoodsoftConfig[:price_markup] / 100 + 1)).round(2)
end
# Returns true if article has been updated at least 2 days ago
@ -86,6 +50,11 @@ class Article < ActiveRecord::Base
end
memoize :in_open_order
# Returns true if the article has been ordered in the given order at least once
def ordered_in_order?(order)
order.order_articles.where(article_id: id).where('quantity > 0').one?
end
# this method checks, if the shared_article has been changed
# unequal attributes will returned in array
# if only the timestamps differ and the attributes are equal,
@ -157,8 +126,8 @@ class Article < ActiveRecord::Base
false
end
else # get factors for fc and supplier
fc_unit_factor = Foodsoft.config[:units][self.unit]
supplier_unit_factor = Foodsoft.config[:units][self.shared_article.unit]
fc_unit_factor = FoodsoftConfig[:units][self.unit]
supplier_unit_factor = FoodsoftConfig[:units][self.shared_article.unit]
if fc_unit_factor and supplier_unit_factor
convertion_factor = fc_unit_factor / supplier_unit_factor
new_price = BigDecimal((convertion_factor * shared_article.price).to_s).round(2)
@ -173,11 +142,20 @@ class Article < ActiveRecord::Base
end
end
def deleted?
deleted_at.present?
end
def mark_as_deleted
check_article_in_use
update_column :deleted_at, Time.now
end
protected
# Checks if the article is in use before it will deleted
def check_article_in_use
raise self.name.to_s + " kann nicht gelöscht werden. Der Artikel befindet sich in einer laufenden Bestellung!" if self.in_open_order
raise I18n.t('articles.model.error_in_use', :article => self.name.to_s) if self.in_open_order
end
# Create an ArticlePrice, when the price-attr are changed.

View File

@ -1,17 +1,15 @@
class ArticleCategory < ActiveRecord::Base
has_many :articles
validates_length_of :name, :in => 2..20
validates_uniqueness_of :name
validates :name, :presence => true, :uniqueness => true, :length => { :in => 2..20 }
before_destroy :check_for_associated_articles
protected
def check_for_associated_articles
raise I18n.t('activerecord.errors.has_many_left', collection: Article.model_name.human) if articles.undeleted.exists?
end
end
# == Schema Information
#
# Table name: article_categories
#
# id :integer(4) not null, primary key
# name :string(255) default(""), not null
# description :string(255)
#

View File

@ -4,22 +4,11 @@ class ArticlePrice < ActiveRecord::Base
has_many :order_articles
validates_presence_of :price, :tax, :deposit, :unit_quantity
validates_numericality_of :price, :unit_quantity, :greater_than => 0
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def price=(price)
self[:price] = String.delocalized_decimal(price)
end
validates_numericality_of :price, :greater_than_or_equal_to => 0
validates_numericality_of :unit_quantity, :greater_than => 0
validates_numericality_of :deposit, :tax
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def tax=(tax)
self[:tax] = String.delocalized_decimal(tax)
end
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def deposit=(deposit)
self[:deposit] = String.delocalized_decimal(deposit)
end
localize_input_of :price, :tax, :deposit
# The financial gross, net plus tax and deposit.
def gross_price
@ -28,20 +17,7 @@ class ArticlePrice < ActiveRecord::Base
# The price for the foodcoop-member.
def fc_price
(gross_price * (Foodsoft.config[:price_markup] / 100 + 1)).round(2)
(gross_price * (FoodsoftConfig[:price_markup] / 100 + 1)).round(2)
end
end
# == Schema Information
#
# Table name: article_prices
#
# id :integer(4) not null, primary key
# article_id :integer(4)
# price :decimal(8, 2) default(0.0), not null
# tax :decimal(8, 2) default(0.0), not null
# deposit :decimal(8, 2) default(0.0), not null
# unit_quantity :integer(4)
# created_at :datetime
#

View File

@ -2,26 +2,6 @@ class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :task
# after user is assigned mark task as assigned
def after_create
self.task.update_attribute(:assigned, true)
end
# update assigned-attribute
def after_destroy
self.task.update_attribute(:assigned, false) if self.task.assignments.empty?
end
end
# == Schema Information
#
# Table name: assignments
#
# id :integer(4) not null, primary key
# user_id :integer(4) default(0), not null
# task_id :integer(4) default(0), not null
# accepted :boolean(1) default(FALSE)
#

View File

@ -4,7 +4,7 @@ class Delivery < ActiveRecord::Base
has_one :invoice
has_many :stock_changes, :dependent => :destroy
named_scope :recent, :order => 'created_at DESC', :limit => 10
scope :recent, :order => 'created_at DESC', :limit => 10
validates_presence_of :supplier_id
@ -19,14 +19,3 @@ class Delivery < ActiveRecord::Base
end
# == Schema Information
#
# Table name: deliveries
#
# id :integer(4) not null, primary key
# supplier_id :integer(4)
# delivered_on :date
# created_at :datetime
# note :text
#

View File

@ -4,25 +4,14 @@ class FinancialTransaction < ActiveRecord::Base
belongs_to :ordergroup
belongs_to :user
validates_presence_of :note, :user_id, :ordergroup_id
validates_presence_of :amount, :note, :user_id, :ordergroup_id
validates_numericality_of :amount
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def amount=(amount)
self[:amount] = String.delocalized_decimal(amount)
end
localize_input_of :amount
# Use this save method instead of simple save and after callback
def add_transaction!
ordergroup.add_financial_transaction! amount, note, user
end
end
# == Schema Information
#
# Table name: financial_transactions
#
# id :integer(4) not null, primary key
# ordergroup_id :integer(4) default(0), not null
# amount :decimal(8, 2) default(0.0), not null
# note :text default(""), not null
# user_id :integer(4) default(0), not null
# created_on :datetime not null
#

View File

@ -1,12 +1,15 @@
# Groups organize the User.
# A Member gets the roles from the Group
class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :memberships
has_many :users, :through => :memberships
validates :name, :presence => true, :length => {:in => 1..25}
validates_length_of :name, :in => 1..25
validates_uniqueness_of :name
attr_reader :user_tokens
scope :undeleted, -> { where(deleted_at: nil) }
# Returns true if the given user if is an member of this group.
def member?(user)
memberships.find_by_user_id(user.id)
@ -16,58 +19,23 @@ class Group < ActiveRecord::Base
def non_members
User.all(:order => 'nick').reject { |u| users.include?(u) }
end
# Check before destroy a group, if this is the last group with admin role
def before_destroy
if self.role_admin == true && Group.find_all_by_role_admin(true).size == 1
raise "Die letzte Gruppe mit Admin-Rechten darf nicht gelöscht werden"
end
end
protected
# validates uniqueness of the Group.name. Checks groups and ordergroups
def validate
errors.add(:name, "ist schon vergeben") if (group = Group.find_by_name(name) || group = Ordergroup.find_by_name(name)) && self != group
def user_tokens=(ids)
self.user_ids = ids.split(",")
end
# add validation check on update
def validate_on_update
# error if this is the last group with admin role and role_admin should set to false
if self.role_admin == false && Group.find_all_by_role_admin(true).size == 1 && self == Group.find(:first, :conditions => "role_admin = 1")
errors.add(:role_admin, "Der letzten Gruppe mit Admin-Rechten darf die Admin-Rolle nicht entzogen werden")
def deleted?
deleted_at.present?
end
def mark_as_deleted
# TODO: Checks for participating in not closed orders
transaction do
memberships.destroy_all
# TODO: What should happen to users?
update_column :deleted_at, Time.now
end
end
end
# == Schema Information
#
# Table name: groups
#
# id :integer(4) not null, primary key
# type :string(255) default(""), not null
# name :string(255) default(""), not null
# description :string(255)
# account_balance :decimal(8, 2) default(0.0), not null
# account_updated :datetime
# created_on :datetime not null
# role_admin :boolean(1) default(FALSE), not null
# role_suppliers :boolean(1) default(FALSE), not null
# role_article_meta :boolean(1) default(FALSE), not null
# role_finance :boolean(1) default(FALSE), not null
# role_orders :boolean(1) default(FALSE), not null
# weekly_task :boolean(1) default(FALSE)
# weekday :integer(4)
# task_name :string(255)
# task_description :string(255)
# task_required_users :integer(4) default(1)
# deleted_at :datetime
# contact_person :string(255)
# contact_phone :string(255)
# contact_address :string(255)
# stats :text
# task_duration :integer(4) default(1)
#

View File

@ -1,6 +1,8 @@
# A GroupOrder represents an Order placed by an Ordergroup.
class GroupOrder < ActiveRecord::Base
attr_accessor :group_order_articles_attributes
belongs_to :order
belongs_to :ordergroup
has_many :group_order_articles, :dependent => :destroy
@ -12,41 +14,74 @@ class GroupOrder < ActiveRecord::Base
validates_numericality_of :price
validates_uniqueness_of :ordergroup_id, :scope => :order_id # order groups can only order once per order
named_scope :open, lambda { {:conditions => ["order_id IN (?)", Order.open.collect(&:id)]} }
named_scope :finished, lambda { {:conditions => ["order_id IN (?)", Order.finished_not_closed.collect(&:id)]} }
# Updates the "price" attribute.
# Until the order is finished this will be the maximum price or
# the minimum price depending on configuration. When the order is finished it
# will be the value depending of the article results.
def update_price!
total = 0
for article in group_order_articles.find(:all, :include => :order_article)
unless order.finished?
if Foodsoft.config[:tolerance_is_costly]
total += article.order_article.article.fc_price * (article.quantity + article.tolerance)
else
total += article.order_article.article.fc_price * article.quantity
end
else
total += article.order_article.price.fc_price * article.result
scope :in_open_orders, joins(:order).merge(Order.open)
scope :in_finished_orders, joins(:order).merge(Order.finished_not_closed)
# Generate some data for the javascript methods in ordering view
def load_data
data = {}
data[:available_funds] = ordergroup.get_available_funds(self)
# load prices and other stuff....
data[:order_articles] = {}
order.articles_grouped_by_category.each do |article_category, order_articles|
order_articles.each do |order_article|
# Get the result of last time ordering, if possible
goa = group_order_articles.detect { |goa| goa.order_article_id == order_article.id }
# Build hash with relevant data
data[:order_articles][order_article.id] = {
:price => order_article.article.fc_price,
:unit => order_article.article.unit_quantity,
:quantity => (goa ? goa.quantity : 0),
:others_quantity => order_article.quantity - (goa ? goa.quantity : 0),
:used_quantity => (goa ? goa.result(:quantity) : 0),
:tolerance => (goa ? goa.result(:tolerance) : 0),
:others_tolerance => order_article.tolerance - (goa ? goa.result(:tolerance) : 0),
:used_tolerance => (goa ? goa.result(:tolerance) : 0),
:total_price => (goa ? goa.total_price : 0),
:missing_units => order_article.missing_units,
:quantity_available => (order.stockit? ? order_article.article.quantity_available : 0)
}
end
end
data
end
def save_group_order_articles
for order_article in order.order_articles
# Find the group_order_article, create a new one if necessary...
group_order_article = group_order_articles.find_or_create_by_order_article_id(order_article.id)
# Get ordered quantities and update group_order_articles/_quantities...
quantities = group_order_articles_attributes.fetch(order_article.id.to_s, {:quantity => 0, :tolerance => 0})
group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i)
# Also update results for the order_article
logger.debug "[save_group_order_articles] update order_article.results!"
order_article.update_results!
end
# set attributes to nil to avoid and infinite loop of
end
# Updates the "price" attribute.
def update_price!
total = group_order_articles.includes(:order_article => [:article, :article_price]).to_a.sum(&:total_price)
update_attribute(:price, total)
end
# Save GroupOrder and updates group_order_articles/quantities accordingly
def save_ordering!
transaction do
save!
save_group_order_articles
update_price!
end
end
end
# == Schema Information
#
# Table name: group_orders
#
# id :integer(4) not null, primary key
# ordergroup_id :integer(4) default(0), not null
# order_id :integer(4) default(0), not null
# price :decimal(8, 2) default(0.0), not null
# lock_version :integer(4) default(0), not null
# updated_on :datetime not null
# updated_by_user_id :integer(4)
#

View File

@ -8,21 +8,26 @@ class GroupOrderArticle < ActiveRecord::Base
belongs_to :order_article
has_many :group_order_article_quantities, :dependent => :destroy
validates_presence_of :group_order_id, :order_article_id
validates_presence_of :group_order, :order_article
validates_inclusion_of :quantity, :in => 0..99
validates_inclusion_of :result, :in => 0..99, :allow_nil => true
validates_inclusion_of :tolerance, :in => 0..99
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
attr_accessor :ordergroup_id # To create an new GroupOrder if neccessary
scope :ordered, :conditions => 'result > 0'
named_scope :ordered, :conditions => 'result > 0'
localize_input_of :result
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
def result=(result)
self[:result] = String.delocalized_decimal(result)
# Setter used in group_order_article#new
# We have to create an group_order, if the ordergroup wasn't involved in the order yet
def ordergroup_id=(id)
self.group_order = GroupOrder.find_or_initialize_by_order_id_and_ordergroup_id(order_article.order_id, id)
end
def ordergroup_id
group_order.try(:ordergroup_id)
end
# Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties
# and the associated GroupOrderArticleQuantities chronologically.
#
@ -30,17 +35,17 @@ class GroupOrderArticle < ActiveRecord::Base
def update_quantities(quantity, tolerance)
logger.debug("GroupOrderArticle[#{id}].update_quantities(#{quantity}, #{tolerance})")
logger.debug("Current quantity = #{self.quantity}, tolerance = #{self.tolerance}")
# Get quantities ordered with the newest item first.
quantities = group_order_article_quantities.find(:all, :order => 'created_on desc')
logger.debug("GroupOrderArticleQuantity items found: #{quantities.size}")
if (quantities.size == 0)
if (quantities.size == 0)
# There is no GroupOrderArticleQuantity item yet, just insert with desired quantities...
logger.debug("No quantities entry at all, inserting a new one with the desired quantities")
quantities.push(GroupOrderArticleQuantity.new(:group_order_article => self, :quantity => quantity, :tolerance => tolerance))
self.quantity, self.tolerance = quantity, tolerance
else
self.quantity, self.tolerance = quantity, tolerance
else
# Decrease quantity/tolerance if necessary by going through the existing items and decreasing their values...
i = 0
while (i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance))
@ -50,31 +55,31 @@ class GroupOrderArticle < ActiveRecord::Base
delta = (delta > quantities[i].quantity ? quantities[i].quantity : delta)
logger.debug("Decreasing quantity by #{delta}")
quantities[i].quantity -= delta
self.quantity -= delta
self.quantity -= delta
end
if (tolerance < self.tolerance && quantities[i].tolerance > 0)
delta = self.tolerance - tolerance
delta = (delta > quantities[i].tolerance ? quantities[i].tolerance : delta)
logger.debug("Decreasing tolerance by #{delta}")
quantities[i].tolerance -= delta
self.tolerance -= delta
self.tolerance -= delta
end
i += 1
end
end
# If there is at least one increased value: insert a new GroupOrderArticleQuantity object
if (quantity > self.quantity || tolerance > self.tolerance)
logger.debug("Inserting a new GroupOrderArticleQuantity")
quantities.insert(0, GroupOrderArticleQuantity.new(
:group_order_article => self,
:quantity => (quantity > self.quantity ? quantity - self.quantity : 0),
:tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0)
:group_order_article => self,
:quantity => (quantity > self.quantity ? quantity - self.quantity : 0),
:tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0)
))
# Recalc totals:
self.quantity += quantities[0].quantity
self.tolerance += quantities[0].tolerance
self.tolerance += quantities[0].tolerance
end
end
# Check if something went terribly wrong and quantites have not been adjusted as desired.
if (self.quantity != quantity || self.tolerance != tolerance)
raise 'Invalid state: unable to update GroupOrderArticle/-Quantities to desired quantities!'
@ -82,7 +87,7 @@ class GroupOrderArticle < ActiveRecord::Base
# Remove zero-only items.
quantities = quantities.reject { | q | q.quantity == 0 && q.tolerance == 0}
# Save
transaction do
quantities.each { | i | i.save! }
@ -90,7 +95,7 @@ class GroupOrderArticle < ActiveRecord::Base
save!
end
end
# Determines how many items of this article the Ordergroup receives.
# Returns a hash with three keys: :quantity / :tolerance / :total
#
@ -102,15 +107,15 @@ class GroupOrderArticle < ActiveRecord::Base
# Get total
total = stockit ? order_article.article.quantity : order_article.units_to_order * order_article.price.unit_quantity
logger.debug("<#{order_article.article.name}>.unitsToOrder => items ordered: #{order_article.units_to_order} => #{total}")
if (total > 0)
# In total there are enough units ordered. Now check the individual result for the ordergroup (group_order).
#
# Get all GroupOrderArticleQuantities for this OrderArticle...
order_quantities = GroupOrderArticleQuantity.all(
:conditions => ["group_order_article_id IN (?)", order_article.group_order_article_ids], :order => 'created_on')
:conditions => ["group_order_article_id IN (?)", order_article.group_order_article_ids], :order => 'created_on')
logger.debug("GroupOrderArticleQuantity records found: #{order_quantities.size}")
# Determine quantities to be ordered...
total_quantity = i = 0
while (i < order_quantities.size && total_quantity < total)
@ -137,10 +142,10 @@ class GroupOrderArticle < ActiveRecord::Base
i += 1
end
end
logger.debug("determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{quantity + tolerance}")
end
{:quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance}
end
memoize :calculate_result
@ -156,20 +161,23 @@ class GroupOrderArticle < ActiveRecord::Base
def save_results!
self.update_attribute(:result, calculate_result[:total])
end
# Returns total price for this individual article
# Until the order is finished this will be the maximum price or
# the minimum price depending on configuration. When the order is finished it
# will be the value depending of the article results.
def total_price(order_article = self.order_article)
unless order_article.order.finished?
if FoodsoftConfig[:tolerance_is_costly]
order_article.article.fc_price * (quantity + tolerance)
else
order_article.article.fc_price * quantity
end
else
order_article.price.fc_price * result
end
end
end
# == Schema Information
#
# Table name: group_order_articles
#
# id :integer(4) not null, primary key
# group_order_id :integer(4) default(0), not null
# order_article_id :integer(4) default(0), not null
# quantity :integer(4) default(0), not null
# tolerance :integer(4) default(0), not null
# updated_on :datetime not null
# result :decimal(8, 3)
#

View File

@ -11,14 +11,3 @@ class GroupOrderArticleQuantity < ActiveRecord::Base
end
# == Schema Information
#
# Table name: group_order_article_quantities
#
# id :integer(4) not null, primary key
# group_order_article_id :integer(4) default(0), not null
# quantity :integer(4) default(0)
# tolerance :integer(4) default(0)
# created_on :datetime not null
#

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