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 log/*.log
tmp/**/* tmp/**/*
config/*.yml config/*.yml
config/initializers/secret_token.rb
db/*.sqlite3 db/*.sqlite3
nbproject/ nbproject/
config/environments/development.rb config/environments/development.rb
capfile
config/environments/fcschinke09.rb
*.swp *.swp
*~ *~
public/**/*_cached.* public/**/*_cached.*
config/initializers/session_store.rb
.idea .idea
.rvmrc
.get-dump.yml .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 # A sample Gemfile
source "http://rubygems.org" source "https://rubygems.org"
#ruby "1.8.7" ruby "1.9.3"
gem "rails", '2.3.17' gem "rails", '~> 3.2.9'
gem 'mysql' # Gems used only for assets and not required
gem "fastercsv" # in production environments by default.
gem "prawn", '<=0.6.3' group :assets do
gem 'haml', '>=2.0.6' gem 'sass-rails', '~> 3.2.3'
gem 'routing-filter', '0.0.1', :require => 'routing_filter' gem 'coffee-rails', '~> 3.2.1'
gem 'sqlite3-ruby'
gem 'rdoc', '>= 2.4.2' # 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 group :development do
gem 'annotate' gem 'sqlite3'
gem 'hirb'
# 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 end

View file

@ -1,57 +1,256 @@
GEM GIT
remote: http://rubygems.org/ remote: git://github.com/bennibu/localize_input.git
revision: 5eb188d2525a073d09e142cf8b0b04e6ace6e7b0
specs: specs:
actionmailer (2.3.17) localize_input (0.1.0)
actionpack (= 2.3.17)
actionpack (2.3.17) GIT
activesupport (= 2.3.17) remote: git://github.com/bwalding/acts_as_configurable.git
rack (~> 1.1.0) revision: cdf6f6f979019275b523d10684b748f08e2dd8e8
activerecord (2.3.17) specs:
activesupport (= 2.3.17) acts_as_configurable (0.0.1)
activeresource (2.3.17) rake
activesupport (= 2.3.17)
activesupport (2.3.17) GIT
annotate (2.4.0) remote: git://github.com/technoweenie/acts_as_versioned.git
fastercsv (1.5.4) revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b
haml (3.0.25) specs:
hirb (0.3.4) acts_as_versioned (0.6.0)
json (1.7.6) activerecord (>= 3.0.9)
mysql (2.8.1)
prawn (0.6.3) GEM
prawn-core (>= 0.6.3, < 0.7) remote: https://rubygems.org/
prawn-format (>= 0.2.3, < 0.3) specs:
prawn-layout (>= 0.3.2, < 0.4) Ascii85 (1.0.2)
prawn-security (>= 0.1.1, < 0.2) actionmailer (3.2.13)
prawn-core (0.6.3) actionpack (= 3.2.13)
prawn-format (0.2.3) mail (~> 2.5.3)
prawn-core actionpack (3.2.13)
prawn-layout (0.3.2) activemodel (= 3.2.13)
prawn-security (0.1.1) activesupport (= 3.2.13)
rack (1.1.6) builder (~> 3.0.0)
rails (2.3.17) erubis (~> 2.7.0)
actionmailer (= 2.3.17) journey (~> 1.0.4)
actionpack (= 2.3.17) rack (~> 1.4.5)
activerecord (= 2.3.17) rack-cache (~> 1.2)
activeresource (= 2.3.17) rack-test (~> 0.6.1)
activesupport (= 2.3.17) sprockets (~> 2.2.1)
rake (>= 0.8.3) 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) rake (10.0.3)
rdoc (3.12) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
routing-filter (0.0.1) redis (3.0.2)
sqlite3-ruby (1.2.4) 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 PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
annotate acts_as_configurable!
fastercsv acts_as_tree
haml (>= 2.0.6) acts_as_versioned!
hirb better_errors
mysql binding_of_caller
prawn (<= 0.6.3) bullet
rails (= 2.3.17) client_side_validations
rdoc (>= 2.4.2) coffee-rails (~> 3.2.1)
routing-filter (= 0.0.1) daemons
sqlite3-ruby 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 (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 (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: If installed you only need to install the gem bundler:
gem install 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 (6) Create database (schema) and load defaults
-------------------------- --------------------------
rake db:create rake db:setup
rake db:schema:load
rake db:seed
With this, you also get a ready to go user with username 'admin' and password 'secret'. With this, you also get a ready to go user with username 'admin' and password 'secret'.
@ -70,4 +69,28 @@ With this, you also get a ready to go user with username 'admin' and password 's
--------------- ---------------
Start the WEBrick server to 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, # 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. # 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'
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 // colors which are used in the foodsoft
!main_red = #ED0606 $main_red: #ED0606
!hover_yellow = #ffff72 $hover_yellow: #ffff72
!boxContent = #e4eed6 $boxContent: #e4eed6
!lightGrey = #efefef $lightGrey: #efefef
!darkGreen = #78b74e $darkGreen: #78b74e
!lightGreen = #e4eed6 $lightGreen: #e4eed6
/* General rules ... // General rules ...
body body
:background-color #fff :background-color #fff
:color black :color black
@ -26,17 +26,17 @@ body
:border :border
:width 2px :width 2px
:style solid :style solid
:color = !main_red color: $main_red
a, a:visited a, a:visited
:text-decoration underline :text-decoration underline
:color black :color black
a:hover a:hover
:color = !main_red color: $main_red
h1, h2 h1, h2
:color = !main_red color: $main_red
h1 h1
:font-size 2.2em :font-size 2.2em
@ -46,7 +46,7 @@ h1
:border-bottom :border-bottom
:width 1px :width 1px
:style dotted :style dotted
:color = !main_red color: $main_red
h2 h2
:font-size 1.4em :font-size 1.4em
@ -88,7 +88,7 @@ option
border-top: 1px solid #D7D7D7 border-top: 1px solid #D7D7D7
margin: .2em 0 margin: .2em 0
span.click-me .click-me
cursor: pointer cursor: pointer
.left .left
float: left float: left
@ -104,10 +104,15 @@ span.click-me
.hidden .hidden
display: none display: none
.warning
background: yellow
font-weight: bold
padding: 1px 10px
// ********************************* loginpage // ********************************* loginpage
#login #login
:margin auto :margin auto
:width 27em :width 35em
:font-size 1.2em :font-size 1.2em
#login #meta #login #meta
@ -124,7 +129,7 @@ span.click-me
#logo #logo
:background = !main_red background: $main_red
:height 1.1em :height 1.1em
:width 8em :width 8em
:padding 0 20px :padding 0 20px
@ -136,17 +141,17 @@ span.click-me
:margin 0 :margin 0
a, a:hover a, a:hover
:color white :color white
:background-color = !main_red background-color: $main_red
:text-decoration none :text-decoration none
a span a span
:color = !main_red color: $main_red
:background #FFF :background #FFF
:padding-right 0.1em :padding-right 0.1em
:font-weight bold :font-weight bold
:border-top :border-top
:width 2px :width 2px
:style dotted :style dotted
:color = !main_red color: $main_red
#logininfo #logininfo
:position absolute :position absolute
@ -162,7 +167,7 @@ span.click-me
:color #737272 :color #737272
:font-weight bold :font-weight bold
a:hover a:hover
:color = !main_red color: $main_red
// ************************************* box structure // ************************************* box structure
#main #main
@ -268,8 +273,6 @@ table
:color #008000 :color #008000
tr.selected, tr.active tr.selected, tr.active
:background-color #ffffc2 :background-color #ffffc2
tr.click-me
:cursor pointer
tr.ignored tr.ignored
color: grey color: grey
tr.success tr.success
@ -290,7 +293,7 @@ table tfoot tr
:padding-top 0.8em :padding-top 0.8em
tr.edit_inline tr.edit_inline
:background-color = !hover_yellow background-color: $hover_yellow
td, span td, span
:padding 0.5em 0.2em :padding 0.5em 0.2em
@ -318,15 +321,15 @@ td.currency, td.actions
:text-align right :text-align right
:padding-right 0.5em :padding-right 0.5em
td.closed 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 a
display: block display: block
text-decoration: none text-decoration: none
padding-left: 20px padding-left: 20px
td.open 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 div.edit_form
:border 2px solid #e3e3e3 :border 2px solid #e3e3e3
:background #f5f5f5 :background #f5f5f5
@ -345,7 +348,7 @@ div.edit_form
:border :border
:width 3px :width 3px
:style solid :style solid
:color = !main_red color: $main_red
// ***************************************** other boxes */ // ***************************************** other boxes */
@ -384,7 +387,7 @@ div.box_title
:font-size 1.3em :font-size 1.3em
div.column_content div.column_content
:background = !boxContent background: $boxContent
:color black :color black
:padding 10px :padding 10px
margin-bottom: 2em margin-bottom: 2em
@ -411,7 +414,7 @@ li.check div.spinner
:display block :display block
:height 5px :height 5px
:width 21px :width 21px
:background-image url(/images/dots-white.gif) :background-image image-url('dots-white.gif')
:line-height 16px :line-height 16px
:float left :float left
:margin-right 5px :margin-right 5px
@ -439,7 +442,7 @@ table#order
-webkit-border-radius: 3px -webkit-border-radius: 3px
padding: 0 padding: 0
th#col_required, th#col_tolerance th#col_required, th#col_tolerance
:width 140px :width 145px
th#col_packages, th#col_left_units th#col_packages, th#col_left_units
:width 50px :width 50px
td.quantity, td.tolerance td.quantity, td.tolerance
@ -453,7 +456,7 @@ table#order
:padding-left 10px :padding-left 10px
tfoot tfoot
tr tr
:background-color = !lightGreen background-color: $lightGreen
td td
:padding-right 10px :padding-right 10px
#order-footer, .article-info #order-footer, .article-info
@ -500,6 +503,7 @@ tr.order-article .article-info
display: none display: none
tr.order-article:hover .article-info tr.order-article:hover .article-info
display: block display: block
// ********* Comments // ********* Comments
#newComment #newComment
:margin 1em :margin 1em
@ -555,6 +559,7 @@ ul.autocomplete
background-color: #fff background-color: #fff
text-align: center text-align: center
margin: 0 10px 10px 0 margin: 0 10px 10px 0
// *** wiki // *** wiki
#wiki_content #wiki_content
border-style: none border-style: none
@ -600,7 +605,8 @@ ul.autocomplete
margin: 0.3em 0 0 3.2em margin: 0.3em 0 0 3.2em
padding: 0 padding: 0
list-style-image: none list-style-image: none
li margin-bottom: 0.1em li
margin-bottom: 0.1em
a.new_wiki_link a.new_wiki_link
color: grey color: grey
@ -612,7 +618,7 @@ a.new_wiki_link
margin-bottom: 2em margin-bottom: 2em
width: 25em width: 25em
border: 1px solid grey border: 1px solid grey
:background-color= !lightGrey background-color: $lightGrey
h2 h2
font-size: 1em font-size: 1em
color: black color: black
@ -626,14 +632,13 @@ a.new_wiki_link
height: 1em height: 1em
color: #ED0606 color: #ED0606
a a
:color = !main_red color: $main_red
:text-decoration = none text-decoration: none
a:hover a:hover
:text-decoration = underline text-decoration: underline
#sidebar #sidebar
float: right float: right
width: 290px width: 290px
margin-top: -60px
#sidebar-links #sidebar-links
margin-bottom: 18px margin-bottom: 18px
text-align: right text-align: right

View file

@ -294,14 +294,14 @@ td.currency, td.actions {
padding-right: 0.5em; } padding-right: 0.5em; }
td.closed { 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 { td.closed a {
display: block; display: block;
text-decoration: none; text-decoration: none;
padding-left: 20px; } padding-left: 20px; }
td.open { 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 { div.edit_form {
border: 2px solid #e3e3e3; border: 2px solid #e3e3e3;
@ -371,7 +371,7 @@ li.check div.spinner {
display: block; display: block;
height: 5px; height: 5px;
width: 21px; width: 21px;
background-image: url(/images/dots-white.gif); background-image: image-url('dots-white.gif');
line-height: 16px; line-height: 16px;
float: left; float: left;
margin-right: 5px; margin-right: 5px;
@ -516,25 +516,24 @@ ul.autocomplete .informal {
text-align: center; text-align: center;
margin: 0 10px 10px 0; } 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 { .wiki_show, .wiki_version, .wiki_new, .wiki_edit, .wiki_all {
margin-top: 30px; margin-top: 30px;
padding: 10px; } 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 { #wiki_content {
border: 1px solid grey;
margin-right: 300px;
padding: 10px;
color: black;
line-height: 1.5em;
min-height: 400px; } min-height: 400px; }
#wiki_content span.editsection { #wiki_content span.editsection {
display: none; } 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 { #wiki_content h2, #wiki_content h3, #wiki_content h4, #wiki_content h5, #wiki_content h6 {
background: transparent none repeat scroll 0 0; background: transparent none repeat scroll 0 0;
border-bottom: 1px solid #AAAAAA; border-bottom: 1px solid #AAAAAA;
@ -593,8 +592,7 @@ a.new_wiki_link {
#sidebar { #sidebar {
float: right; float: right;
width: 290px; width: 290px; }
margin-top: -60px; }
#sidebar #sidebar-links { #sidebar #sidebar-links {
margin-bottom: 18px; margin-bottom: 18px;
text-align: right; } 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 class Admin::OrdergroupsController < Admin::BaseController
inherit_resources
def index def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100) @ordergroups = Ordergroup.undeleted.order('name ASC')
@per_page = params[:per_page].to_i
else # if somebody uses the search field:
@per_page = 20 unless params[:query].blank?
@ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%")
end end
# if the search field is used @ordergroups = @ordergroups.page(params[:page]).per(@per_page)
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
end end
def destroy def destroy
@ordergroup = Ordergroup.find(params[:id]) @ordergroup = Ordergroup.find(params[:id])
@ordergroup.destroy @ordergroup.mark_as_deleted
redirect_to admin_ordergroups_url, notice: t('admin.ordergroups.destroy.notice')
redirect_to(admin_ordergroups_url) rescue => error
end redirect_to admin_ordergroups_url, alert: t('admin.ordergroups.destroy.error')
def memberships
@group = Ordergroup.find(params[:id])
end end
end end

View file

@ -1,69 +1,15 @@
class Admin::UsersController < Admin::BaseController class Admin::UsersController < Admin::BaseController
inherit_resources
def index def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100) @users = User.order('nick ASC')
@per_page = params[:per_page].to_i
else
@per_page = 20
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) # if somebody uses the search field:
@users = User.paginate :page => params[:page], :conditions => conditions, :per_page => @per_page, :order => 'nick' unless params[:user_name].blank?
@users = @users.where("first_name LIKE :user_name OR last_name LIKE :user_name OR nick LIKE :user_name",
respond_to do |format| user_name: "%#{params[:user_name]}%")
format.html # listUsers.haml
format.js do
render :update do |page|
page.replace_html 'table', :partial => "users"
end
end
end
end end
def show @users = @users.page(params[:page]).per(@per_page)
@user = User.find(params[:id])
end 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 end

View file

@ -1,68 +1,20 @@
# encoding: utf-8
class Admin::WorkgroupsController < Admin::BaseController class Admin::WorkgroupsController < Admin::BaseController
inherit_resources
def index def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100) @workgroups = Workgroup.order('name ASC')
@per_page = params[:per_page].to_i # if somebody uses the search field:
else @workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].blank?
@per_page = 20
end
# if the search field is used @workgroups = @workgroups.page(params[:page]).per(@per_page)
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
end end
def destroy def destroy
@workgroup = Workgroup.find(params[:id]) @workgroup = Workgroup.find(params[:id])
@workgroup.destroy @workgroup.destroy
redirect_to admin_workgroups_url, notice: t('admin.ordergroups.destroy.notice')
redirect_to(admin_workgroups_url) rescue => error
end redirect_to admin_workgroups_url, alert: t('admin.ordergroups.destroy.error')
def memberships
@group = Workgroup.find(params[:id])
end end
end end

View file

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

View file

@ -1,71 +1,27 @@
class ArticleCategoriesController < ApplicationController class ArticleCategoriesController < ApplicationController
inherit_resources # Build default REST Actions via plugin
before_filter :authenticate_article_meta 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 def create
@article_category = ArticleCategory.new(params[:article_category]) create!(:notice => I18n.t('article_categories.create.notice')) { article_categories_path }
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
end end
def update def update
@article_category = ArticleCategory.find(params[:id]) update!(:notice => I18n.t('article_categories.update.notice')) { article_categories_path }
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
end end
def destroy def destroy
@article_category = ArticleCategory.find(params[:id]) destroy!
@article_category.destroy rescue => error
redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: error.message)
end
if @article_category.destroy protected
render :update do |page|
page['category_'+@article_category.id.to_s].visual_effect :drop_out def collection
end @article_categories = ArticleCategory.order('name')
end
end end
end end

View file

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

View file

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

View file

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

View file

@ -1,283 +1,50 @@
class Finance::BalancingController < ApplicationController # encoding: utf-8
before_filter :authenticate_finance class Finance::BalancingController < Finance::BaseController
verify :method => :post, :only => [:close, :close_direct]
def index def index
@financial_transactions = FinancialTransaction.find(:all, :order => "created_on DESC", :limit => 8) @orders = Order.finished.page(params[:page]).per(@per_page).order('ends DESC')
@orders = Order.finished_not_closed
@unpaid_invoices = Invoice.unpaid
end
def list
@orders = Order.finished.paginate :page => params[:page], :per_page => 10, :order => 'ends DESC'
end end
def new 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 @comments = @order.comments
if params['sort'] @articles = @order.order_articles.ordered.includes(:article, :article_price,
sort = case params['sort'] group_order_articles: {group_order: :ordergroup})
when "name" then "articles.name"
when "order_number" then "articles.order_number" sort_param = params['sort'] || 'name'
when "name_reverse" then "articles.name DESC" @articles = case sort_param
when "order_number_reverse" then "articles.order_number DESC" when 'name' then
end 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 else
sort = "id" @articles
end end
@articles = @order.order_articles.ordered.find( render layout: false if request.xhr?
: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 end
view = params[:view] def update_summary
params[:view] = nil @order = Order.find(params[:id])
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
end end
def edit_note def edit_note
@order = Order.find(params[:id]) @order = Order.find(params[:id])
render :partial => 'edit_note' render :layout => false
end end
def update_note def update_note
@order = Order.find(params[:id]) @order = Order.find(params[:id])
render :update do |page|
if @order.update_attributes(params[:order]) if @order.update_attributes(params[:order])
page["note"].replace_html simple_format(@order.note) render :layout => false
page["edit_box"].hide
else else
page["results"].replace_html :partial => "edit_note" render :action => :edit_note, :layout => false
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
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
end end
end end
@ -289,27 +56,20 @@ class Finance::BalancingController < ApplicationController
# Balances the Order, Update of the Ordergroup.account_balances # Balances the Order, Update of the Ordergroup.account_balances
def close def close
@order = Order.find(params[:id]) @order = Order.find(params[:id])
begin
@order.close!(@current_user) @order.close!(@current_user)
flash[:notice] = "Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert." redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice')
redirect_to :action => "index"
rescue => e rescue => error
flash[:error] = "Ein Fehler ist beim Abrechnen aufgetreten: " + e redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message)
redirect_to :action => "new", :id => @order
end
end end
# Close the order directly, without automaticly updating ordergroups account balances # Close the order directly, without automaticly updating ordergroups account balances
def close_direct def close_direct
@order = Order.find(params[:id]) @order = Order.find(params[:id])
if @order.finished? @order.close_direct!(@current_user)
@order.update_attributes(:state => 'closed', :updated_by => @current_user) redirect_to finance_order_index_url, notice: t('finance.balancing.close_direct.notice')
flash[:notice] = 'Die Bestellung wurde auf "gebucht" gesetzt.' rescue => error
redirect_to :action => 'listOrders', :id => @order redirect_to finance_order_index_url, alert: t('finance.balancing.close_direct.alert', message: error.message)
else
flash[:error] = 'Die Bestellung ist noch nicht beendet.'
redirect_to :action => 'listOrders', :id => @order
end
end end
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 class Finance::InvoicesController < ApplicationController
def index def index
@invoices = Invoice.find(:all, :order => "date DESC") @invoices = Invoice.includes(:supplier, :delivery, :order).order('date DESC').page(params[:page]).per(@per_page)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @invoices }
end
end end
def show def show
@invoice = Invoice.find(params[:id]) @invoice = Invoice.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @invoice }
end
end end
def new def new
@invoice = Invoice.new :supplier_id => params[:supplier_id], @invoice = Invoice.new :supplier_id => params[:supplier_id],
:delivery_id => params[:delivery_id], :order_id => params[:order_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
end end
def edit def edit
@invoice = Invoice.find(params[:id]) @invoice = Invoice.find(params[:id])
end end
# POST /invoices
# POST /invoices.xml
def create def create
@invoice = Invoice.new(params[:invoice]) @invoice = Invoice.new(params[:invoice])
if @invoice.save if @invoice.save
flash[:notice] = "Rechnung wurde erstellt." flash[:notice] = I18n.t('finance.create.notice')
if @invoice.order if @invoice.order
# Redirect to balancing page # 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 else
redirect_to [:finance, @invoice] redirect_to [:finance, @invoice]
end end
@ -50,32 +34,20 @@ class Finance::InvoicesController < ApplicationController
end end
end end
# PUT /invoices/1
# PUT /invoices/1.xml
def update def update
@invoice = Invoice.find(params[:id]) @invoice = Invoice.find(params[:id])
respond_to do |format|
if @invoice.update_attributes(params[:invoice]) if @invoice.update_attributes(params[:invoice])
flash[:notice] = 'Invoice was successfully updated.' redirect_to [:finance, @invoice], notice: I18n.t('finance.update.notice')
format.html { redirect_to([:finance, @invoice]) }
format.xml { head :ok }
else else
format.html { render :action => "edit" } render :edit
format.xml { render :xml => @invoice.errors, :status => :unprocessable_entity }
end
end end
end end
# DELETE /invoices/1
# DELETE /invoices/1.xml
def destroy def destroy
@invoice = Invoice.find(params[:id]) @invoice = Invoice.find(params[:id])
@invoice.destroy @invoice.destroy
respond_to do |format| redirect_to finance_invoices_url
format.html { redirect_to(finance_invoices_path) }
format.xml { head :ok }
end
end end
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 class Foodcoop::OrdergroupsController < ApplicationController
def index def index
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100) @ordergroups = Ordergroup.undeleted.order('name DESC')
@per_page = params[:per_page].to_i
else unless params[:name].blank? # Search by name
@per_page = 20 @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%")
end end
if (params[:only_active].to_i == 1) if params[:only_active] # Select only active groups
if (! params[:query].blank?) @ordergroups = @ordergroups.joins(:orders).where("orders.starts >= ?", Time.now.months_ago(3)).uniq
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?
end end
@total = Ordergroup.count(:conditions => conditions, :include => "orders") @ordergroups = @ordergroups.page(params[:page]).per(@per_page)
@ordergroups = Ordergroup.paginate(:page => params[:page], :per_page => @per_page, :conditions => conditions, :order => "name", :include => "orders")
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
format.js { render :partial => "ordergroups" } format.js { render :layout => false }
end end
end end
end end

View file

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

View file

@ -14,13 +14,9 @@ class Foodcoop::WorkgroupsController < ApplicationController
def update def update
@workgroup = Workgroup.find(params[:id]) @workgroup = Workgroup.find(params[:id])
if @workgroup.update_attributes(params[:workgroup]) if @workgroup.update_attributes(params[:workgroup])
flash[:notice] = "Arbeitsgruppe wurde aktualisiert" redirect_to foodcoop_workgroups_url, :notice => I18n.t('workgroups.update.notice')
redirect_to foodcoop_workgroups_url
else else
render :action => 'edit' render :action => 'edit'
end end
end end
def memberships
end
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 class HomeController < ApplicationController
helper :messages
def index def index
@currentOrders = Order.open
@ordergroup = @current_user.ordergroup
# unaccepted tasks # unaccepted tasks
@unaccepted_tasks = @current_user.unaccepted_tasks @unaccepted_tasks = Task.unaccepted_tasks_for(current_user)
# task in next week # task in next week
@next_tasks = @current_user.next_tasks @next_tasks = Task.next_assigned_tasks_for(current_user)
@messages = Message.public.all :order => 'created_at DESC', :limit => 5
# count tasks with no responsible person # count tasks with no responsible person
# tasks for groups the current user is not a member are ignored # tasks for groups the current user is not a member are ignored
tasks = Task.find(:all, :conditions => ["assigned = ? and done = ?", false, false]) @unassigned_tasks = Task.unassigned_tasks_for(current_user)
@unassigned_tasks_number = 0
for task in tasks
(@unassigned_tasks_number += 1) unless task.workgroup && !current_user.member_of?(task.workgroup)
end
end end
def profile def profile
@user = @current_user
end end
def update_profile def update_profile
@user = @current_user if @current_user.update_attributes(params[:user])
if @user.update_attributes(params[:user]) redirect_to my_profile_url, notice: I18n.t('home.changes_saved')
flash[:notice] = 'Änderungen wurden gespeichert.'
redirect_to :action => 'profile'
else else
render :action => 'profile' render :profile
end end
end end
@ -37,10 +27,6 @@ class HomeController < ApplicationController
@ordergroup = @user.ordergroup @ordergroup = @user.ordergroup
unless @ordergroup.nil? 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'] if params['sort']
sort = case params['sort'] sort = case params['sort']
@ -55,21 +41,11 @@ class HomeController < ApplicationController
sort = "created_on DESC" sort = "created_on DESC"
end end
# or if somebody uses the search field: @financial_transactions = @ordergroup.financial_transactions.page(params[:page]).per(@per_page).order(sort)
conditions = ["note LIKE ?", "%#{params[:query]}%"] unless params[:query].nil? @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 else
flash[:error] = "Leider bist Du kein Mitglied einer Bestellgruppe" redirect_to root_path, :alert => I18n.t('home.no_ordergroups')
redirect_to root_path
end end
end end
@ -78,9 +54,9 @@ class HomeController < ApplicationController
membership = Membership.find(params[:membership_id]) membership = Membership.find(params[:membership_id])
if membership.user == current_user if membership.user == current_user
membership.destroy 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 else
flash[:error] = "Ein Problem ist aufgetreten." flash[:error] = I18n.t('errors.general')
end end
redirect_to my_profile_path redirect_to my_profile_path
end end

View file

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

View file

@ -1,69 +1,33 @@
# encoding: utf-8
class LoginController < ApplicationController class LoginController < ApplicationController
skip_before_filter :authenticate # no authentication since this is the login page 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. # Display the form to enter an email address requesting a token to set a new password.
def forgot_password def forgot_password
@user = User.new
end end
# Sends an email to a user with the token that allows setting a new password through action "password". # Sends an email to a user with the token that allows setting a new password through action "password".
def reset_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_token = user.new_random_password(16)
user.reset_password_expires = Time.now.advance(:days => 2) user.reset_password_expires = Time.now.advance(:days => 2)
if user.save if user.save
email = Mailer.deliver_reset_password(user) Mailer.reset_password(user).deliver
logger.debug("Sent password reset email to #{user.email}.") logger.debug("Sent password reset email to #{user.email}.")
end end
end end
flash[:notice] = "Wenn Deine E-Mail hier registiert ist bekommst Du jetzt eine Nachricht mit einem Passwort-Zurücksetzen-Link." redirect_to login_url, :notice => I18n.t('login.controller.reset_password.notice')
render :action => 'login'
end end
# Set a new password with a token from the password reminder email. # 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. # Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
def password def new_password
end end
# Sets a new password. # Sets a new password.
@ -74,38 +38,32 @@ class LoginController < ApplicationController
@user.reset_password_token = nil @user.reset_password_token = nil
@user.reset_password_expires = nil @user.reset_password_expires = nil
@user.save @user.save
flash[:notice] = "Dein Passwort wurde aktualisiert. Du kannst Dich jetzt anmelden." redirect_to login_url, :notice => I18n.t('login.controller.update_password.notice')
render :action => 'login'
else else
render :action => 'password' render :new_password
end end
end end
# Invited users. # For invited users.
def invite def accept_invitation
@invite = Invite.find_by_token(params[:id]) @invite = Invite.find_by_token(params[:token])
if (@invite.nil? || @invite.expires_at < Time.now) if @invite.nil? || @invite.expires_at < Time.now
flash[:error] = "Deine Einladung ist nicht (mehr) gültig." redirect_to login_url, alert: I18n.t('login.controller.error_invite_invalid')
render :action => 'login'
elsif @invite.group.nil? elsif @invite.group.nil?
flash[:error] = "Die Gruppe, in die Du eingeladen wurdest, existiert leider nicht mehr." redirect_to login_url, alert: I18n.t('login.controller.error_group_invalid')
render :action => 'login' elsif request.post?
elsif (request.post?)
User.transaction do User.transaction do
@user = User.new(params[:user]) @user = User.new(params[:user])
@user.email = @invite.email @user.email = @invite.email
if @user.save if @user.save
Membership.new(:user => @user, :group => @invite.group).save! Membership.new(:user => @user, :group => @invite.group).save!
@invite.destroy @invite.destroy
flash[:notice] = "Herzlichen Glückwunsch, Dein Account wurde erstellt. Du kannst Dich nun einloggen." redirect_to login_url, notice: I18n.t('login.controller.accept_invitation.notice')
render :action => 'login'
end end
end end
else else
@user = User.new(:email => @invite.email) @user = User.new(:email => @invite.email)
end end
rescue
flash[:error] = "Ein Fehler ist aufgetreten. Bitte erneut versuchen."
end end
protected protected
@ -113,8 +71,7 @@ class LoginController < ApplicationController
def validate_token def validate_token
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token]) @user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
if (@user.nil? || @user.reset_password_expires < Time.now) if (@user.nil? || @user.reset_password_expires < Time.now)
flash[:error] = "Ungültiger oder abgelaufener Token. Bitte versuch es erneut." redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid')
render :action => 'forgot_password'
end end
end end
end end

View file

@ -2,22 +2,23 @@ class MessagesController < ApplicationController
# Renders the "inbox" action. # Renders the "inbox" action.
def index 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 end
# Creates a new message object. # Creates a new message object.
def new 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 end
# Creates a new message. # Creates a new message.
def create def create
@message = @current_user.send_messages.new(params[:message]) @message = @current_user.send_messages.new(params[:message])
if @message.save if @message.save
#FIXME: Send Mails wit ID instead of using message.state ... Resque.enqueue(UserNotifier, FoodsoftConfig.scope, 'message_deliver', @message.id)
call_rake :send_emails redirect_to messages_url, :notice => I18n.t('messages.create.notice')
flash[:notice] = "Nachricht ist gespeichert und wird versendet."
redirect_to messages_path
else else
render :action => 'new' render :action => 'new'
end end
@ -26,51 +27,8 @@ class MessagesController < ApplicationController
# Shows a single message. # Shows a single message.
def show def show
@message = Message.find(params[:id]) @message = Message.find(params[:id])
unless @message.is_readable_for?(current_user)
redirect_to messages_url, alert: 'Nachricht ist privat!'
end 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'
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
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. # 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. # Normal ordering actions of members of order groups is handled by the OrderingController.
class OrdersController < ApplicationController class OrdersController < ApplicationController
before_filter :authenticate_orders before_filter :authenticate_orders
# Define layout exceptions for PDF actions:
layout "application", :except => [:faxPdf, :matrixPdf, :articlesPdf, :groupsPdf]
prawnto :prawn => { :page_size => 'A4' }
# List orders # List orders
def index def index
@open_orders = Order.open @open_orders = Order.open
@ -22,18 +20,7 @@ class OrdersController < ApplicationController
else else
sort = "ends DESC" sort = "ends DESC"
end end
@orders = Order.paginate :page => params[:page], :per_page => @per_page, @orders = Order.page(params[:page]).per(@per_page).order(sort).where("state != 'open'").includes(:supplier)
: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
end end
# Gives a view for the results to a specific order # Gives a view for the results to a specific order
@ -41,13 +28,26 @@ class OrdersController < ApplicationController
def show def show
@order= Order.find(params[:id]) @order= Order.find(params[:id])
if params[:view] # Articles-list will be replaced respond_to do |format|
partial = case params[:view] format.html
when 'normal' then "articles" format.js do
@partial = case params[:view]
when 'default' then "articles"
when 'groups'then 'shared/articles_by_groups' when 'groups'then 'shared/articles_by_groups'
when 'articles'then 'shared/articles_by_articles' 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 end
render :partial => partial, :locals => {:order => @order} if partial
end end
end end
@ -60,10 +60,12 @@ class OrdersController < ApplicationController
# order_articles will be saved in Order.article_ids=() # order_articles will be saved in Order.article_ids=()
def create def create
@order = Order.new(params[:order]) @order = Order.new(params[:order])
@order.created_by = current_user
if @order.save if @order.save
flash[:notice] = "Die Bestellung wurde erstellt." flash[:notice] = I18n.t('orders.create.notice')
redirect_to @order redirect_to @order
else else
logger.debug "[debug] order errors: #{@order.errors.messages}"
render :action => 'new' render :action => 'new'
end end
end end
@ -78,7 +80,7 @@ class OrdersController < ApplicationController
def update def update
@order = Order.find params[:id] @order = Order.find params[:id]
if @order.update_attributes params[:order] 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 redirect_to :action => 'show', :id => @order
else else
render :action => 'edit' render :action => 'edit'
@ -95,31 +97,7 @@ class OrdersController < ApplicationController
def finish def finish
order = Order.find(params[:id]) order = Order.find(params[:id])
order.finish!(@current_user) order.finish!(@current_user)
call_rake "foodsoft:finished_order_tasks", :order_id => order.id redirect_to order, notice: I18n.t('orders.finish.notice')
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"
end end
# Renders the fax-text-file # Renders the fax-text-file
@ -127,15 +105,15 @@ class OrdersController < ApplicationController
def text_fax_template def text_fax_template
order = Order.find(params[:id]) order = Order.find(params[:id])
supplier = order.supplier supplier = order.supplier
contact = Foodsoft.config[:contact].symbolize_keys contact = FoodsoftConfig[:contact].symbolize_keys
text = "Bestellung für" + " #{Foodsoft.config[:name]}" text = I18n.t('orders.fax.heading', :name => FoodsoftConfig[:name])
text += "\n" + "Kundennummer" + ": #{supplier.customer_number}" unless supplier.customer_number.blank? text += "\n" + I18n.t('orders.fax.customer_number') + ': #{supplier.customer_number}' unless supplier.customer_number.blank?
text += "\n" + "Liefertag" + ": " text += "\n" + I18n.t('orders.fax.delivery_day')
text += "\n\n#{supplier.name}\n#{supplier.address}\nFAX: #{supplier.fax}\n\n" text += "\n\n#{supplier.name}\n#{supplier.address}\n" + I18n.t('simple_form.labels.supplier.fax') + ": #{supplier.fax}\n\n"
text += "****** " + "Versandadresse" + "\n\n" text += "****** " + I18n.t('orders.fax.to_address') + "\n\n"
text += "#{Foodsoft.config[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n" text += "#{FoodsoftConfig[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n"
text += "****** " + "Artikel" + "\n\n" text += "****** " + I18n.t('orders.fax.articles') + "\n\n"
text += "Nummer" + " " + "Menge" + " " + "Name" + "\n" text += I18n.t('orders.fax.number') + " " + I18n.t('orders.fax.amount') + " " + I18n.t('orders.fax.name') + "\n"
# now display all ordered articles # now display all ordered articles
order.order_articles.ordered.all(:include => [:article, :article_price]).each do |oa| order.order_articles.ordered.all(:include => [:article, :article_price]).each do |oa|
number = oa.article.order_number number = oa.article.order_number
@ -148,28 +126,4 @@ class OrdersController < ApplicationController
:type => 'text/plain; charset=utf-8; header=present', :type => 'text/plain; charset=utf-8; header=present',
:disposition => "attachment; filename=#{order.name}" :disposition => "attachment; filename=#{order.name}"
end 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 end

View file

@ -1,3 +1,4 @@
# encoding: utf-8
class PagesController < ApplicationController class PagesController < ApplicationController
def index def index
@ -16,7 +17,7 @@ class PagesController < ApplicationController
elsif params[:id] elsif params[:id]
page = Page.find_by_id(params[:id]) page = Page.find_by_id(params[:id])
if page.nil? if page.nil?
flash[:error] = "Seite existiert nicht!" flash[:error] = I18n.t('pages.cshow.error_noexist')
redirect_to all_pages_path and return redirect_to all_pages_path and return
else else
redirect_to wiki_page_path(page.permalink) and return redirect_to wiki_page_path(page.permalink) and return
@ -28,7 +29,7 @@ class PagesController < ApplicationController
elsif @page.redirect? elsif @page.redirect?
page = Page.find_by_id(@page.redirect) page = Page.find_by_id(@page.redirect)
unless page.nil? 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) redirect_to wiki_page_path(page.permalink)
end end
end end
@ -56,7 +57,7 @@ class PagesController < ApplicationController
render :action => 'new' render :action => 'new'
else else
if @page.save if @page.save
flash[:notice] = 'Seite wurde angelegt.' flash[:notice] = I18n.t('pages.create.notice')
redirect_to(wiki_page_path(@page.permalink)) redirect_to(wiki_page_path(@page.permalink))
else else
render :action => "new" render :action => "new"
@ -75,7 +76,7 @@ class PagesController < ApplicationController
if @page.save if @page.save
@page.parent_id = parent_id if (!params[:parent_id].blank? \ @page.parent_id = parent_id if (!params[:parent_id].blank? \
and params[:parent_id] != @page_id) 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) redirect_to wiki_page_path(@page.permalink)
else else
render :action => "edit" render :action => "edit"
@ -83,7 +84,7 @@ class PagesController < ApplicationController
end end
rescue ActiveRecord::StaleObjectError 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) redirect_to wiki_page_path(@page.permalink)
end end
@ -91,25 +92,27 @@ class PagesController < ApplicationController
@page = Page.find(params[:id]) @page = Page.find(params[:id])
@page.destroy @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 redirect_to wiki_path
end end
def all def all
@recent_pages = Page.non_redirected.all :order => 'updated_at DESC' @pages = Page.non_redirected
@pages = Page.non_redirected.all :order => 'title' @partial = params[:view] || 'recent_changes'
@top_pages = Page.no_parent.non_redirected.all :order => 'created_at'
view = params[:view] if params[:name]
params[:view] = nil @pages = @pages.where("title LIKE ?", "%#{params[:name]}%").limit(20).order('updated_at DESC')
@partial = 'title_list'
case view else
when 'recentChanges' order = case @partial
render :partial => 'recent_changes' and return when 'recent_changes' then
when 'siteMap' 'updated_at DESC'
render :partial => 'site_map' and return when 'site_map' then
when 'titleList' 'created_at DESC'
render :partial => 'title_list' and return when 'title_list' then
'title DESC'
end
@pages.order(order)
end end
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 class StockTakingsController < ApplicationController
inherit_resources
def index def index
@stock_takings = StockTaking.find(:all) @stock_takings = StockTaking.order('date DESC').page(params[:page]).per(@per_page)
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
end end
def new def new
@stock_taking = StockTaking.new @stock_taking = StockTaking.new
StockArticle.without_deleted.each { |a| @stock_taking.stock_changes.build(:stock_article => a) } StockArticle.undeleted.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])
end end
def create def create
@stock_taking = StockTaking.new(params[:stock_taking]) create!(:notice => I18n.t('stock_takings.create.notice'))
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
end end
def update def update
@stock_taking = StockTaking.find(params[:id]) update!(:notice => I18n.t('stock_takings.update.notice'))
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
end end
def fill_new_stock_article_form def fill_new_stock_article_form
@ -107,6 +52,4 @@ class StockTakingsController < ApplicationController
page.visual_effect :DropOut, "stock_change_#{stock_change.id}" page.visual_effect :DropOut, "stock_change_#{stock_change.id}"
end end
end end
end end

View file

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

View file

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

View file

@ -1,29 +1,25 @@
# encoding: utf-8
class TasksController < ApplicationController class TasksController < ApplicationController
#auto_complete_for :user, :nick #auto_complete_for :user, :nick
def index def index
@non_group_tasks = Task.non_group @non_group_tasks = Task.non_group.includes(assignments: :user)
@groups = Workgroup.all @groups = Workgroup.includes(open_tasks: {assignments: :user})
end end
def user def user
@unaccepted_tasks = @current_user.unaccepted_tasks @unaccepted_tasks = Task.unaccepted_tasks_for(current_user)
@accepted_tasks = @current_user.accepted_tasks @accepted_tasks = Task.accepted_tasks_for(current_user)
end end
def new def new
@task = Task.new @task = Task.new(current_user_id: current_user.id)
end end
def create def create
@task = Task.new(params[:task]) @task = Task.new(params[:task])
if @task.errors.empty? && @task.save if @task.save
flash[:notice] = "Aufgabe wurde erstellt" redirect_to tasks_url, :notice => I18n.t('tasks.create.notice')
if @task.workgroup
redirect_to :action => "workgroup", :id => @task.workgroup
else
redirect_to :action => "index"
end
else else
render :template => "tasks/new" render :template => "tasks/new"
end end
@ -35,17 +31,18 @@ class TasksController < ApplicationController
def edit def edit
@task = Task.find(params[:id]) @task = Task.find(params[:id])
@task.current_user_id = current_user.id
end end
def update def update
@task = Task.find(params[:id]) @task = Task.find(params[:id])
@task.attributes=(params[:task]) @task.attributes=(params[:task])
if @task.errors.empty? && @task.save if @task.errors.empty? && @task.save
flash[:notice] = "Aufgabe wurde aktualisiert" flash[:notice] = I18n.t('tasks.update.notice')
if @task.workgroup if @task.workgroup
redirect_to :action => "workgroup", :id => @task.workgroup redirect_to workgroup_tasks_url(workgroup_id: @task.workgroup_id)
else else
redirect_to :action => "index" redirect_to tasks_url
end end
else else
render :template => "tasks/edit" render :template => "tasks/edit"
@ -53,18 +50,13 @@ class TasksController < ApplicationController
end end
def destroy def destroy
Task.find(params[:id]).destroy task = Task.find(params[:id])
flash[:notice] = "Aufgabe wurde gelöscht" # Save user_ids to update apple statistics after destroy
redirect_to :action => "index" user_ids = task.user_ids
end task.destroy
task.update_ordergroup_stats(user_ids)
# Delete an given Assignment redirect_to tasks_url, :notice => I18n.t('tasks.destroy.notice')
# currently used in edit-view
def drop_assignment
ass = Assignment.find(params[:id])
task = ass.task
ass.destroy
redirect_to :action => "show", :id => task
end end
# assign current_user to the task and set the assignment to "accepted" # assign current_user to the task and set the assignment to "accepted"
@ -76,8 +68,7 @@ class TasksController < ApplicationController
else else
task.assignments.create(:user => current_user, :accepted => true) task.assignments.create(:user => current_user, :accepted => true)
end end
flash[:notice] = "Du hast die Aufgabe übernommen" redirect_to user_tasks_path, :notice => I18n.t('tasks.accept.notice')
redirect_to user_tasks_path
end end
# deletes assignment between current_user and given task # deletes assignment between current_user and given task
@ -86,34 +77,21 @@ class TasksController < ApplicationController
redirect_to :action => "index" redirect_to :action => "index"
end end
def update_status def set_done
Task.find(params[:id]).update_attribute("done", params[:task][:done]) Task.find(params[:id]).update_attribute :done, true
flash[:notice] = "Aufgabenstatus wurde aktualisiert" redirect_to tasks_url, :notice => I18n.t('tasks.set_done.notice')
redirect_to :action => "index"
end end
# Shows all tasks, which are already done # Shows all tasks, which are already done
def archive 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 end
# shows workgroup (normal group) to edit weekly_tasks_template # shows workgroup (normal group) to edit weekly_tasks_template
def workgroup def workgroup
@group = Group.find(params[:id]) @group = Group.find(params[:workgroup_id])
if @group.is_a? Ordergroup if @group.is_a? Ordergroup
flash[:error] = "Keine Arbeitsgruppe gefunden" redirect_to tasks_url, :alert => I18n.t('tasks.error_not_found')
redirect_to :action => "index"
end end
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 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. # Methods added to this helper will be available to all templates in the application.
module ApplicationHelper module ApplicationHelper
@ -18,97 +20,76 @@ module ApplicationHelper
end end
# Creates ajax-controlled-links for pagination # Creates ajax-controlled-links for pagination
# see also the plugin "will_paginate"
def pagination_links_remote(collection, options = {}) def pagination_links_remote(collection, options = {})
per_page = options[:per_page] || @per_page per_page = options[:per_page] || @per_page
params = options[:params] || {} 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}) params = params.merge({:per_page => per_page})
will_paginate collection, { :params => params, :remote => true, :update => update, paginate collection, :params => params, :remote => true
:previous_label => previous_label, :next_label => next_label, }
end end
# Link-collection for per_page-options when using the pagination-plugin # Link-collection for per_page-options when using the pagination-plugin
def items_per_page(options = {}) def items_per_page(options = {})
per_page_options = options[:per_page_options] || [20, 50, 100] per_page_options = options[:per_page_options] || [20, 50, 100]
current = options[:current] || @per_page current = options[:current] || @per_page
action = options[:action] || controller.action_name params = params || {}
update = options[:update] || nil
links = [] links = per_page_options.map do |per_page|
per_page_options.each do |per_page| params.merge!({:per_page => per_page})
unless per_page == current link_class = 'btn'
links << link_to_remote( link_class << ' disabled' if per_page == current
per_page, link_to(per_page, params, :remote => true, class: link_class)
{ :url => { :action => action, :params => {:per_page => per_page}}, end
:before => "Element.show('loader')",
:success => "Element.hide('loader')", content_tag :div, class: 'btn-group pull-right' do
:method => :get, :update => update } ) links.join.html_safe
else
links << per_page
end
end
return "Pro Seite: " + links.join(" ")
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 end
def sort_link_helper(text, key, options = {}) def sort_link_helper(text, key, options = {})
per_page = options[:per_page] || 10 # Hmtl options
action = options[:action] || "list"
remote = options[:remote].nil? ? true : options[:remote] remote = options[:remote].nil? ? true : options[:remote]
key += "_reverse" if params[:sort] == key class_name = case params[:sort]
when key then
link_options = { 'sortup'
:url => url_for(:params => params.merge({:sort => key, :page => nil, :per_page => per_page})), when key + '_reverse' then
:before => "Element.show('loader')", 'sortdown'
:success => "Element.hide('loader')",
:method => :get
}
html_options = {
:title => _("Nach #{text} sortieren"),
:href => url_for(:action => action, :params => params.merge({:sort => key, :page => nil, :per_page => per_page}))
}
if remote
link_to_remote(text, link_options, html_options)
else else
link_to(text, link_options[:url], html_options) nil
end end
html_options = {
:title => I18n.t('helpers.application.sort_by', text: text),
:remote => remote,
:class => class_name
}
# 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 end
# Generates a link to the top of the website # Generates a link to the top of the website
def link_to_top 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 end
# Returns the weekday. 0 is sunday, 1 is monday and so on # Returns the weekday. 0 is sunday, 1 is monday and so on
def weekday(dayNumber) def weekday(dayNumber)
weekdays = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"] weekdays = I18n.t('date.day_names')
return weekdays[dayNumber] return weekdays[dayNumber]
end 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 # to set a title for both the h1-tag and the title in the header
def title(page_title, show_title = true) 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 @show_title = show_title
end end
@ -121,8 +102,11 @@ module ApplicationHelper
end end
def icon(name, options={}) def icon(name, options={})
icons = { :delete => { :file => 'b_drop.png', :alt => 'Löschen'}, icons = {
:edit => { :file => 'b_edit.png', :alt => 'Bearbeiten'}} :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[:alt] ||= icons[name][:alt]
options[:title] ||= icons[name][:title] options[:title] ||= icons[name][:title]
options.merge!({:size => '16x16',:border => "0"}) options.merge!({:size => '16x16',:border => "0"})
@ -137,28 +121,42 @@ module ApplicationHelper
:success => "Element.hide('loader')", :success => "Element.hide('loader')",
:method => :get :method => :get
} }
link_to_remote(text, remote_options.merge(options)) link_to(text, options[:url], remote_options.merge(options))
end end
def format_roles(record) def format_roles(record)
roles = [] roles = []
roles << 'Admin' if record.role_admin? roles << I18n.t('helpers.application.role_admin') if record.role_admin?
roles << 'Finanzen' if record.role_finance? roles << I18n.t('helpers.application.role_finance') if record.role_finance?
roles << 'Lieferanten' if record.role_suppliers? roles << I18n.t('helpers.application.role_suppliers') if record.role_suppliers?
roles << 'Artikel' if record.role_article_meta? roles << I18n.t('helpers.application.role_article_meta') if record.role_article_meta?
roles << 'Bestellung' if record.role_orders? roles << I18n.t('helpers.application.role_orders') if record.role_orders?
roles.join(', ') roles.join(', ')
end end
def link_to_gmaps(address) 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" :target => "_blank"
end end
# offers a link for writing message to user # offers a link for writing message to user
# checks for nil (useful for relations) # checks for nil (useful for relations)
def link_to_user_message_if_valid(user) 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
end end

View file

@ -6,9 +6,14 @@ module ArticlesHelper
end end
def row_classes(article) def row_classes(article)
classes = " click-me" classes = []
classes += " unavailable" if !article.availability classes << "unavailable" if !article.availability
classes += " just_updated" if @article.recently_updated && @article.availability classes << "just-updated" if article.recently_updated && article.availability
classes 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
end end

View file

@ -3,14 +3,15 @@ module DeliveriesHelper
def link_to_invoice(delivery) def link_to_invoice(delivery)
if delivery.invoice if delivery.invoice
link_to number_to_currency(delivery.invoice.amount), [:finance, delivery.invoice], link_to number_to_currency(delivery.invoice.amount), [:finance, delivery.invoice],
:title => "Rechnung anzeigen" title: I18n.t('helpers.deliveries.show_invoice')
else 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
end end
def stock_articles_for_select(supplier) 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
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 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) def format_subject(message, length)
if message.subject.length > length if message.subject.length > length
subject = truncate(message.subject, :length => length) subject = truncate(message.subject, :length => length)
@ -19,6 +7,14 @@ module MessagesHelper
subject = message.subject subject = message.subject
body = truncate(message.body, :length => length - subject.length) body = truncate(message.body, :length => length - subject.length)
end 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
end end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,16 @@
module TasksHelper 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 # generate colored number of still required users
def highlighted_required_users(task) def highlighted_required_users(task)
unless task.enough_users_assigned? unless task.enough_users_assigned?
still_required = task.required_users - task.assignments.select { |ass| ass.accepted }.size content_tag :span, task.still_required_users, class: 'badge badge-important',
"<small style='color:red;font-weight:bold'>(#{still_required})</small>" title: I18n.t('helpers.tasks.required_users', :count => task.still_required_users)
end end
end 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,46 +1,25 @@
# == Schema Information # encoding: utf-8
#
# 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)
#
class Article < ActiveRecord::Base 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 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 # Associations
belongs_to :supplier belongs_to :supplier
belongs_to :article_category belongs_to :article_category
has_many :article_prices, :order => "created_at DESC" has_many :article_prices, :order => "created_at DESC"
named_scope :available, :conditions => {:availability => true} scope :undeleted, -> { where(deleted_at: nil) }
named_scope :not_in_stock, :conditions => {:type => nil} scope :available, -> { undeleted.where(availability: true) }
scope :not_in_stock, :conditions => {:type => nil}
# Validations # 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 :name, :in => 4..60
validates_length_of :unit, :in => 2..15 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_numericality_of :deposit, :tax
validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type] validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type]
@ -48,21 +27,6 @@ class Article < ActiveRecord::Base
before_save :update_price_history before_save :update_price_history
before_destroy :check_article_in_use 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 # The financial gross, net plus tax and deposti
def gross_price def gross_price
((price + deposit) * (tax / 100 + 1)).round(2) ((price + deposit) * (tax / 100 + 1)).round(2)
@ -70,7 +34,7 @@ class Article < ActiveRecord::Base
# The price for the foodcoop-member. # The price for the foodcoop-member.
def fc_price def fc_price
(gross_price * (Foodsoft.config[:price_markup] / 100 + 1)).round(2) (gross_price * (FoodsoftConfig[:price_markup] / 100 + 1)).round(2)
end end
# Returns true if article has been updated at least 2 days ago # Returns true if article has been updated at least 2 days ago
@ -86,6 +50,11 @@ class Article < ActiveRecord::Base
end end
memoize :in_open_order 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 # this method checks, if the shared_article has been changed
# unequal attributes will returned in array # unequal attributes will returned in array
# if only the timestamps differ and the attributes are equal, # if only the timestamps differ and the attributes are equal,
@ -157,8 +126,8 @@ class Article < ActiveRecord::Base
false false
end end
else # get factors for fc and supplier else # get factors for fc and supplier
fc_unit_factor = Foodsoft.config[:units][self.unit] fc_unit_factor = FoodsoftConfig[:units][self.unit]
supplier_unit_factor = Foodsoft.config[:units][self.shared_article.unit] supplier_unit_factor = FoodsoftConfig[:units][self.shared_article.unit]
if fc_unit_factor and supplier_unit_factor if fc_unit_factor and supplier_unit_factor
convertion_factor = fc_unit_factor / supplier_unit_factor convertion_factor = fc_unit_factor / supplier_unit_factor
new_price = BigDecimal((convertion_factor * shared_article.price).to_s).round(2) new_price = BigDecimal((convertion_factor * shared_article.price).to_s).round(2)
@ -173,11 +142,20 @@ class Article < ActiveRecord::Base
end end
end end
def deleted?
deleted_at.present?
end
def mark_as_deleted
check_article_in_use
update_column :deleted_at, Time.now
end
protected protected
# Checks if the article is in use before it will deleted # Checks if the article is in use before it will deleted
def check_article_in_use 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 end
# Create an ArticlePrice, when the price-attr are changed. # Create an ArticlePrice, when the price-attr are changed.

View file

@ -1,17 +1,15 @@
class ArticleCategory < ActiveRecord::Base class ArticleCategory < ActiveRecord::Base
has_many :articles has_many :articles
validates_length_of :name, :in => 2..20 validates :name, :presence => true, :uniqueness => true, :length => { :in => 2..20 }
validates_uniqueness_of :name
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 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 has_many :order_articles
validates_presence_of :price, :tax, :deposit, :unit_quantity validates_presence_of :price, :tax, :deposit, :unit_quantity
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
# Custom attribute setter that accepts decimal numbers using localized decimal separator. localize_input_of :price, :tax, :deposit
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 deposit. # The financial gross, net plus tax and deposit.
def gross_price def gross_price
@ -28,20 +17,7 @@ class ArticlePrice < ActiveRecord::Base
# The price for the foodcoop-member. # The price for the foodcoop-member.
def fc_price def fc_price
(gross_price * (Foodsoft.config[:price_markup] / 100 + 1)).round(2) (gross_price * (FoodsoftConfig[:price_markup] / 100 + 1)).round(2)
end end
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 :user
belongs_to :task 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 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_one :invoice
has_many :stock_changes, :dependent => :destroy 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 validates_presence_of :supplier_id
@ -19,14 +19,3 @@ class Delivery < ActiveRecord::Base
end 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 :ordergroup
belongs_to :user belongs_to :user
validates_presence_of :note, :user_id, :ordergroup_id validates_presence_of :amount, :note, :user_id, :ordergroup_id
validates_numericality_of :amount validates_numericality_of :amount
# Custom attribute setter that accepts decimal numbers using localized decimal separator. localize_input_of :amount
def amount=(amount)
self[:amount] = String.delocalized_decimal(amount)
end
# Use this save method instead of simple save and after callback
def add_transaction!
ordergroup.add_financial_transaction! amount, note, user
end
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,11 +1,14 @@
# Groups organize the User. # Groups organize the User.
# A Member gets the roles from the Group # A Member gets the roles from the Group
class Group < ActiveRecord::Base class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy has_many :memberships
has_many :users, :through => :memberships has_many :users, :through => :memberships
validates_length_of :name, :in => 1..25 validates :name, :presence => true, :length => {: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. # Returns true if the given user if is an member of this group.
def member?(user) def member?(user)
@ -17,57 +20,22 @@ class Group < ActiveRecord::Base
User.all(:order => 'nick').reject { |u| users.include?(u) } User.all(:order => 'nick').reject { |u| users.include?(u) }
end end
# Check before destroy a group, if this is the last group with admin role def user_tokens=(ids)
def before_destroy self.user_ids = ids.split(",")
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 end
protected def deleted?
deleted_at.present?
# 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
end end
# add validation check on update def mark_as_deleted
def validate_on_update # TODO: Checks for participating in not closed orders
# error if this is the last group with admin role and role_admin should set to false transaction do
if self.role_admin == false && Group.find_all_by_role_admin(true).size == 1 && self == Group.find(:first, :conditions => "role_admin = 1") memberships.destroy_all
errors.add(:role_admin, "Der letzten Gruppe mit Admin-Rechten darf die Admin-Rolle nicht entzogen werden") # TODO: What should happen to users?
update_column :deleted_at, Time.now
end end
end 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. # A GroupOrder represents an Order placed by an Ordergroup.
class GroupOrder < ActiveRecord::Base class GroupOrder < ActiveRecord::Base
attr_accessor :group_order_articles_attributes
belongs_to :order belongs_to :order
belongs_to :ordergroup belongs_to :ordergroup
has_many :group_order_articles, :dependent => :destroy has_many :group_order_articles, :dependent => :destroy
@ -12,41 +14,74 @@ class GroupOrder < ActiveRecord::Base
validates_numericality_of :price validates_numericality_of :price
validates_uniqueness_of :ordergroup_id, :scope => :order_id # order groups can only order once per order 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)]} } scope :in_open_orders, joins(:order).merge(Order.open)
named_scope :finished, lambda { {:conditions => ["order_id IN (?)", Order.finished_not_closed.collect(&:id)]} } 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. # 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! def update_price!
total = 0 total = group_order_articles.includes(:order_article => [:article, :article_price]).to_a.sum(&:total_price)
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
end
end
update_attribute(:price, total) update_attribute(:price, total)
end 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 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,19 +8,24 @@ class GroupOrderArticle < ActiveRecord::Base
belongs_to :order_article belongs_to :order_article
has_many :group_order_article_quantities, :dependent => :destroy 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 :quantity, :in => 0..99
validates_inclusion_of :result, :in => 0..99, :allow_nil => true validates_inclusion_of :result, :in => 0..99, :allow_nil => true
validates_inclusion_of :tolerance, :in => 0..99 validates_inclusion_of :tolerance, :in => 0..99
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order 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. # Setter used in group_order_article#new
def result=(result) # We have to create an group_order, if the ordergroup wasn't involved in the order yet
self[:result] = String.delocalized_decimal(result) 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 end
# Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties # Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties
@ -157,19 +162,22 @@ class GroupOrderArticle < ActiveRecord::Base
self.update_attribute(:result, calculate_result[:total]) self.update_attribute(:result, calculate_result[:total])
end 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 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 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