Merge branch 'master' into fix-closed-group_order-totals

Conflicts:
	db/schema.rb
This commit is contained in:
wvengen 2013-09-18 18:14:07 +02:00
commit ebb22ccb53
137 changed files with 4484 additions and 1507 deletions

1
.gitignore vendored
View file

@ -16,3 +16,4 @@ doc/app/
Capfile
config/deploy.rb
config/deploy/*
.localeapp

14
.travis.yml Normal file
View file

@ -0,0 +1,14 @@
language: ruby
rvm:
- 1.9.3
services:
- redis-server
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
before_script:
- "bundle exec rake foodsoft:setup:stock_config"
- "mysql -e 'create database foodsoft_test;'"
- 'printf "test:\n adapter: mysql2\n database: foodsoft_test\n username: travis\n encoding: utf8\n" >config/database.yml'
- 'bundle exec rake db:schema:load RAILS_ENV=test'
script: bundle exec rake spec

29
Gemfile
View file

@ -17,6 +17,8 @@ group :assets do
end
gem 'jquery-rails'
gem 'select2-rails'
gem 'bootstrap-datepicker-rails'
gem 'mysql2'
@ -35,7 +37,7 @@ 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 "rails-settings-cached", "0.2.4"
gem 'resque'
gem 'whenever', require: false # For defining cronjobs, see config/schedule.rb
@ -50,10 +52,7 @@ group :development do
# Better error output
gem 'better_errors'
gem 'binding_of_caller'
# Re-enable rails benchmarker/profiler
gem 'ruby-prof'
gem 'test-unit'
# gem "rails-i18n-debug"
# Get infos when not using proper eager loading
gem 'bullet'
@ -68,3 +67,23 @@ group :development do
# Avoid having content-length warnings
gem 'thin'
end
group :development, :test do
gem 'ruby-prof'
end
group :test do
gem 'rspec-rails'
gem 'factory_girl_rails', '~> 4.0'
gem 'faker'
# version requirements to avoid problem http://stackoverflow.com/questions/18114544
gem 'capybara', '~> 2.1.0'
# webkit and poltergeist don't seem to work yet
gem 'selenium-webdriver', '~> 2.35.1'
gem 'database_cleaner'
gem 'simplecov', require: false
# need to include rspec components before i18n-spec or rake fails in test environment
gem 'rspec-core'
gem 'rspec-expectations'
gem 'i18n-spec'
end

View file

@ -4,13 +4,6 @@ GIT
specs:
localize_input (0.1.0)
GIT
remote: git://github.com/bwalding/acts_as_configurable.git
revision: cdf6f6f979019275b523d10684b748f08e2dd8e8
specs:
acts_as_configurable (0.0.1)
rake
GIT
remote: git://github.com/technoweenie/acts_as_versioned.git
revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b
@ -56,6 +49,8 @@ GEM
coderay (>= 1.0.0)
erubis (>= 2.7.0)
binding_of_caller (0.6.8)
bootstrap-datepicker-rails (1.1.1.1)
railties (>= 3.0)
builder (3.0.4)
bullet (4.3.0)
uniform_notifier
@ -67,6 +62,14 @@ GEM
net-ssh-gateway (>= 1.1.0)
capistrano-ext (1.2.1)
capistrano (>= 1.0.0)
capybara (2.1.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
chronic (0.9.0)
client_side_validations (3.1.4)
coderay (1.0.8)
@ -79,6 +82,8 @@ GEM
coffee-script-source (1.3.3)
commonjs (0.2.6)
daemons (1.1.9)
database_cleaner (0.7.1)
diff-lcs (1.2.4)
erubis (2.7.0)
eventmachine (1.0.3)
exception_notification (2.6.1)
@ -86,6 +91,14 @@ GEM
execjs (1.4.0)
multi_json (~> 1.0)
expression_parser (0.9.0)
factory_girl (4.2.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.2.1)
factory_girl (~> 4.2.0)
railties (>= 3.0.0)
faker (1.1.2)
i18n (~> 0.5)
ffi (1.9.0)
haml (3.1.7)
haml-rails (0.3.5)
actionpack (>= 3.1, < 4.1)
@ -95,11 +108,15 @@ GEM
has_scope (0.5.1)
hashery (2.0.1)
highline (1.6.19)
hike (1.2.1)
hike (1.2.3)
i18n (0.6.1)
i18n-spec (0.4.0)
iso
inherited_resources (1.3.1)
has_scope (~> 0.5.0)
responders (~> 0.6)
iso (0.2.0)
i18n
journey (1.0.4)
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
@ -133,8 +150,9 @@ GEM
activesupport (~> 3.1)
polyamorous (~> 0.5.0)
mime-types (1.21)
mini_portile (0.5.1)
mono_logger (1.1.0)
multi_json (1.7.3)
multi_json (1.7.9)
mysql2 (0.3.11)
net-scp (1.1.1)
net-ssh (>= 2.6.5)
@ -143,6 +161,8 @@ GEM
net-ssh (2.6.7)
net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5)
nokogiri (1.6.0)
mini_portile (~> 0.5.0)
pdf-reader (1.2.0)
Ascii85 (~> 1.0.0)
hashery (~> 2.0)
@ -172,6 +192,8 @@ GEM
activesupport (= 3.2.13)
bundler (~> 1.0)
railties (= 3.2.13)
rails-settings-cached (0.2.4)
rails (>= 3.0.0)
railties (3.2.13)
actionpack (= 3.2.13)
activesupport (= 3.2.13)
@ -183,7 +205,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redis (3.0.4)
redis-namespace (1.3.0)
redis-namespace (1.3.1)
redis (~> 3.0.0)
responders (0.9.3)
railties (~> 3.1)
@ -193,20 +215,44 @@ GEM
redis-namespace (~> 1.2)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
ruby-prof (0.11.2)
rspec-core (2.14.2)
rspec-expectations (2.14.0)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.1)
rspec-rails (2.14.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-prof (0.13.0)
ruby-rc4 (0.1.5)
rubyzip (0.9.9)
sass (3.2.1)
sass-rails (3.2.5)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
select2-rails (3.4.2)
sass-rails
thor (~> 0.14)
selenium-webdriver (2.35.1)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip (< 1.0.0)
websocket (~> 1.0.4)
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)
simple_form (2.1.0)
actionpack (~> 3.0)
activemodel (~> 3.0)
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
sinatra (1.3.6)
rack (~> 1.4)
rack-protection (~> 1.3)
@ -220,7 +266,6 @@ GEM
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)
thin (1.5.1)
@ -245,30 +290,38 @@ GEM
uniform_notifier (1.1.1)
vegas (0.1.11)
rack (>= 1.0.0)
websocket (1.0.7)
whenever (0.8.1)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
wikicloth (0.8.0)
builder
expression_parser
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
acts_as_configurable!
acts_as_tree
acts_as_versioned!
better_errors
binding_of_caller
bootstrap-datepicker-rails
bullet
capistrano (= 2.13.5)
capistrano-ext
capybara (~> 2.1.0)
client_side_validations
coffee-rails (~> 3.2.1)
daemons
database_cleaner
exception_notification
factory_girl_rails (~> 4.0)
faker
haml-rails
i18n-spec
inherited_resources
jquery-rails
kaminari
@ -279,14 +332,20 @@ DEPENDENCIES
prawn
quiet_assets
rails (~> 3.2.9)
rails-settings-cached (= 0.2.4)
resque
rspec-core
rspec-expectations
rspec-rails
ruby-prof
sass-rails (~> 3.2.3)
select2-rails
selenium-webdriver (~> 2.35.1)
simple-navigation
simple-navigation-bootstrap
simple_form
simplecov
sqlite3
test-unit
therubyracer
thin
twitter-bootstrap-rails

View file

@ -1,10 +1,6 @@
Important
--------
We changed the branch structure. The rails3 branch is now master. But you can safely send pull requests to rails3. It'll remain there for a couple of weeks.
FoodSoft
=========
[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png?branch=tests-rspec)](https://travis-ci.org/foodcoops/foodsoft)
[![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.png)](https://codeclimate.com/github/foodcoops/foodsoft)
[![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft)

View file

@ -1,14 +1,17 @@
//= require jquery
//= require jquery-ui
//= require jquery_ujs
//= require select2
//= require twitter/bootstrap
//= require jquery.tokeninput
//= require bootstrap-datepicker
//= require bootstrap-datepicker.de
//= require bootstrap-datepicker/core
//= require bootstrap-datepicker/locales/bootstrap-datepicker.de
//= require bootstrap-datepicker/locales/bootstrap-datepicker.nl
//= require jquery.observe_field
//= require rails.validations
//= require_self
//= require ordering
//= require stupidtable
// allow touch devices to work on click events
// http://stackoverflow.com/a/16221066
@ -112,9 +115,16 @@ $(function() {
});
// Use bootstrap datepicker for dateinput
$('.datepicker').datepicker({format: 'yyyy-mm-dd', weekStart: 1, language: 'de'});
$('.datepicker').datepicker({format: 'yyyy-mm-dd', language: I18n.locale});
// See stupidtable.js for initialization of local table sorting
});
// retrigger last local table sorting
function updateSort(table) {
$('.sorting-asc, .sorting-desc', table).toggleClass('.sorting-asc .sorting-desc')
.removeData('sort-dir').trigger('click'); // CAUTION: removing data field of plugin
}
// gives the row an yellow background
function highlightRow(checkbox) {

View file

@ -10,6 +10,7 @@ var groupBalance = 0; // available group money
var currencySeparator = "."; // default decimal separator
var currencyPrecision = 2; // default digits behind comma
var currencyUnit = "€"; // default currency
var minimumBalance = 0; // minimum group balance for the order to be succesful
var toleranceIsCostly = true; // default tolerance behaviour
var isStockit = false; // Wheter the order is from stock oder normal supplier
@ -40,6 +41,10 @@ function setGroupBalance(amount) {
groupBalance = amount;
}
function setMinimumBalance(amount) {
minimumBalance = amount;
}
function addData(orderArticleId, itemPrice, itemUnit, itemSubtotal, itemQuantityOthers, itemToleranceOthers, allocated, available) {
var i = orderArticleId;
price[i] = itemPrice;
@ -159,7 +164,7 @@ function updateBalance() {
$('#total_balance').val(asMoney(balance));
// determine bgcolor and submit button state according to balance
var bgcolor = '';
if (balance < 0) {
if (balance < minimumBalance) {
bgcolor = '#FF0000';
$('#submit_button').attr('disabled', 'disabled')
} else {

View file

@ -0,0 +1,186 @@
// Stupid jQuery table plugin.
// Call on a table
// sortFns: Sort functions for your datatypes.
(function($) {
$.fn.stupidtable = function(sortFns) {
return this.each(function() {
var $table = $(this);
sortFns = sortFns || {};
// ==================================================== //
// Utility functions //
// ==================================================== //
// Merge sort functions with some default sort functions.
sortFns = $.extend({}, {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
if (a < b) return -1;
if (a > b) return +1;
return 0;
},
"string-ins": function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
}, sortFns);
// Return the resulting indexes of a sort so we can apply
// this result elsewhere. This returns an array of index numbers.
// return[0] = x means "arr's 0th element is now at x"
var sort_map = function(arr, sort_function, reverse_column) {
var map = [];
var index = 0;
if (reverse_column) {
for (var i = arr.length-1; i >= 0; i--) {
map.push(i);
}
}
else {
var sorted = arr.slice(0).sort(sort_function);
for (var i=0; i<arr.length; i++) {
index = $.inArray(arr[i], sorted);
// If this index is already in the map, look for the next index.
// This handles the case of duplicate entries.
while ($.inArray(index, map) != -1) {
index++;
}
map.push(index);
}
}
return map;
};
// Apply a sort map to the array.
var apply_sort_map = function(arr, map) {
var clone = arr.slice(0),
newIndex = 0;
for (var i=0; i<map.length; i++) {
newIndex = map[i];
clone[newIndex] = arr[i];
}
return clone;
};
// ==================================================== //
// Begin execution! //
// ==================================================== //
// Do sorting when THs are clicked
$table.on("click", "th", function() {
var trs = $table.children("tbody").children("tr");
var $this = $(this);
var th_index = 0;
var dir = $.fn.stupidtable.dir;
$table.find("th").slice(0, $this.index()).each(function() {
var cols = $(this).attr("colspan") || 1;
th_index += parseInt(cols,10);
});
// Determine (and/or reverse) sorting direction, default `asc`
var sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
// Choose appropriate sorting function. If we're sorting descending, check
// for a `data-sort-desc` attribute.
if ( sort_dir == dir.DESC )
var type = $this.data("sort-desc") || $this.data("sort") || null;
else
var type = $this.data("sort") || null;
// Prevent sorting if no type defined
if (type === null) {
return;
}
// Trigger `beforetablesort` event that calling scripts can hook into;
// pass parameters for sorted column index and sorting direction
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
// Run sorting asynchronously on a timout to force browser redraw after
// `beforetablesort` callback. Also avoids locking up the browser too much.
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortMethod = sortFns[type];
// Push either the value of the `data-order-by` attribute if specified
// or just the text() value in this column to column[] for comparison.
trs.each(function(index,tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
column.push(order_by);
});
// Create the sort map. This column having a sort-dir implies it was
// the last column sorted. As long as no data-sort-desc is specified,
// we're free to just reverse the column.
var reverse_column = !!$this.data("sort-dir") && !$this.data("sort-desc");
var theMap = sort_map(column, sortMethod, reverse_column);
// Reset siblings
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
// Replace the content of tbody with the sortedTRs. Strangely (and
// conveniently!) enough, .append accomplishes this for us.
var sortedTRs = $(apply_sort_map(trs, theMap));
$table.children("tbody").append(sortedTRs);
// Trigger `aftertablesort` event. Similar to `beforetablesort`
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
}, 10);
});
});
};
// Enum containing sorting directions
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
})(jQuery);
////////////////////////////////////////////////////////////////////////////////
//
// own additions for automatic initialization of table sorting
//
////////////////////////////////////////////////////////////////////////////////
$(function() {
var stupidtables = $('table.stupidtable');
if(stupidtables.length) {
// Add pseudo links just for matching foodsoft style
$('th[data-sort]', stupidtables).wrapInner('<a href="#" class="stupidlink"></a>');
$('.stupidlink', stupidtables).on('click', function(e) {e.preventDefault();});
// Init stupidtable sorting
stupidtables.stupidtable();
// Update class of sort link after sort to match foodsoft style
stupidtables.on('aftertablesort', function(e, data) {
// Ignore data and use the updated classes in DOM
var stupidthead = $('thead', this);
$('a.stupidlink', stupidthead).removeClass('sortup sortdown');
$('th.sorting-asc a.stupidlink', stupidthead).addClass('sortup');
$('th.sorting-desc a.stupidlink', stupidthead).addClass('sortdown');
});
// Sort tables with a default sort
$('.default-sort', stupidtables).trigger('click');
}
});

View file

@ -1,4 +1,6 @@
/*
*= require bootstrap_and_overrides
*= require select2
*= require token-input-bootstrappy
*= require bootstrap-datepicker
*/

View file

@ -31,10 +31,6 @@ body {
// Example:
// @linkColor: #ff0000;
// Bootstrap datepicker
@import "datepicker";
// Custom styles
// Fix empty dd tags in horizontal dl, see https://github.com/twitter/bootstrap/issues/4062
@ -42,6 +38,22 @@ body {
dd { .clearfix(); }
}
// Do not use additional margin for input in table
.form-horizontal .control-group.control-group-intable,
.form-horizontal .controls.controls-intable {
margin: 0;
}
// Light tooltips without empty space below tables
.tooltip-inner {
color: #000;
background-color: rgb(245,245,245);
border: 1px solid #ccc;
}
.tooltip-inner .table {
margin-bottom: 0;
}
@mainRedColor: #ED0606;
.logo {
@ -208,3 +220,21 @@ tr.unavailable {
dt { width: 160px; }
dd { margin-left: 170px; }
}
.settings {
.settings-group {
margin-bottom: 10px;
.control-label {
margin: 5px 0 0 0;
}
}
.control-group {
margin-bottom: 5px;
}
.control-group.h_wrapper {
margin-bottom: 5px;
}
.control-group.select {
margin-bottom: 15px
}
}

View file

@ -1,10 +1,13 @@
# encoding: utf-8
class ApplicationController < ActionController::Base
include Foodsoft::ControllerExtensions::Locale
helper_method :available_locales
protect_from_forgery
before_filter :select_language, :select_foodcoop, :authenticate, :store_controller, :items_per_page, :set_redirect_to
before_filter :select_foodcoop, :authenticate, :store_controller, :items_per_page, :set_redirect_to
after_filter :remove_controller
# Returns the controller handling the current request.
def self.current
Thread.current[:application_controller]
@ -142,8 +145,4 @@ class ApplicationController < ActionController::Base
{foodcoop: FoodsoftConfig.scope}
end
# Used to prevent accidently switching to :en in production mode.
def select_language
I18n.locale = :de
end
end

View file

@ -32,7 +32,7 @@ class ArticlesController < ApplicationController
end
def new
@article = @supplier.articles.build(:tax => 7.0)
@article = @supplier.articles.build(:tax => FoodsoftConfig[:tax_default])
render :layout => false
end
@ -145,19 +145,22 @@ class ArticlesController < ApplicationController
begin
@articles = Array.new
articles, outlisted_articles = FoodsoftFile::parse(params[:articles]["file"])
no_category = ArticleCategory.new
articles.each do |row|
# fallback to Others category
category = (ArticleCategory.find_by_name(row[:category]) or no_category)
# creates a new article and price
article = Article.new( :name => row[:name],
:note => row[:note],
:manufacturer => row[:manufacturer],
:origin => row[:origin],
:unit => row[:unit],
:article_category => ArticleCategory.find_by_name(row[:category]),
:article_category => category,
:price => row[:price],
:unit_quantity => row[:unit_quantity],
:order_number => row[:number],
:deposit => row[:deposit],
:tax => row[:tax])
:tax => (row[:tax] or FoodsoftConfig[:tax_default]))
# stop parsing, when an article isn't valid
unless article.valid?
raise I18n.t('articles.controller.error_parse', :msg => article.errors.full_messages.join(", "), :line => (articles.index(row) + 2).to_s)
@ -206,7 +209,7 @@ class ArticlesController < ApplicationController
# fills a form whith values of the selected shared_article
def import
@article = SharedArticle.find(params[:shared_article_id]).build_new_article
@article = SharedArticle.find(params[:shared_article_id]).build_new_article(@supplier)
render :action => 'new', :layout => false
end

View file

@ -5,43 +5,25 @@ class DeliveriesController < ApplicationController
def index
@deliveries = @supplier.deliveries.all :order => 'delivered_on DESC'
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @deliveries }
end
end
def show
@delivery = Delivery.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @delivery }
end
end
def new
@delivery = @supplier.deliveries.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @delivery }
end
@delivery.delivered_on = Date.today #TODO: move to model/database
end
def create
@delivery = Delivery.new(params[:delivery])
respond_to do |format|
if @delivery.save
flash[:notice] = I18n.t('deliveries.create.notice')
format.html { redirect_to([@supplier,@delivery]) }
format.xml { render :xml => @delivery, :status => :created, :location => @delivery }
redirect_to [@supplier, @delivery]
else
format.html { render :action => "new" }
format.xml { render :xml => @delivery.errors, :status => :unprocessable_entity }
end
render :action => "new"
end
end
@ -52,15 +34,11 @@ class DeliveriesController < ApplicationController
def update
@delivery = Delivery.find(params[:id])
respond_to do |format|
if @delivery.update_attributes(params[:delivery])
flash[:notice] = I18n.t('deliveries.update.notice')
format.html { redirect_to([@supplier,@delivery]) }
format.xml { head :ok }
redirect_to [@supplier,@delivery]
else
format.html { render :action => "edit" }
format.xml { render :xml => @delivery.errors, :status => :unprocessable_entity }
end
render :action => "edit"
end
end
@ -69,40 +47,60 @@ class DeliveriesController < ApplicationController
@delivery.destroy
flash[:notice] = I18n.t('deliveries.destroy.notice')
respond_to do |format|
format.html { redirect_to(supplier_deliveries_url(@supplier)) }
format.xml { head :ok }
end
redirect_to supplier_deliveries_url(@supplier)
end
def add_stock_article
article = @supplier.stock_articles.build(params[:stock_article])
render :update do |page|
if article.save
logger.debug "new StockArticle: #{article.id}"
page.insert_html :bottom, 'stock_changes', :partial => 'stock_change',
:locals => {:stock_change => article.stock_changes.build, :supplier => @supplier}
# three possibilites to fill a new_stock_article form
# (1) start from blank or use params
def new_stock_article
@stock_article = @supplier.stock_articles.build(params[:stock_article])
page.replace_html 'new_stock_article', :partial => 'stock_article_form',
:locals => {:stock_article => @supplier.stock_articles.build}
render :layout => false
end
# (2) StockArticle as template
def copy_stock_article
@stock_article = StockArticle.find(params[:old_stock_article_id]).dup
render :layout => false
end
# (3) non-stock Article as template
def derive_stock_article
@stock_article = Article.find(params[:old_article_id]).becomes(StockArticle).dup
render :layout => false
end
def create_stock_article
@stock_article = StockArticle.new(params[:stock_article])
if @stock_article.valid? and @stock_article.save
render :layout => false
else
page.replace_html 'new_stock_article', :partial => 'stock_article_form',
:locals => {:stock_article => article}
render :action => 'new_stock_article', :layout => false
end
end
def edit_stock_article
@stock_article = StockArticle.find(params[:stock_article_id])
render :layout => false
end
def update_stock_article
@stock_article = StockArticle.find(params[:stock_article][:id])
if @stock_article.update_attributes(params[:stock_article])
render :layout => false
else
render :action => 'edit_stock_article', :layout => false
end
end
def add_stock_change
@stock_change = StockChange.new
@stock_change.stock_article = StockArticle.find(params[:stock_article_id])
render :layout => false
end
def fill_new_stock_article_form
article = Article.find(params[:article_id])
@supplier = article.supplier
stock_article = @supplier.stock_articles.build(
article.attributes.reject { |attr| attr == ('id' || 'type')}
)
render :partial => 'stock_article_form', :locals => {:stock_article => stock_article}
end
end

View file

@ -65,7 +65,13 @@ class Finance::GroupOrderArticlesController < ApplicationController
def destroy
group_order_article = GroupOrderArticle.find(params[:id])
# only destroy if quantity and tolerance was zero already, so that we don't
# lose what the user ordered, if any
if group_order_article.quantity > 0 or group_order_article.tolerance >0
group_order_article.update_attribute(:result, 0)
else
group_order_article.destroy
end
update_summaries(group_order_article)
@order_article = group_order_article.order_article

View file

@ -42,6 +42,14 @@ class Finance::OrderArticlesController < ApplicationController
def destroy
@order_article = OrderArticle.find(params[:id])
# only destroy if there are no associated GroupOrders; if we would, the requested
# quantity and tolerance would be gone. Instead of destroying, we set all result
# quantities to zero.
if @order_article.group_order_articles.count == 0
@order_article.destroy
else
@order_article.group_order_articles.each { |goa| goa.update_attribute(:result, 0) }
@order_article.update_results!
end
end
end

View file

@ -16,6 +16,7 @@ class HomeController < ApplicationController
def update_profile
if @current_user.update_attributes(params[:user])
session[:locale] = @current_user.locale
redirect_to my_profile_url, notice: I18n.t('home.changes_saved')
else
render :profile

View file

@ -58,6 +58,7 @@ class LoginController < ApplicationController
if @user.save
Membership.new(:user => @user, :group => @invite.group).save!
@invite.destroy
session[:locale] = @user.locale
redirect_to login_url, notice: I18n.t('login.controller.accept_invitation.notice')
end
end

View file

@ -11,6 +11,8 @@ class SessionsController < ApplicationController
if user
session[:user_id] = user.id
session[:scope] = FoodsoftConfig.scope # Save scope in session to not allow switching between foodcoops with one account
session[:locale] = user.locale
if session[:return_to].present?
redirect_to_url = session[:return_to]
session[:return_to] = nil

View file

@ -55,4 +55,9 @@ class StockitController < ApplicationController
render :partial => 'form', :locals => {:stock_article => stock_article}
end
def history
@stock_article = StockArticle.undeleted.find(params[:stock_article_id])
@stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC').each {|s| s.readonly!}
end
end

View file

@ -18,7 +18,7 @@ class SuppliersController < ApplicationController
def new
if params[:shared_supplier_id]
shared_supplier = SharedSupplier.find(params[:shared_supplier_id])
@supplier = shared_supplier.build_supplier(shared_supplier.autofill_attributes)
@supplier = shared_supplier.suppliers.new(shared_supplier.autofill_attributes)
else
@supplier = Supplier.new
end

View file

@ -18,6 +18,9 @@ class TasksController < ApplicationController
def create
@task = Task.new(params[:task])
if params[:periodic]
@task.periodic_task_group = PeriodicTaskGroup.new
end
if @task.save
redirect_to tasks_url, :notice => I18n.t('tasks.create.notice')
else
@ -32,13 +35,20 @@ class TasksController < ApplicationController
def edit
@task = Task.find(params[:id])
@task.current_user_id = current_user.id
if @task.periodic?
flash.now[:alert] = I18n.t('tasks.edit.warning_periodic').html_safe
end
end
def update
@task = Task.find(params[:id])
was_periodic = @task.periodic?
@task.attributes=(params[:task])
if @task.errors.empty? && @task.save
flash[:notice] = I18n.t('tasks.update.notice')
if was_periodic and not @task.periodic?
flash[:notice] = I18n.t('tasks.update.notice_converted')
end
if @task.workgroup
redirect_to workgroup_tasks_url(workgroup_id: @task.workgroup_id)
else
@ -53,7 +63,12 @@ class TasksController < ApplicationController
task = Task.find(params[:id])
# Save user_ids to update apple statistics after destroy
user_ids = task.user_ids
if params[:periodic]
task.periodic_task_group.exclude_tasks_before(task)
task.periodic_task_group.destroy
else
task.destroy
end
task.update_ordergroup_stats(user_ids)
redirect_to tasks_url, :notice => I18n.t('tasks.destroy.notice')

View file

@ -12,20 +12,29 @@ class OrderByArticles < OrderPdf
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
dimrows = []
for goa in order_article.group_order_articles.ordered
rows << [goa.group_order.ordergroup.name,
"#{goa.quantity} + #{goa.tolerance}",
goa.result,
number_with_precision(order_article.price.fc_price * goa.result, precision: 2)]
dimrows << rows.length if goa.result == 0
end
next if rows.length == 0
rows.unshift I18n.t('documents.order_by_articles.rows') # table header
table rows, column_widths: [200,40,40], cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.columns(1..2).align = :right
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
table rows, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.column(0).width = 200
table.columns(1..3).align = :right
table.column(2).font_style = :bold
table.cells.border_width = 1
table.cells.border_color = '666666'
table.rows(0).border_bottom_width = 2
# dim rows which were ordered but not received
dimrows.each { |ri| table.row(ri).text_color = '999999' }
end
move_down 10
end

View file

@ -12,12 +12,10 @@ class OrderByGroups < OrderPdf
def body
# Start rendering
@order.group_orders.each do |group_order|
text group_order.ordergroup.name, size: 9, style: :bold
@order.group_orders.ordered.each do |group_order|
total = 0
rows = []
rows << I18n.t('documents.order_by_groups.rows') # Table Header
dimrows = []
group_order_articles = group_order.group_order_articles.ordered
group_order_articles.each do |goa|
@ -25,15 +23,20 @@ class OrderByGroups < OrderPdf
sub_total = price * goa.result
total += sub_total
rows << [goa.order_article.article.name,
"#{goa.quantity} + #{goa.tolerance}",
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)]
dimrows << rows.length if goa.result == 0
end
rows << [ I18n.t('documents.order_by_groups.sum'), nil, nil, nil, nil, number_with_precision(total, precision: 2)]
next if rows.length == 0
rows << [ I18n.t('documents.order_by_groups.sum'), nil, nil, nil, nil, nil, number_with_precision(total, precision: 2)]
rows.unshift I18n.t('documents.order_by_groups.rows') # Table Header
table rows, column_widths: [250,50,50,50,50,50], cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
text group_order.ordergroup.name, size: 9, style: :bold
table rows, width: 500, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
# borders
table.cells.borders = []
table.row(0).borders = [:bottom]
@ -41,8 +44,14 @@ class OrderByGroups < OrderPdf
table.cells.border_width = 1
table.cells.border_color = '666666'
table.columns(1..3).align = :right
table.columns(5).align = :right
table.column(0).width = 240
table.column(2).font_style = :bold
table.columns(1..4).align = :right
table.column(6).align = :right
table.column(6).font_style = :bold
# dim rows which were ordered but not received
dimrows.each { |ri| table.row(ri).text_color = '999999' }
end
move_down 15

View file

@ -20,20 +20,28 @@ class OrderFax < OrderPdf
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
unless @order.supplier.try(:customer_number).blank?
text "#{I18n.t('simple_form.labels.supplier.customer_number')}: #{@order.supplier[:customer_number]}", size: 9, align: :right
move_down 5
end
unless contact[:phone].blank?
text "#{I18n.t('simple_form.labels.supplier.phone')}: #{contact[:phone]}", size: 9, align: :right
move_down 5
end
unless contact[:email].blank?
text "#{I18n.t('simple_form.labels.supplier.email')}: #{contact[:email]}", size: 9, align: :right
end
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
unless @order.supplier.try(:fax).blank?
move_down 5
text "#{I18n.t('simple_form.labels.supplier.fax')}: #{@order.supplier.try(:fax)}"
text "#{I18n.t('simple_form.labels.supplier.fax')}: #{@order.supplier[:fax]}"
end
end
move_down 5
@ -42,25 +50,37 @@ class OrderFax < OrderPdf
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)}"
unless @order.supplier.try(:contact_person).blank?
text "#{I18n.t('simple_form.labels.supplier.contact_person')}: #{@order.supplier[:contact_person]}"
move_down 10
end
# Articles
total = 0
data = [I18n.t('documents.order_fax.rows')]
data += @order.order_articles.ordered.all(include: :article).collect do |a|
subtotal = a.units_to_order * a.price.unit_quantity * a.price.price
total += subtotal
[a.article.order_number,
a.units_to_order,
a.article.name,
a.price.unit_quantity,
a.article.unit,
a.price.price]
number_to_currency(a.price.price),
number_to_currency(subtotal)]
end
data << [I18n.t('documents.order_fax.total'), nil, nil, nil, nil, nil, number_to_currency(total)]
table data, cell_style: {size: 8, overflow: :shrink_to_fit} do |table|
table.header = true
table.cells.border_width = 1
table.cells.border_color = '666666'
table.row(0).border_bottom_width = 2
table.columns(1).align = :right
table.columns(3..5).align = :right
table.columns(3..6).align = :right
table.row(data.length-1).columns(0..5).borders = [:top, :bottom]
table.row(data.length-1).columns(0).borders = [:top, :bottom, :left]
table.row(data.length-1).border_top_width = 2
end
#font_size: 8,
#vertical_padding: 3,

View file

@ -159,4 +159,12 @@ module ApplicationHelper
flash_messages.join("\n").html_safe
end
# render base errors in a form after failed validation
# http://railsapps.github.io/twitter-bootstrap-rails.html
def base_errors resource
return '' if (resource.errors.empty?) or (resource.errors[:base].empty?)
messages = resource.errors[:base].map { |msg| content_tag(:li, msg) }.join
render :partial => 'shared/base_errors', :locals => {:error_messages => messages}
end
end

View file

@ -10,8 +10,28 @@ module DeliveriesHelper
end
end
def stock_articles_for_select(supplier)
supplier.stock_articles.undeleted.reorder('articles.name ASC').map {|a| ["#{a.name} (#{number_to_currency a.price}/#{a.unit})", a.id] }
def articles_for_select2(supplier)
supplier.articles.undeleted.reorder('articles.name ASC').map {|a| {:id => a.id, :text => "#{a.name} (#{number_to_currency a.price}/#{a.unit})"} }
end
def stock_articles_for_table(supplier)
supplier.stock_articles.undeleted.reorder('articles.name ASC')
end
def stock_change_remove_link(stock_change_form)
return link_to t('.remove_article'), "#", :class => 'remove_new_stock_change btn btn-small' if stock_change_form.object.new_record?
output = stock_change_form.hidden_field :_destroy
output += link_to t('.remove_article'), "#", :class => 'destroy_stock_change btn btn-small'
return output.html_safe
end
def stock_article_price_hint(stock_article)
t('simple_form.hints.stock_article.edit_stock_article.price',
:stock_article_copy_link => link_to(t('.copy_stock_article'),
copy_stock_article_supplier_deliveries_path(@supplier, :old_stock_article_id => stock_article.id),
:remote => true
)
)
end
end

View file

@ -4,7 +4,7 @@ module Finance::OrderArticlesHelper
if @order.stockit?
StockArticle.order('articles.name')
else
@order.supplier.articles.order('articles.name')
@order.supplier.articles.undeleted.order('articles.name')
end
end
end

View file

@ -4,4 +4,14 @@ module StockitHelper
class_names << "unavailable" if article.quantity_available <= 0
class_names.join(" ")
end
def link_to_stock_change_reason(stock_change)
if stock_change.delivery_id
link_to t('.delivery'), supplier_delivery_path(stock_change.delivery.supplier, stock_change.delivery)
elsif stock_change.order_id
link_to t('.order'), order_path(stock_change.order)
elsif stock_change.stock_taking_id
link_to t('.stock_taking'), stock_taking_path(stock_change.stock_taking)
end
end
end

View file

@ -0,0 +1,6 @@
module SuppliersHelper
def associated_supplier_names(shared_supplier)
"(#{shared_supplier.suppliers.map(&:name).join(', ')})"
end
end

View file

@ -2,11 +2,15 @@ class Delivery < ActiveRecord::Base
belongs_to :supplier
has_one :invoice
has_many :stock_changes, :dependent => :destroy
has_many :stock_changes,
:dependent => :destroy,
:include => 'stock_article',
:order => 'articles.name ASC'
scope :recent, :order => 'created_at DESC', :limit => 10
validates_presence_of :supplier_id
validates_presence_of :supplier_id, :delivered_on
validate :stock_articles_must_be_unique
accepts_nested_attributes_for :stock_changes, :allow_destroy => :true
@ -16,6 +20,18 @@ class Delivery < ActiveRecord::Base
end
end
def includes_article?(article)
self.stock_changes.map{|stock_change| stock_change.stock_article.id}.include? article.id
end
protected
def stock_articles_must_be_unique
unless stock_changes.reject{|sc| sc.marked_for_destruction?}.map {|sc| sc.stock_article.id}.uniq!.nil?
errors.add(:base, I18n.t('model.delivery.each_stock_article_must_be_unique'))
end
end
end

View file

@ -17,6 +17,8 @@ class GroupOrder < ActiveRecord::Base
scope :in_open_orders, joins(:order).merge(Order.open)
scope :in_finished_orders, joins(:order).merge(Order.finished_not_closed)
scope :ordered, :include => :ordergroup, :order => 'groups.name'
# Generate some data for the javascript methods in ordering view
def load_data
data = {}

View file

@ -14,7 +14,7 @@ class GroupOrderArticle < ActiveRecord::Base
validates_inclusion_of :tolerance, :in => 0..99
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
scope :ordered, :conditions => 'result > 0'
scope :ordered, :conditions => 'group_order_articles.result > 0 OR group_order_articles.quantity > 0 OR group_order_articles.tolerance > 0', :include => {:group_order => :ordergroup}, :order => 'groups.name'
localize_input_of :result

View file

@ -108,10 +108,10 @@ class Ordergroup < Group
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
def uniqueness_of_name
id = new_record? ? nil : self.id
group = Ordergroup.where('groups.id != ? AND groups.name = ?', id, name).first
if group.present?
message = group.deleted? ? :taken_with_deleted : :taken
group = Ordergroup.where('groups.name = ?', name)
group = group.where('groups.id != ?', self.id) unless new_record?
if group.exists?
message = group.first.deleted? ? :taken_with_deleted : :taken
errors.add :name, message
end
end

View file

@ -0,0 +1,29 @@
class PeriodicTaskGroup < ActiveRecord::Base
has_many :tasks, dependent: :destroy
PeriodDays = 7
def has_next_task?
return false if tasks.empty?
return false if tasks.first.due_date.nil?
return true
end
def create_next_task
template_task = tasks.first
self.next_task_date ||= template_task.due_date + PeriodDays
next_task = template_task.dup
next_task.due_date = next_task_date
next_task.save
self.next_task_date += PeriodDays
self.save
end
def exclude_tasks_before(task)
tasks.where("due_date < '#{task.due_date}'").each do |t|
t.update_attribute(:periodic_task_group, nil)
end
end
end

View file

@ -7,8 +7,8 @@ class SharedArticle < ActiveRecord::Base
belongs_to :shared_supplier, :foreign_key => :supplier_id
def build_new_article
shared_supplier.supplier.articles.build(
def build_new_article(supplier)
supplier.articles.build(
:name => name,
:unit => unit,
:note => note,

View file

@ -5,7 +5,7 @@ class SharedSupplier < ActiveRecord::Base
# set correct table_name in external DB
self.table_name = 'suppliers'
has_one :supplier
has_many :suppliers
has_many :shared_articles, :foreign_key => :supplier_id
# These set of attributes are used to autofill attributes of new supplier,

View file

@ -18,6 +18,10 @@ class StockArticle < Article
joins(:order).where("orders.state = 'open' OR orders.state = 'finished'").sum(:units_to_order)
end
def quantity_history
stock_changes.reorder('stock_changes.created_at ASC').map{|s| s.quantity}.cumulative_sum
end
def self.stock_value
available.collect { |a| a.quantity * a.gross_price }.sum
end

View file

@ -1,6 +1,7 @@
class StockChange < ActiveRecord::Base
belongs_to :delivery
belongs_to :order
belongs_to :stock_taking
belongs_to :stock_article
validates_presence_of :stock_article_id, :quantity

View file

@ -13,11 +13,9 @@ class Supplier < ActiveRecord::Base
:delivery_days, :order_howto, :note, :shared_supplier_id, :min_order_quantity
validates :name, :presence => true, :length => { :in => 4..30 }
validates :phone, :presence => true, :length => { :in => 8..20 }
validates :phone, :presence => true, :length => { :in => 8..25 }
validates :address, :presence => true, :length => { :in => 8..50 }
validates_length_of :order_howto, :note, maximum: 250
validates_length_of :phone, :in => 8..20
validates_length_of :address, :in => 8..50
validate :uniqueness_of_name
scope :undeleted, -> { where(deleted_at: nil) }
@ -82,10 +80,10 @@ class Supplier < ActiveRecord::Base
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
def uniqueness_of_name
id = new_record? ? nil : self.id
supplier = Supplier.where('suppliers.id != ? AND suppliers.name = ?', id, name).first
if supplier.present?
message = supplier.deleted? ? :taken_with_deleted : :taken
supplier = Supplier.where('suppliers.name = ?', name)
supplier = supplier.where('suppliers.id != ?', self.id) unless new_record?
if supplier.exists?
message = supplier.first.deleted? ? :taken_with_deleted : :taken
errors.add :name, message
end
end

View file

@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
class Task < ActiveRecord::Base
has_many :assignments, :dependent => :destroy
has_many :users, :through => :assignments
belongs_to :workgroup
belongs_to :periodic_task_group
scope :non_group, where(workgroup_id: nil, done: false)
scope :done, where(done: true)
@ -16,7 +18,9 @@ class Task < ActiveRecord::Base
validates :required_users, :presence => true
validates_numericality_of :duration, :required_users, :only_integer => true, :greater_than => 0
validates_length_of :description, maximum: 250
validates :done, exclusion: { in: [true] }, if: :periodic?, on: :create
before_save :exclude_from_periodic_task_group, if: :changed?, unless: :new_record?
after_save :update_ordergroup_stats
# Find all tasks, for which the current user should be responsible
@ -46,6 +50,10 @@ class Task < ActiveRecord::Base
end
end
def periodic?
not periodic_task_group.nil?
end
def is_assigned?(user)
self.assignments.detect {|ass| ass.user_id == user.id }
end
@ -100,5 +108,10 @@ class Task < ActiveRecord::Base
def update_ordergroup_stats(user_ids = self.user_ids)
Ordergroup.joins(:users).where(users: {id: user_ids}).each(&:update_stats!)
end
def exclude_from_periodic_task_group
self.periodic_task_group = nil
true
end
end

View file

@ -3,6 +3,7 @@
require 'digest/sha1'
# specific user rights through memberships (see Group)
class User < ActiveRecord::Base
include RailsSettings::Extend
#TODO: acts_as_paraniod ??
has_many :memberships, :dependent => :destroy
@ -19,7 +20,10 @@ class User < ActiveRecord::Base
has_many :pages, :foreign_key => 'updated_by'
has_many :created_orders, :class_name => 'Order', :foreign_key => 'created_by_user_id', :dependent => :nullify
attr_accessor :password, :setting_attributes
attr_accessor :password, :settings_attributes
# makes the current_user (logged-in-user) available in models
cattr_accessor :current_user
validates_presence_of :nick, :email
validates_presence_of :password, :on => :create
@ -32,45 +36,29 @@ class User < ActiveRecord::Base
validates_length_of :password, :in => 5..25, :allow_blank => true
before_validation :set_password
after_save :update_settings
# Adds support for configuration settings (through "settings" attribute).
acts_as_configurable
# makes the current_user (logged-in-user) available in models
cattr_accessor :current_user
# User settings keys
# returns the User-settings and the translated description
def self.setting_keys
{
"notify.orderFinished" => I18n.t('model.user.notify.order_finished'),
"notify.negativeBalance" => I18n.t('model.user.notify.negative_balance'),
"notify.upcoming_tasks" => I18n.t('model.user.notify.upcoming_tasks'),
"messages.sendAsEmail" => I18n.t('model.user.notify.send_as_email'),
"profile.phoneIsPublic" => I18n.t('model.user.notify.phone_is_public'),
"profile.emailIsPublic" => I18n.t('model.user.notify.email_is_public'),
"profile.nameIsPublic" => I18n.t('model.user.notify.name_is_public')
}
end
# retuns the default setting for a NEW user
# for old records nil will returned
# TODO: integrate default behaviour in acts_as_configurable plugin
def settings_default(setting)
# define a default for the settings
defaults = {
"messages.sendAsEmail" => true,
"notify.upcoming_tasks" => true
}
return true if self.new_record? && defaults[setting]
after_initialize do
settings.defaults['profile'] = { 'language' => I18n.default_locale } unless settings.profile
settings.defaults['messages'] = { 'send_as_email' => true } unless settings.messages
settings.defaults['notify'] = { 'upcoming_tasks' => true } unless settings.notify
end
def update_settings
unless setting_attributes.nil?
for setting in User::setting_keys.keys
self.settings[setting] = setting_attributes[setting] && setting_attributes[setting] == '1' ? '1' : nil
after_save do
return if settings_attributes.nil?
settings_attributes.each do |key, value|
value.each do |k, v|
case v
when '1'
value[k] = true
when '0'
value[k] = false
end
end
self.settings.merge!(key, value)
end
end
def locale
settings.profile['language']
end
def name
@ -78,7 +66,7 @@ class User < ActiveRecord::Base
end
def receive_email?
settings['messages.sendAsEmail'] == "1" && email.present?
settings.messages['send_as_email'] && email.present?
end
# Sets the user's password. It will be stored encrypted along with a random salt.

View file

@ -3,54 +3,12 @@ class Workgroup < Group
has_many :tasks
# returns all non-finished tasks
has_many :open_tasks, :class_name => 'Task', :conditions => ['done = ?', false], :order => 'due_date ASC'
has_many :open_tasks, :class_name => 'Task', :conditions => ['done = ?', false], order: 'due_date ASC, name ASC'
validates_uniqueness_of :name
validates_presence_of :task_name, :weekday, :task_required_users, :next_weekly_tasks_number,
:if => :weekly_task
validates_numericality_of :next_weekly_tasks_number, :greater_than => 0, :less_than => 21, :only_integer => true,
:if => :weekly_task
validates_length_of :task_description, maximum: 250
validate :last_admin_on_earth, :on => :update
before_destroy :check_last_admin_group
def self.weekdays
days = I18n.t('date.day_names')
(0..days.length-1).map {|i| [days[i], i.to_s]}
end
# Returns an Array with date-objects to represent the next weekly-tasks
def next_weekly_tasks
# our system starts from 0 (sunday) to 6 (saturday)
# get difference between groups weekday and now
diff = self.weekday - Time.now.wday
if diff >= 0
# weektask is in current week
nextTask = diff.day.from_now
else
# weektask is in the next week
nextTask = (diff + 7).day.from_now
end
# now generate the Array
nextTasks = Array.new
next_weekly_tasks_number.times do
nextTasks << nextTask.to_date
nextTask = 1.week.from_now(nextTask)
end
return nextTasks
end
def task_attributes(date)
{
:name => task_name,
:description => task_description,
:due_date => date,
:required_users => task_required_users,
:duration => task_duration,
:weekly => true
}
end
protected
# Check before destroy a group, if this is the last group with admin role

View file

@ -20,10 +20,10 @@
.well
%h4= t '.preference'
%table.table
- for setting in User::setting_keys.keys
- @user.settings.profile.each do |key, setting|
%tr
%td= User::setting_keys[setting]
%td= @user.settings[setting] == '1' ? t('simple_form.yes') : t('simple_form.no')
%td= t("simple_form.labels.settings.profile.#{key}")
%td= (setting != true and setting != false) ? setting : (setting === true ? t('simple_form.yes') : t('simple_form.no'))
.span3
.well
%h4= t '.groupabos'

View file

@ -19,7 +19,9 @@
- @articles.each_with_index do |article, index|
= fields_for "articles[#{article.id || index}]", article do |form|
%tr
%td= form.check_box 'availability'
%td
= yield form # allow to add hidden fields to form
= form.check_box 'availability'
%td= form.text_field 'name', class: 'input-medium'
%td= form.text_field 'unit', class: 'input-mini'
%td= form.text_field 'price', class: 'input-mini'

View file

@ -2,7 +2,9 @@
%p= t('.body').html_safe
= form_tag(create_from_upload_supplier_articles_path(@supplier)) do
= render 'edit_all_table'
= render layout: 'edit_all_table' do |form|
= form.hidden_field :manufacturer
= form.hidden_field :origin
.form-actions
= submit_tag t('.submit', supplier: @supplier.name), class: 'btn btn-primary'
= link_to t('ui.or_cancel'), upload_supplier_articles_path(@supplier)

View file

@ -1,45 +1,134 @@
- content_for :javascript do
:javascript
$(function() {
$('.destroy_stock_change').live('click', function() {
$(this).prev('input').val('1').parent().hide();
$('#stock_changes').on('click', '.destroy_stock_change', function() {
$(this).prev('input').val('1'); // check for destruction
var stock_change = $(this).closest('tr');
stock_change.hide(); // do not remove (to ensure destruction)
stock_change.removeAttr('id'); // remove id to allow re-adding
mark_article_for_delivery( stock_change.data('id') );
return false;
});
$('.remove_new_stock_change').live('click', function() {
$(this).parent().remove();
$('#stock_changes').on('click', '.remove_new_stock_change', function() {
var stock_change = $(this).closest('tr');
stock_change.remove();
mark_article_for_delivery( stock_change.data('id') );
return false;
})
$('#new_stock_article').removeAttr('disabled').select2({
placeholder: '#{t '.create_stock_article'}',
data: #{articles_for_select2(@supplier).to_json},
createSearchChoice: function(term) {
return {
id: 'new',
text: term
};
},
formatResult: function(result, container, query, escapeMarkup) {
if(result.id == 'new') {
return result.text + ' (#{t '.create_from_blank'})';
}
var markup=[];
Select2.util.markMatch(result.text, query.term, markup, escapeMarkup);
return markup.join("");
}
}).on('change', function(e) {
var selectedArticle = $(e.currentTarget).select2('data');
if(!selectedArticle) {
return false;
}
if('new' == selectedArticle.id) {
$.ajax({
url: '#{new_stock_article_supplier_deliveries_path(@supplier)}',
type: 'get',
data: {stock_article: {name: selectedArticle.text}},
contentType: 'application/json; charset=UTF-8'
});
$('#new_stock_article').select2('data', null);
return true;
}
if('' != selectedArticle.id) {
$.ajax({
url: '#{derive_stock_article_supplier_deliveries_path(@supplier)}',
type: 'get',
data: {old_article_id: selectedArticle.id},
contentType: 'application/json; charset=UTF-8'
});
$('#new_stock_article').select2('data', null);
return true;
}
});
enablePriceTooltips();
});
function mark_article_for_delivery(stock_article_id) {
var articleTr = $('#stock_article_' + stock_article_id);
if( is_article_available_for_delivery(stock_article_id) ) {
articleTr.removeClass('unavailable');
$('.button-add-stock-change', articleTr).removeAttr('disabled');
}
else {
articleTr.addClass('unavailable');
$('.button-add-stock-change', articleTr).attr('disabled', 'disabled');
}
}
function is_article_available_for_delivery(stock_article_id) {
return ( 0 == $('#stock_change_stock_article_' + stock_article_id).length );
}
function enablePriceTooltips(context) {
$('[data-toggle~="tooltip"]', context).tooltip({
animation: false,
html: true,
placement: 'left'
});
}
= simple_form_for [@supplier, @delivery], validate: true do |f|
= f.hidden_field :supplier_id
#stock_changes
= f.fields_for :stock_changes do |stock_change_form|
%p
= stock_change_form.select :stock_article_id, stock_articles_for_select(@supplier)
Menge
= stock_change_form.text_field :quantity, size: 5, autocomplete: 'off'
= stock_change_form.hidden_field :_destroy
= link_to t('.remove_article'), "#", class: 'destroy_stock_change'
%p
= link_to t('.add_article'), {action: 'add_stock_change', supplier_id: @supplier.id}, remote: true
%p
%small= t('.note_new_article', new_link: link_to(t('.note_new_article_link'), new_stock_article_path)).html_safe
%hr/
= f.error_notification
= base_errors f.object
= f.association :supplier, :as => :hidden
%h2= t '.title_select_stock_articles'
%table#stock_articles_for_adding.table.table-hover.stupidtable
%thead
%tr
%th.default-sort{:data => {:sort => 'string'}}= t '.article'
%th= t '.price'
%th= t '.unit'
%th= t '.category'
%th= t '.actions'
%tfoot
%tr
%th{:colspan => 5}
- if articles_for_select2(@supplier).empty?
= link_to t('.create_stock_article'), new_stock_article_supplier_deliveries_path(@supplier), :remote => true, :class => 'btn'
- else
%input#new_stock_article{:style => 'width: 500px;'}
%tbody
- for article in stock_articles_for_table(@supplier)
= render :partial => 'stock_article_for_adding', :locals => {:article => article}
%h2= t '.title_fill_quantities'
%table.table#stock_changes.stupidtable
%thead
%tr
%th.default-sort{:data => {:sort => 'string'}}= t '.article'
%th= t '.price'
%th= t '.unit'
%th= t '.quantity'
%th= t '.actions'
%tbody
= f.simple_fields_for :stock_changes do |stock_change_form|
= render :partial => 'stock_change_fields', :locals => {:f => stock_change_form}
%h2= t '.title_finish_delivery'
= f.input :delivered_on, as: :date_picker
= f.input :note, input_html: {size: '35x4'}
.form-actions
= f.submit class: 'btn btn-primary'
= link_to t('ui.or_cancel'), supplier_deliveries_path(@supplier)
/
TODO: Fix this!!
.span6
%h2= t '.new_article.title'
%p
= t('.new_article.search', supplier: @supplier.name).html_safe + ': '
= text_field_tag 'article_name'
%hr/
#stock_article_form
= render 'stock_article_form', stock_article: @supplier.stock_articles.build

View file

@ -0,0 +1,11 @@
- css_class = ( @delivery and @delivery.includes_article? article ) ? ( 'unavailable' ) : ( false )
%tr{:id => "stock_article_#{article.id}", :class => css_class}
%td= article.name
%td{:data => {:toggle => :tooltip, :title => render(:partial => 'shared/article_price_info', :locals => {:article => article})}}= number_to_currency article.price
%td= article.unit
%td= article.article_category.name
%td
= link_to t('.action_edit'), edit_stock_article_supplier_deliveries_path(@supplier, :stock_article_id => article.id), remote: true, class: 'btn btn-mini'
= link_to t('.action_other_price'), copy_stock_article_supplier_deliveries_path(@supplier, :old_stock_article_id => article.id), remote: true, class: 'btn btn-mini'
- deliver_button_disabled = ( @delivery and @delivery.includes_article? article ) ? ( 'disabled' ) : ( false )
= link_to t('.action_add_to_delivery'), add_stock_change_supplier_deliveries_path(@supplier, :stock_article_id => article.id), :method => :post, remote: true, class: 'button-add-stock-change btn btn-mini btn-primary', disabled: deliver_button_disabled

View file

@ -1,14 +1,23 @@
= simple_form_for stock_article, url: add_stock_article_supplier_deliveries_path(@supplier), remote: true,
validate: true do |f|
= f.hidden_field :supplier_id
- url = ( stock_article.new_record? ) ? ( create_stock_article_supplier_deliveries_path(@supplier) ) : ( update_stock_article_supplier_deliveries_path(@supplier) )
= simple_form_for stock_article, url: url, remote: true, validate: true do |f|
= f.association :supplier, :as => :hidden
= f.hidden_field :id unless stock_article.new_record?
.modal-header
= link_to t('ui.marks.close').html_safe, '#', class: 'close', data: {dismiss: 'modal'}
%h3= t 'activerecord.models.stock_article'
.modal-body
= f.input :name
= f.input :unit
= f.input :note
- if stock_article.new_record?
= f.input :price
= f.input :tax, :wrapper => :append do
= f.input_field :tax
%span.add-on %
-# untested, because this view is currently not included (?)
= f.input :deposit
- else
= f.input :price, :input_html => {:disabled => 'disabled'}, :hint => stock_article_price_hint(stock_article)
= f.association :article_category
= f.submit class: 'btn'
.modal-footer
= link_to t('ui.close'), '#', class: 'btn', data: {dismiss: 'modal'}
= f.submit :class => 'btn btn-primary', 'data-disable-with' => t('ui.please_wait')

View file

@ -1,6 +1,6 @@
%p
= fields_for "delivery[new_stock_changes][]", stock_change do |form|
= form.select :stock_article_id, stock_articles_for_select(supplier)
Menge
= form.text_field :quantity, :size => 5, :autocomplete => 'off'
= link_to t('.remove_article'), "#", :class => 'remove_new_stock_change'
- if stock_change.stock_article.new_record?
= simple_fields_for "delivery[new_stock_changes_new_stock_article][]", stock_change do |f|
= render :partial => 'stock_change_fields', :locals => {:f => f}
- else
= simple_fields_for "delivery[new_stock_changes][]", stock_change do |f|
= render :partial => 'stock_change_fields', :locals => {:f => f}

View file

@ -0,0 +1,10 @@
- stock_change = f.object
- stock_article = stock_change.stock_article
%tr{:id => "stock_change_stock_article_#{stock_article.id}", :data => {:id => stock_article.id}}
%td
%span.stock_article_name= stock_change.stock_article.name
= f.association :stock_article, :as => :hidden
%td.price{:data => {:toggle => :tooltip, :title => render(:partial => 'shared/article_price_info', :locals => {:article => stock_article})}}= number_to_currency stock_article.price
%td.unit= stock_change.stock_article.unit
%td= f.input :quantity, :wrapper => :intable, :input_html => {:class => 'stock-change-quantity', :autocomplete => :off}
%td= stock_change_remove_link f

View file

@ -0,0 +1,25 @@
(function(w) {
if(!is_article_available_for_delivery(<%= @stock_change.stock_article.id %>)) {
return false;
}
$('#stock_changes tr').removeClass('success');
var stock_change = $(
'<%= j(render(:partial => 'stock_change', :locals => {:stock_change => @stock_change})) %>'
).addClass('success');
enablePriceTooltips(stock_change);
$('#stock_changes').append(stock_change);
mark_article_for_delivery(<%= @stock_change.stock_article.id %>);
updateSort('#stock_changes');
var quantity = w.prompt('<%= j(t('.how_many_units', :unit => @stock_change.stock_article.unit, :name => @stock_change.stock_article.name)) %>'); <%# how to properly escape here? %>
if(null === quantity) {
stock_change.remove();
mark_article_for_delivery(<%= @stock_change.stock_article.id %>);
return false;
}
$('input.stock-change-quantity', stock_change).val(quantity);
})(window);

View file

@ -1 +0,0 @@
$('#stock_changes').append('#{escape_javascript(render(:partial => 'stock_change', :locals => {:stock_change => StockChange.new, :supplier => @supplier}))}');

View file

@ -0,0 +1,5 @@
$('#modalContainer').html(
'<%= j(render(:partial => "stock_article_form", :locals => {:stock_article => @stock_article})) %>'
);
$('#modalContainer').modal();

View file

@ -0,0 +1,17 @@
$('div.container-fluid').prepend(
'<%= j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.notice', :name => @stock_article.name)})) %>'
);
(function() {
$('#stock_articles_for_adding tr').removeClass('success');
var stock_article_for_adding = $(
'<%= j(render(:partial => 'stock_article_for_adding', :locals => {:article => @stock_article})) %>'
).addClass('success');
enablePriceTooltips(stock_article_for_adding);
$('#stock_articles_for_adding tbody').append(stock_article_for_adding);
updateSort('#stock_articles_for_adding');
})();
$('#modalContainer').modal('hide');

View file

@ -0,0 +1,5 @@
$('#modalContainer').html(
'<%= j(render(:partial => "stock_article_form", :locals => {:stock_article => @stock_article})) %>'
);
$('#modalContainer').modal();

View file

@ -0,0 +1,5 @@
$('#modalContainer').html(
'<%= j(render(:partial => "stock_article_form", :locals => {:stock_article => @stock_article})) %>'
);
$('#modalContainer').modal();

View file

@ -0,0 +1,5 @@
$('#modalContainer').html(
'<%= j(render(:partial => "stock_article_form", :locals => {:stock_article => @stock_article})) %>'
);
$('#modalContainer').modal();

View file

@ -0,0 +1,33 @@
$('div.container-fluid').prepend(
'<%= j(render(:partial => 'shared/alert_success', :locals => {:alert_message => t('.notice', :name => @stock_article.name)})) %>'
);
(function() {
// update entry in stock_article table
$('#stock_articles_for_adding tr').removeClass('success');
var stock_article_for_adding = $(
'<%= j(render(:partial => 'stock_article_for_adding', :locals => {:article => @stock_article, :delivery => @delivery})) %>'
).addClass('success');
enablePriceTooltips(stock_article_for_adding);
$('#stock_article_<%= @stock_article.id %>').replaceWith(stock_article_for_adding);
updateSort('#stock_articles_for_adding');
mark_article_for_delivery(<%= @stock_article.id %>);
// update entry in stock_changes table
$('#stock_changes tr').removeClass('success');
var stock_change_entry = $('#stock_change_stock_article_<%= @stock_article.id %>');
$('.stock_article_name', stock_change_entry).text('<%= j(@stock_article.name) %>');
$('.unit', stock_change_entry).text('<%= j(@stock_article.unit) %>');
stock_change_entry.addClass('success');
updateSort('#stock_changes');
})();
$('#modalContainer').modal('hide');

View file

@ -12,7 +12,7 @@
.well.well-small
%h3= t('.notes_and_journal')
#note
- unless @order.note.empty?
- unless @order.note.blank?
= simple_format @order.note
- else
%p= t('.comment_on_transaction')

View file

@ -5,7 +5,7 @@
%thead
%tr
%th= sort_link_helper t('.name'), "name", :per_page => @per_page
%th Kontakt
%th= t '.contact'
%th.numeric= sort_link_helper t('.account_balance'), "account_balance", :per_page => @per_page
%th
%tbody

View file

@ -14,9 +14,9 @@
- for user in @users
%tr
%td= user.nick
%td= user.name if @current_user.role_admin? || user.settings["profile.nameIsPublic"] == '1'
%td= user.email if @current_user.role_admin? || user.settings["profile.emailIsPublic"] == '1'
%td= user.phone if @current_user.role_admin? || user.settings["profile.phoneIsPublic"] == '1'
%td= user.name if @current_user.role_admin? || user.settings.profile["name_is_public"]
%td= user.email if @current_user.role_admin? || user.settings.profile["email_is_public"]
%td= user.phone if @current_user.role_admin? || user.settings.profile["phone_is_public"]
%td= user.ordergroup_name
%td= user.workgroups.collect(&:name).join(', ')
%td= link_to_new_message(message_params: {mail_to: user.id})

View file

@ -4,6 +4,7 @@
#{data_to_js(@ordering_data)}
setGroupBalance(#{@ordering_data[:available_funds]});
setCurrencyFormat("#{t('number.currency.format.separator')}", #{t('number.currency.format.precision')}, "#{t('number.currency.format.unit')}");
setMinimumBalance(#{FoodsoftConfig[:minimum_balance] or 0});
setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]});
setStockit(#{@order.stockit?});
});

View file

@ -22,5 +22,7 @@
Javascripts
\==================================================
/ Placed at the end of the document so the pages load faster
:javascript
I18n = {locale: '#{j(I18n.locale.to_s)}'}
= javascript_include_tag "application"
= yield(:javascript)

View file

@ -45,26 +45,6 @@
= f.label :role_orders
%br/
= f.check_box :role_orders
%p
= f.label :weekly_task
%br/
= f.check_box :weekly_task
%p
= f.label :weekday
%br/
= f.text_field :weekday
%p
= f.label :task_name
%br/
= f.text_field :task_name
%p
= f.label :task_description
%br/
= f.text_field :task_description
%p
= f.label :task_required_users
%br/
= f.text_field :task_required_users
%p
= f.label :deleted_at
%br/

View file

@ -12,11 +12,6 @@
%th Role Article Meta
%th Role Finance
%th Role Orders
%th Weekly Task
%th Weekday
%th Task Name
%th Task Description
%th Task Required Users
%th Deleted At
%th Contact Person
%th Contact Phone
@ -34,11 +29,6 @@
%td= h ordergroup.role_article_meta
%td= h ordergroup.role_finance
%td= h ordergroup.role_orders
%td= h ordergroup.weekly_task
%td= h ordergroup.weekday
%td= h ordergroup.task_name
%td= h ordergroup.task_description
%td= h ordergroup.task_required_users
%td= h ordergroup.deleted_at
%td= h ordergroup.contact_person
%td= h ordergroup.contact_phone

View file

@ -29,7 +29,10 @@
- if order.stockit?
%td= units
- else
%td= "#{order_article.quantity} + #{order_article.tolerance}" if unit_quantity > 1
- if unit_quantity > 1 or order_article.tolerance > 0
%td= "#{order_article.quantity} + #{order_article.tolerance}"
- else
%td= "#{order_article.quantity}"
%td= units
%p
= t '.prices_sum'

View file

@ -56,7 +56,7 @@
%pre
* #{t '.help.list_item_1'}
%pre
** #{t '.help_list_item_2'}
** #{t '.help.list_item_2'}
%tr
%td= t '.help.ordered_list'
%td

View file

@ -0,0 +1,5 @@
.alert.fade.in.alert-success
%a.close{:href => '#', :data => {:dismiss => 'alert'}}
= t('ui.marks.close').html_safe
= t('ui.marks.success').html_safe
= alert_message

View file

@ -0,0 +1,17 @@
%table.table.table-condensed
%tr
%th= t 'activerecord.attributes.article.price'
%td.numeric= number_to_currency article.price
%tr
%th= t 'activerecord.attributes.article.deposit'
%td.numeric= number_to_currency article.deposit
%tr
%th= t 'activerecord.attributes.article.tax'
%td.numeric= number_to_percentage article.tax
- unless article.fc_price == article.gross_price
%tr
%th= t 'activerecord.attributes.article.fc_share'
%td.numeric= number_to_currency(article.fc_price-article.gross_price)
%tr
%th= t 'activerecord.attributes.article.fc_price'
%td.numeric= number_to_currency article.fc_price

View file

@ -2,8 +2,10 @@
%thead
%tr
%th{:style => 'width:70%'}= t '.ordergroup'
%th= t '.ordered'
%th= t '.received'
%th
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
%th
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
%th= t '.price'
- for order_article in order.order_articles.ordered.all(:include => [:article, :article_price])
@ -13,8 +15,8 @@
= order_article.article.name
= "(#{order_article.article.unit} | #{order_article.price.unit_quantity} | #{number_to_currency(order_article.price.gross_price)})"
%tbody
- for goa in order_article.group_order_articles
%tr{:class => cycle('even', 'odd', :name => 'groups')}
- for goa in order_article.group_order_articles.ordered
%tr{:class => [cycle('even', 'odd', :name => 'groups'), if goa.result == 0 then 'unavailable' end]}
%td{:style => "width:70%"}=h goa.group_order.ordergroup.name
%td= "#{goa.quantity} + #{goa.tolerance}"
%td

View file

@ -3,7 +3,9 @@
%tr
%th{:style => "width:40%"}= t '.name'
%th
%acronym{:title => t('.units_desc')}= t '.units'
%acronym{:title => t('shared.articles.ordered_desc')}= t 'shared.articles.ordered'
%th
%acronym{:title => t('shared.articles.received_desc')}= t 'shared.articles.received'
%th
%acronym{:title => t('.fc_price_desc')}= t '.fc_price'
%th
@ -11,10 +13,10 @@
%th= t '.unit'
%th= t '.price'
- for group_order in order.group_orders.all
- for group_order in order.group_orders.ordered
%thead
%tr
%th{:colspan => "6"}
%th{:colspan => "7"}
%h4= group_order.ordergroup.name
%tbody
- total = 0
@ -22,17 +24,19 @@
- fc_price = goa.order_article.price.fc_price
- subTotal = fc_price * goa.result
- total += subTotal
%tr{:class => cycle('even', 'odd', :name => 'articles')}
%tr{:class => [cycle('even', 'odd', :name => 'articles'), if goa.result == 0 then 'unavailable' end]}
%td{:style => "width:40%"}=h goa.order_article.article.name
%td= goa.result
%td= "#{goa.quantity} + #{goa.tolerance}"
%td
%b= goa.result
%td= number_to_currency(fc_price)
%td= goa.order_article.price.unit_quantity
%td= goa.order_article.article.unit
%td= number_to_currency(subTotal)
%tr{:class => cycle('even', 'odd', :name => 'articles')}
%th{:colspan => "5"} Summe
%th{:colspan => "6"} Summe
%th= number_to_currency(total)
%tr
%th(colspan="6")
%th(colspan="7")
- reset_cycle("articles")

View file

@ -0,0 +1,5 @@
.alert.alert-error.alert-block
%a.close{:href => '#', :data => {:dismiss => 'alert'}}
= t('ui.marks.close').html_safe
%ul
= error_messages.html_safe

View file

@ -13,13 +13,6 @@
- members = group.users
= "(#{members.size})"
= members.collect(&:nick).join(", ")
- if group.is_a?(Workgroup)
%dt= t('.weekly_job') + ':'
%dd
- if group.weekly_task
=h "#{group.task_name} am #{weekday(group.weekday)}"
- else
= t '.no_weekly_job'
- else
- unless group.is_a?(Workgroup)
%dt= t '.apple_limit'
%dd= group.ignore_apple_restriction ? t('.deactivated') : t('.activated')

View file

@ -3,17 +3,6 @@
= yield
- if f.object.is_a?(Workgroup)
%h3= t '.title'
= f.input :weekly_task
#weekly_task_fields
= f.input :weekday, as: :select, collection: Workgroup.weekdays
= f.input :task_name
= f.input :task_required_users
= f.input :task_duration, :as => :select, :collection => (1..3)
= f.input :task_description, as: :text, input_html: {rows: 5}
= f.input :next_weekly_tasks_number
= f.input :user_tokens, :as => :string,
:input_html => { 'data-pre' => f.object.users.map { |u| u.token_attributes }.to_json }

View file

@ -5,11 +5,31 @@
= f.input :phone
= f.input :password, :required => f.object.new_record?
= f.input :password_confirmation
.control-group
.controls
- for setting in User::setting_keys.keys
%label.checkbox{:for => "user[setting_attributes][#{setting}]"}
= hidden_field_tag "user[setting_attributes][#{setting}]", '0'
= check_box_tag "user[setting_attributes][#{setting}]", '1',
f.object.settings[setting] == '1' || f.object.settings_default(setting)
= User::setting_keys[setting]
= f.simple_fields_for :settings_attributes do |s|
= s.simple_fields_for :profile, defaults: { inline_label: true } do |profile|
= profile.input 'language', as: :select, collection: available_locales, required: false, selected: f.object.settings.profile['language']
.settings
.settings-group
= s.simple_fields_for :profile, defaults: { inline_label: true } do |profile|
%div{class: 'control-group h_wrapper'}
%h5{class: 'controls'}
= t 'simple_form.labels.settings.settings_group.privacy'
= profile.input 'phone_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['phone_is_public'] }
= profile.input 'email_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['email_is_public'] }
= profile.input 'name_is_public', as: :boolean, label: false, input_html: { checked: f.object.settings.profile['name_is_public'] }
.settings-group
%div{class: 'control-group'}
%h5{class: 'controls'}
= t 'simple_form.labels.settings.settings_group.messages'
= s.simple_fields_for :messages, defaults: { inline_label: true, label: false } do |messages|
= messages.input 'send_as_email', as: :boolean, input_html: { checked: f.object.settings.messages['send_as_email'] }
= s.simple_fields_for :notify, defaults: { inline_label: true, label: false } do |notify|
= notify.input 'order_finished', as: :boolean, input_html: { checked: f.object.settings.notify['order_finished'] }
= notify.input 'negative_balance', as: :boolean, input_html: { checked: f.object.settings.notify['negative_balance'] }
= notify.input 'upcoming_tasks', as: :boolean, input_html: { checked: f.object.settings.notify['upcoming_tasks'] }

View file

@ -4,4 +4,4 @@
= f.input :date
= f.input :note
= f.submit
= link_to t('ui.cancel'), stock_takings_path
= link_to t('ui.or_cancel'), stock_takings_path

View file

@ -14,4 +14,4 @@
= render :partial => 'stock_change', :collection => @stock_taking.stock_changes
.form-actions
= f.submit class: 'btn'
= link_to t('ui.cancel'), stock_takings_path
= link_to t('ui.or_cancel'), stock_takings_path

View file

@ -4,11 +4,5 @@ var successDiv = $('<div class="alert fade in alert-success"><a class="close" da
successDiv.append(document.createTextNode('#{escape_javascript(t('.notice', name: @article.name))}'));
$('div.container-fluid').prepend(successDiv);
-# WARNING: If you try to use the escape j(...) here, an error occurs:
-# Ein Fehler ist aufgetreten: undefined method `gsub' for 50:Fixnum
-# However, it should work without without escaping.
-# Note that article names which are purely numeric, e.g. 12345, are escaped correctly (see above).
$('#stockArticle-#{@article.id}').remove();
-# WARNING: Do not use a simple .fadeOut() above, because it conflicts with the show/hide function of unavailable articles.

View file

@ -0,0 +1,17 @@
- title t('.stock_changes', :article_name => @stock_article.name)
%table.table.table-hover#stock_changes
%thead
%tr
%th= t '.datetime'
%th= t '.reason'
%th= t '.change_quantity'
%th= t '.new_quantity'
%tbody
- reversed_history = @stock_article.quantity_history.reverse
- @stock_changes.each_with_index do |stock_change, index|
%tr
%td= l stock_change.created_at
%td= link_to_stock_change_reason(stock_change)
%td= stock_change.quantity
%td= reversed_history[index]

View file

@ -1,4 +1,4 @@
- title "Lager (#{StockArticle.available.count})"
- title t('.title', article_count: StockArticle.available.count)
- content_for :javascript do
:javascript
$(function() {
@ -56,6 +56,7 @@
%td= article.article_category.name
%td
= link_to t('ui.edit'), edit_stock_article_path(article), class: 'btn btn-mini'
= link_to t('ui.history'), stock_article_history_path(article), class: 'btn btn-mini'
= link_to t('ui.delete'), article, :method => :delete, :confirm => t('.confirm_delete'),
class: 'btn btn-mini btn-danger', :remote => true
%p

View file

@ -17,7 +17,9 @@
%td= shared_supplier.note
%td= shared_supplier.delivery_days
%td
- if shared_supplier.supplier
- if shared_supplier.suppliers.any?
%i.icon-ok
= associated_supplier_names(shared_supplier)
= link_to t('.subscribe_again'), new_supplier_path(:shared_supplier_id => shared_supplier), class: 'btn'
- else
= link_to t('.subscribe'), new_supplier_path(:shared_supplier_id => shared_supplier), class: 'btn'

View file

@ -25,5 +25,7 @@
= f.input :due_date, as: :date_picker
= f.input :done
.form-actions
= f.submit class: 'btn'
= f.submit class: 'btn btn-primary'
- if @task.new_record?
= f.submit t('.submit.periodic'), name: 'periodic', class: 'btn'
= link_to t('ui.or_cancel'), :back

View file

@ -2,6 +2,7 @@
%thead
%tr
%th= t '.due_date'
%th
%th= t '.task'
%th{:colspan => '2'}
= t '.who'
@ -11,6 +12,9 @@
- done = task.done ? " done" : ""
%tr{:class => done }
%td= format_date(task.due_date) unless task.due_date.nil?
%td
- if task.periodic?
%i.icon-repeat{title: t('tasks.repeated')}
%td= link_to t('.task_format', name: task.name, duration: task.duration), task_path(task)
%td
= task_assignments task

View file

@ -4,7 +4,7 @@
- content_for :sidebar do
.well.well-small
%ul.nav.nav-list
%li.nav-header Seiten
%li.nav-header= t '.pages'
%li= link_to t('.my_tasks'), user_tasks_path
%li= link_to t('.all_tasks'), tasks_path
%li= link_to t('.archive'), archive_tasks_path

View file

@ -10,7 +10,10 @@
%dd= simple_format(@task.description)
- if @task.due_date.present?
%dt= t '.due_date'
%dd= format_date(@task.due_date)
%dd
= format_date(@task.due_date)
- if @task.periodic?
%i.icon-repeat{title: t('tasks.repeated')}
%dt= t 'simple_form.labels.task.duration'
%dd= t('.hours', count: @task.duration)
%dt= t 'simple_form.labels.task.user_list'
@ -29,3 +32,6 @@
= link_to t('ui.edit'), edit_task_path(@task), class: 'btn'
= link_to t('ui.delete'), task_path(@task), :method => :delete, :confirm => "Die Aufgabe wirklich löschen?",
class: 'btn btn-danger'
- if @task.periodic?
= link_to t('.delete_group'), task_path(@task, periodic: true), method: :delete,
confirm: t('.confirm_delete_group'), class: 'btn btn-danger'

View file

@ -1,4 +1,4 @@
- title "Meine Aufgaben"
- title t('.title')
= render 'nav'
- unless @unaccepted_tasks.empty?

View file

@ -1,16 +1,6 @@
- title t('.title', workgroup: @group.name)
= render 'nav'
%section.well
%h3= t '.weekly.title'
- if @group.weekly_task
= t('.weekly.desc', weekday: weekday(@group.weekday), task: @group.task_name).html_safe
- else
= t('.weekly.empty').html_safe
- if @current_user.member_of?(@group) or @current_user.role_admin?
= link_to t('.weekly.edit'), edit_foodcoop_workgroup_path(@group), class: 'btn'
%section
%h3= t '.title_all'
= render 'list', tasks: @group.open_tasks

View file

@ -45,26 +45,6 @@
= f.label :role_orders
%br/
= f.check_box :role_orders
%p
= f.label :weekly_task
%br/
= f.check_box :weekly_task
%p
= f.label :weekday
%br/
= f.text_field :weekday
%p
= f.label :task_name
%br/
= f.text_field :task_name
%p
= f.label :task_description
%br/
= f.text_field :task_description
%p
= f.label :task_required_users
%br/
= f.text_field :task_required_users
%p
= f.label :deleted_at
%br/

View file

@ -12,11 +12,6 @@
%th Role Article Meta
%th Role Finance
%th Role Orders
%th Weekly Task
%th Weekday
%th Task Name
%th Task Description
%th Task Required Users
%th Deleted At
%th Contact Person
%th Contact Phone
@ -34,11 +29,6 @@
%td= h workgroup.role_article_meta
%td= h workgroup.role_finance
%td= h workgroup.role_orders
%td= h workgroup.weekly_task
%td= h workgroup.weekday
%td= h workgroup.task_name
%td= h workgroup.task_description
%td= h workgroup.task_required_users
%td= h workgroup.deleted_at
%td= h workgroup.contact_person
%td= h workgroup.contact_phone

View file

@ -18,7 +18,7 @@ class UserNotifier
Order.find(order_id).group_orders.each do |group_order|
group_order.ordergroup.users.each do |user|
begin
Mailer.order_result(user, group_order).deliver if user.settings["notify.orderFinished"] == '1'
Mailer.order_result(user, group_order).deliver if user.settings.notify["order_finished"]
rescue
Rails.logger.warn "Can't deliver mail to #{user.email}"
end
@ -34,7 +34,7 @@ class UserNotifier
Ordergroup.find(ordergroup_id).users.each do |user|
begin
Mailer.negative_balance(user, transaction).deliver if user.settings["notify.negativeBalance"] == '1'
Mailer.negative_balance(user, transaction).deliver if user.settings.notify["negative_balance"]
rescue
Rails.logger.warn "Can't deliver mail to #{user.email}"
end

View file

@ -37,6 +37,9 @@ default: &defaults
# price markup in percent
price_markup: 2.0
# default vat percentage for new articles
tax_default: 7.0
# tolerance order option: If set to false, article tolerance values do not count
# for total article price as long as the order is not finished.
tolerance_is_costly: false
@ -45,6 +48,10 @@ default: &defaults
# Comment out this option to activate this restriction
# stop_ordering_under: 75
# ordergroups can only order when their balance is higher than or equal to this
# not fully enforced right now, since the check is only client-side
# minimum_balance: 0
# email address to be used as sender
email_sender: foodsoft@foodcoop.test

View file

@ -31,7 +31,7 @@ module Foodsoft
# Internationalization.
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.i18n.default_locale = :de
config.i18n.default_locale = :en
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"

View file

@ -10,3 +10,10 @@ class String
end
end
end
class Array
def cumulative_sum
csum = 0
self.map{|val| csum += val}
end
end

View file

@ -37,6 +37,17 @@ SimpleForm.setup do |config|
end
end
# Do not use the label in tables
config.wrappers :intable, :tag => 'div', :class => 'control-group control-group-intable', :error_class => 'error' do |b|
b.use :html5
b.use :placeholder
b.wrapper :tag => 'div', :class => 'controls controls-intable' do |ba|
ba.use :input
ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
end
end
# Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
# Check the Bootstrap docs (http://twitter.github.com/bootstrap)
# to learn about the different styles for forms and inputs,

View file

@ -39,6 +39,8 @@ de:
article_category: Kategorie
availability: Artikel ist verfügbar?
deposit: Pfand
fc_price: Endpreis
fc_share: FC-Aufschlag
gross_price: Bruttopreis
price: Nettopreis
tax: MwSt
@ -81,6 +83,11 @@ de:
too_long: ist zu lang (nicht mehr als %{count} Zeichen)
too_short: ist zu kurz (nicht weniger als %{count} Zeichen)
wrong_length: hat die falsche Länge (muss genau %{count} Zeichen haben)
models:
task:
attributes:
done:
exclusion: erledigte Aufgaben können nicht wöchentlich wiederholt werden
template:
body: ! 'Bitte überprüfen Sie die folgenden Felder:'
header:
@ -416,20 +423,28 @@ de:
second: Sekunden
year: Jahr
deliveries:
add_stock_change:
how_many_units: Wie viele Einheiten (%{unit}) des Artikels »%{name}« liefern?
create:
notice: Lieferung wurde erstellt. Bitte nicht vergessen die Rechnung anzulegen!
create_stock_article:
notice: Neuer Lagerartikel »%{name}« gespeichert.
destroy:
notice: Lieferung wurde gelöscht.
edit:
title: Lieferung bearbeiten
form:
add_article: Lagerartikel der Lieferung hinzufügen
new_article:
search: Suche nach Artikeln aus dem <i>%{supplier}</i> Katalog
title: Neuen Lagerartikel anlegen
note_new_article: Ist ein Artikel noch nicht in der Lagerverwaltung, muss er erst %{new_link} werden.
note_new_article_link: neu angelegt
remove_article: Artikel aus Lieferung entfernen
actions: Optionen
article: Artikel
category: Kategorie
create_from_blank: Ohne Vorlage anlegen
create_stock_article: Lagerartikel anlegen
price: Nettopreis
quantity: Menge
title_fill_quantities: 2. Liefermenge angeben
title_finish_delivery: 3. Lieferung abschließen
title_select_stock_articles: 1. Lagerartikel auswählen
unit: Einheit
index:
confirm_delete: Bist Du sicher?
new_delivery: Neue Lieferung für %{supplier} anlegen
@ -449,24 +464,34 @@ de:
title: Lieferung anzeigen
title_articles: Artikel
unit: Einheit
stock_change:
stock_article_for_adding:
action_add_to_delivery: Liefern
action_edit: Bearbeiten
action_other_price: Kopieren
stock_article_form:
copy_stock_article: Lagerartikel kopieren
stock_change_fields:
remove_article: Artikel aus Lieferung entfernen
suppliers_overview: Lieferantenübersicht
update:
notice: Lieferung wurde aktualisiert.
update_stock_article:
notice: Lagerartikel »%{name}« aktualisiert.
documents:
order_by_articles:
filename: Bestellung %{name}-%{date} - Artikelsortierung
rows:
- Bestellgruppe
- Menge
- Bestellt
- Bekommen
- Preis
title: ! 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}'
order_by_groups:
filename: Bestellung %{name}-%{date} - Gruppensortierung
rows:
- Artikel
- Menge
- Bestellt
- Bekommen
- Preis
- GebGr
- Einheit
@ -482,6 +507,8 @@ de:
- Gebinde
- Einheit
- Preis/Einheit
- Summe
total: Gesamtpreis
order_matrix:
filename: Bestellung %{name}-%{date} - Sortiermatrix
heading: Artikelübersicht
@ -696,6 +723,7 @@ de:
ordergroups:
account_balance: Kontostand
account_statement: Kontoauszug
contact: Kontakt
name: Name
new_transaction: Neue Transaktion
update:
@ -1146,6 +1174,8 @@ de:
subject: ! 'Betreff:'
title: Nachricht anzeigen
model:
delivery:
each_stock_article_must_be_unique: Lieferung darf jeden Lagerartikel höchstens einmal auflisten.
membership:
no_admin_delete: Mitgliedschaft kann nicht beendet werden. Du bist die letzte Administratorin
order_article:
@ -1154,14 +1184,6 @@ de:
redirect: Weiterleiting auf [[%{title}]]...
user:
no_ordergroup: keine Bestellgruppe
notify:
email_is_public: E-Mail ist für Mitglieder sichtbar
name_is_public: Name ist für Mitglieder sichtbar
negative_balance: Informiere mich, falls meine Bestellgruppe ins Minus rutscht.
order_finished: Informier mich über meine Bestellergebnisse (nach Ende der Bestellung).
phone_is_public: Telefon ist für Mitglieder sichtbar
send_as_email: Bekomme Nachrichten als Emails.
upcoming_tasks: Erinnere mich an anstehende Aufgaben.
navigation:
admin:
home: Übersicht
@ -1423,7 +1445,7 @@ de:
sessions:
logged_in: Angemeldet!
logged_out: Abgemeldet!
login_invalid:
login_invalid: Ungültiger Benutzername oder Passwort
new:
forgot_password: Passwort vergessen?
login: Anmelden
@ -1509,6 +1531,10 @@ de:
units_to_order: Anzahl gelieferter Gebinde
update_current_price: Ändert auch den Preis für aktuelle Bestellungen
stock_article:
copy_stock_article:
name: Bitte ändern
edit_stock_article:
price: <ul><li>Preisänderung gesperrt.</li><li>Bei Bedarf %{stock_article_copy_link}.</li></ul>
supplier:
supplier:
min_order_quantity: Die Mindestbestellmenge wird während der Bestellung angezeigt und soll motivieren
@ -1581,9 +1607,25 @@ de:
contact_person: Kontaktperson
contact_phone: Telefon
ignore_apple_restriction: Bestellstop bei zu wenig Äpfeln ignorieren
name: Name
page:
body: Inhalt
parent_id: Oberseite
settings:
messages:
send_as_email: Bekomme Nachrichten als Emails.
notify:
negative_balance: Informiere mich, falls meine Bestellgruppe ins Minus rutscht.
order_finished: Informier mich über meine Bestellergebnisse (nach Ende der Bestellung).
upcoming_tasks: Erinnere mich an anstehende Aufgaben.
profile:
email_is_public: E-Mail ist für Mitglieder sichtbar.
language: Sprache
name_is_public: Name ist für Mitglieder sichtbar.
phone_is_public: Telefon ist für Mitglieder sichtbar.
settings_group:
messages: Nachrichten
privacy: Privatsphäre
stock_article:
supplier: Lieferant
supplier:
@ -1626,13 +1668,15 @@ de:
role_finance: Finanzen
role_orders: Bestellverwaltung
role_suppliers: Lieferanten
task_description: Beschreibung
task_duration: Vor. Dauer in Stunden
task_name: Name für Job
task_required_users: Benötige Verantwortliche
weekday: Wochentag
weekly_task: Monatlichen Job definieren?
'no': Nein
options:
settings:
profile:
language:
de: Deutsch
en: English
fr: Französisch
nl: Niederländisch
required:
mark: ! '*'
text: benötigt
@ -1677,6 +1721,15 @@ de:
title: Lagerartikel bearbeiten
form:
price_hint: Um Chaos zu vermeiden können bis auf weiteres die Preise von angelegten Lagerartikeln nicht mehr verändert werden.
history:
change_quantity: Veränderung
datetime: Zeitpunkt
delivery: Lieferung
new_quantity: Neuer Bestand
order: Bestellung
reason: Ereignis
stock_changes: Verlauf anzeigen für »%{article_name}«
stock_taking: Inventur
index:
article:
article: Artikel
@ -1696,6 +1749,7 @@ de:
show_stock_takings: Inventurübersicht
stock_count: ! 'Artikelanzahl:'
stock_worth: ! 'Aktueller Lagerwert:'
title: Lager (%{article_count})
toggle_unavailable: Nicht verfügbare Artikel zeigen/verstecken
view_options: Ansichtsoptionen
new:
@ -1726,6 +1780,7 @@ de:
shared_suppliers:
body: <p>Hier werden die Lieferantinnen der externen Datenbank angezeigt.</p> <p>Ihr könnt externe Lieferantinnen importieren, indem ihr sie einfach abonniert. (siehe unten)</p> <p>Damit wird eine neue Lieferantin angelegt und mit der externen Datenbank verknüpft.</p>
subscribe: abonnieren
subscribe_again: erneut abonnieren
supplier: Lieferantin
title: Externe Listen
show:
@ -1756,12 +1811,15 @@ de:
notice: Aufgabe wurde gelöscht
edit:
title: Aufgabe bearbeiten
warning_periodic: <strong>Warnung:</strong> Diese Aufgabe ist Teil einer Gruppe von <em>wöchentlichen Aufgaben</em>. Beim Speichern wird sie aus der Gruppe ausgeschlossen und in eine <em>gewöhnliche Aufgabe</em> umgewandelt.
error_not_found: Keine Arbeitsgruppe gefunden
form:
search:
hint: Nach Nutzerin suchen
noresult: Keine Nutzerin gefunden
placeholder: Suche ...
submit:
periodic: Wöchentliche Aufgabe speichern
index:
show_group_tasks: Gruppenaufgaben anzeigen
title: Aufgaben
@ -1783,12 +1841,16 @@ de:
group_tasks: Gruppenaufgaben
my_tasks: Meine Aufgaben
new_task: Neue Aufgabe erstellen
pages: Seiten
new:
title: Neue Aufgabe erstellen
repeated: Aufgabe wird wöchentlich wiederholt
set_done:
notice: Aufgabenstatus wurde aktualisiert
show:
accept_task: Aufgabe übernehmen
confirm_delete_group: Diese und alle folgenden wöchentlichen Aufgaben wirklich löschen?
delete_group: Aufgabe und folgende löschen
due_date: Fälligkeitsdatum
hours: ! '%{count}h'
mark_done: Als erledigt markieren
@ -1796,6 +1858,7 @@ de:
title: Aufgabe anzeigen
update:
notice: Aufgabe wurde aktualisiert
notice_converted: Aufgabe wurde aktualisiert und in eine gewöhnliche Aufgabe umgewandelt
user:
more: Nichts zu tun? %{tasks_link} gibt es bestimmt Arbeit
tasks_link: Hier
@ -1805,11 +1868,6 @@ de:
workgroup:
title: Aufgaben für %{workgroup}
title_all: Alle Aufgaben der Gruppe
weekly:
desc: ! '<p>Jeden <b>%{weekday}</b> hat diese Arbeitsgruppe folgenden Job: <b>%{task}</b></p> <p>Die Wochenaufgaben werden von der Foodsoft automatisch erstellt. Eintragen müsst Ihr Euch aber selber.</p>'
edit: Wöchentliche Aufgaben anpassen
empty: Noch keine Wochenaufgaben angelegt.
title: Wöchentliche Aufgaben
time:
am: vormittags
formats:
@ -1821,9 +1879,12 @@ de:
close: Schließen
delete: Löschen
edit: Bearbeiten
history: Verlauf anzeigen
marks:
close: ! '&times;'
success: <i class="icon icon-ok"></i>
or_cancel: oder abbrechen
please_wait: Bitte warten...
save: Speichern
show: Anzeigen
views:

View file

@ -39,6 +39,8 @@ en:
article_category: article category
availability: Is article available?
deposit: deposit
fc_price: FC price
fc_share: FC share
gross_price: gross price
price: price
tax: VAT
@ -81,6 +83,11 @@ en:
too_long: is too long (no more than %{count} characters)
too_short: is too short (use more than %{count} characters)
wrong_length: is the wrong length (has to have exactly %{count} characters)
models:
task:
attributes:
done:
exclusion: finished tasks may not be repeated weekly
template:
body: ! 'Please check the following fields:'
header:
@ -229,7 +236,7 @@ en:
option_available: Make articles available
option_delete: Delete article
option_not_available: Make articles unavailable
option_select: Choose special offer ...
option_select: Select action ...
price_netto: Price
unit_quantity_desc: Unit quantity
unit_quantity_short: Quantity
@ -307,7 +314,7 @@ en:
title: Synchronize articles with external database
unit_quantity_short: unit quantity
update:
body: ! '<p><i>Every article is shown twice. The old values are gray and contain the current values.</i></p>
body: ! '<p><i>Every article is shown twice. The old values are gray and the text fields contain the current values.</i></p>
<p><i>Differences with the old articles are marked yellow.</i></p>'
title: Update ...
@ -418,20 +425,28 @@ en:
second: seconds
year: years
deliveries:
add_stock_change:
how_many_units: ! 'How many units (%{unit}) to deliver? Stock article name: %{name}.'
create:
notice: Delivery was created. Please dont forget to create invoice!
create_stock_article:
notice: The new stock article »%{name}« was saved.
destroy:
notice: Delivery was deleted.
edit:
title: Edit suppliers
title: Edit delivery
form:
add_article: Add stock article to delivery
new_article:
search: Search for articles in the <i>%{supplier}</i> catalogue
title: Create new stock article
note_new_article: When an article is not yet in the inventory, you have to %{new_link} it first.
note_new_article_link: create
remove_article: Remove article from delivery
actions: Tasks
article: Article
category: Category
create_from_blank: Create new article
create_stock_article: Create stock article
price: Netprice
quantity: Quantity
title_fill_quantities: 2. Set delivery quantities
title_finish_delivery: 3. Finish delivery
title_select_stock_articles: 1. Select stock articles
unit: Unit
index:
confirm_delete: Are you sure?
new_delivery: ! 'Create new delivery for %{supplier} '
@ -451,26 +466,36 @@ en:
title: Show delivery
title_articles: Article
unit: Unit
stock_change:
remove_article: Remove articles from delivery
stock_article_for_adding:
action_add_to_delivery: Add to delivery
action_edit: Edit
action_other_price: Copy
stock_article_form:
copy_stock_article: Copy stock article
stock_change_fields:
remove_article: Remove article from delivery
suppliers_overview: Supplier overview
update:
notice: Delivery was updated.
update_stock_article:
notice: The stock article »%{name}« was updated.
documents:
order_by_articles:
filename: Order %{name}-%{date} - by articles
rows:
- Order group
- Amount
- Ordered
- Received
- Price
title: ! 'Order sorted by articles: %{name}, closed at %{date}'
order_by_groups:
filename: Order %{name}-%{date} - by group
rows:
- Article
- Amount
- Ordered
- Received
- Price
- Unit Quantity
- Unit quantity
- Unit
- Sum
sum: Sum
@ -481,20 +506,24 @@ en:
- Order Number
- Amount
- Name
- Barrel
- Unit quantity
- Unit
- Price/Unit
- Subtotal
total: Total
order_matrix:
filename: Order %{name}-%{date} - sorting matrix
heading: Article overview
rows:
- Article
- Unit
- Barrel
- Unit quantity
- FC-Price
- Amount
title: ! 'Order sorting matrix: %{name}, closed at %{date}'
total: ! '%{count} articles in total'
total:
one: One article in total
other: ! '%{count} articles in total'
errors:
format: ! '%{attribute} %{message}'
general: A problem has occured.
@ -698,6 +727,7 @@ en:
ordergroups:
account_balance: Account balance
account_statement: Account statement
contact: Contact
name: Name
new_transaction: New transaction
update:
@ -1148,6 +1178,8 @@ en:
subject: ! 'Subject:'
title: Show message
model:
delivery:
each_stock_article_must_be_unique: Each stock article must not be listed more than once.
membership:
no_admin_delete: Membership can not be withdrawn as you are the last administrator.
order_article:
@ -1156,14 +1188,6 @@ en:
redirect: Redirect to [[%{title}]]...
user:
no_ordergroup: no order group
notify:
email_is_public: Email is visible for other members.
name_is_public: Name is visible for other members.
negative_balance: inform me when by order group has a negative balance.
order_finished: Inform me about my order result (when the order is closed).
phone_is_public: Phone number is visible for other members.
send_as_email: Receive messages as emails.
upcoming_tasks: Remind me of upcoming tasks.
navigation:
admin:
home: Overview
@ -1435,10 +1459,15 @@ en:
title: Foodsoft login
user: User
shared:
articles:
ordered: Ordered
ordered_desc: Number of articles as ordered by member (amount + tolerance)
received: Received
received_desc: Number of articles that (will be) received by member
articles_by_articles:
ordered: Ordered (Amount + Tolerance)
ordergroup: Ordergroup
price: Total price
ordered: Ordered (Amount + Tolerance)
received: Received
articles_by_groups:
fc_price: FC-Price
@ -1511,6 +1540,10 @@ en:
units_to_order: Amount of delivered units
update_current_price: Also update the price of the current order
stock_article:
copy_stock_article:
name: Please modify
edit_stock_article:
price: <ul><li>Price changes are forbidden.</li><li>If necessary, %{stock_article_copy_link}.</li></ul>
supplier:
supplier:
min_order_quantity: The minimum amount which has to be orderd will be shown during the order process and should motivate ordering
@ -1583,9 +1616,25 @@ en:
contact_person: Contact person
contact_phone: Phone
ignore_apple_restriction: Ignore order stop by apple points restriction
name: Name
page:
body: Body
parent_id: Parent page
settings:
messages:
send_as_email: Receive messages as emails.
notify:
negative_balance: Inform me when my order group has a negative balance.
order_finished: Inform me about my order result (when the order is closed).
upcoming_tasks: Remind me of upcoming tasks.
profile:
email_is_public: Email is visible for other members.
language: Language
name_is_public: Name is visible for other members.
phone_is_public: Phone number is visible for other members.
settings_group:
messages: Messages
privacy: Privacy
stock_article:
supplier: Supplier
supplier:
@ -1628,13 +1677,15 @@ en:
role_finance: Finances
role_orders: Order management
role_suppliers: Suppliers
task_description: Description
task_duration: Duration in hours
task_name: Task name
task_required_users: People required
weekday: Weekday
weekly_task: Define monthly task?
'no': 'No'
options:
settings:
profile:
language:
de: German
en: English
fr: French
nl: Dutch
required:
mark: ! '*'
text: required
@ -1679,6 +1730,15 @@ en:
title: Edit stock articles
form:
price_hint: To avoid choas, it is not possible to edit the prices of already added stock articles until further notice.
history:
change_quantity: Change
datetime: Time
delivery: Delivery
new_quantity: New quantity
order: Order
reason: Reason
stock_changes: Stock quantity changes of %{article_name}
stock_taking: Inventory
index:
article:
article: Article
@ -1698,6 +1758,7 @@ en:
show_stock_takings: Inventory overview
stock_count: ! 'Number of articles:'
stock_worth: ! 'Current stock value:'
title: Stock (%{article_count})
toggle_unavailable: Show/hide unavailable articles
view_options: View options
new:
@ -1728,6 +1789,7 @@ en:
shared_suppliers:
body: <p>Suppliers of the external database are displayed here.</p> <p>You can import external suppliers by subscribing (see below).</p> <p>A new supplier will be created and connected to the external database.</p>
subscribe: Subscribe
subscribe_again: Subscribe again
supplier: Supplier
title: External lists
show:
@ -1758,12 +1820,15 @@ en:
notice: Task has been deleted
edit:
title: Edit task
warning_periodic: <strong>Warning:</strong> This task is part of a group of <em>weekly tasks</em>. When saving it will be excluded from the group and it will be converted to a <em>regular task</em>.
error_not_found: No workgroup found
form:
search:
hint: Search for user
noresult: No user found
placeholder: Search ...
submit:
periodic: Save weekly task
index:
show_group_tasks: Show group tasks
title: Tasks
@ -1785,12 +1850,16 @@ en:
group_tasks: Group tasks
my_tasks: My tasks
new_task: Create new task
pages: Pages
new:
title: Create new tasks
repeated: Task is repeated weekly
set_done:
notice: The state of the task has been updated
show:
accept_task: Accept task
confirm_delete_group: Really delete this and all subsequent tasks?
delete_group: Delete task and subsequent
due_date: Due date
hours: ! '%{count}h'
mark_done: Mark task as done
@ -1798,6 +1867,7 @@ en:
title: Show task
update:
notice: Task has been updated
notice_converted: Task has been updated and was converted to a regular task
user:
more: Nothing to do? %{tasks_link} are tasks for sure.
tasks_link: Here
@ -1807,11 +1877,6 @@ en:
workgroup:
title: Tasks for %{workgroup}
title_all: All group tasks
weekly:
desc: ! '<p>Every <b>%{weekday}</b> this workgroup has the following job: <b>%{task}</b></p> <p>The weektask has been created by Foodsoft automatically. You still have to sign up for it yourself.</p>'
edit: Edit weekly tasks
empty: No weekly tasks created yet.
title: Weekly tasks
time:
am: morning
formats:
@ -1823,9 +1888,12 @@ en:
close: Close
delete: Delete
edit: Edit
history: Show history
marks:
close: ! '&times;'
success: <i class="icon icon-ok"></i>
or_cancel: or cancel
please_wait: Please wait...
save: Save
show: Show
views:

1919
config/locales/fr.yml Normal file

File diff suppressed because it is too large Load diff

View file

@ -39,6 +39,8 @@ nl:
article_category: categorie
availability: Artikel leverbaar?
deposit: statiegeld
fc_price: prijs foodcoop
fc_share: marge foodcoop
gross_price: bruto prijs
price: netto prijs
tax: BTW
@ -81,6 +83,11 @@ nl:
too_long: is te lang (niet meer dan %{count} tekens)
too_short: is te kort (niet minder dan %{count} tekens)
wrong_length: heeft de verkeerde lengte (moet precies %{count} tekens zijn)
models:
task:
attributes:
done:
exclusion: gedane taken kunnen niet herhaald worden
template:
body: ! 'Controleer de volgende velden:'
header:
@ -138,7 +145,7 @@ nl:
first_paragraph: Hier kun je %{url} toevoegen, bewerken en verwijderen.
new_ordergroup: Nieuw huishouden toevoegen
new_ordergroups: nieuwe huishoudens
second_paragraph:
second_paragraph: ! 'Bedenk het <em>onderscheid tussen werkgroep en huishouden</em>: een huishouden heeft een rekening en kan bestellen. in een <em>%{url}</em> (bijv. sorteergroep) werken leden samen om taken te vervullen. Leden kunnen slechts lid zijn van éen huishouden, maar van meerdere werkgroepen.'
title: Huishoudens
workgroup: werkgroep
new:
@ -256,7 +263,7 @@ nl:
notice: Alle artikelen en prijzen zijn bijgewerkt.
destroy_active_article:
drop: verwijderen
note:
note: ! '%{article} is deel van een lopende bestelling en kan niet verwijderd worden. Het artikel graag eerst uit de bestelling(en) %{drop_link}.'
edit_all:
note: ! 'Verplichte velden zijn: Naam, eenheid, (netto) prijs en bestellingsnummer.'
submit: Alle artikelen bijwerken
@ -265,10 +272,10 @@ nl:
edit_all_table:
available_desc: beschikbaar
available_short: besch.
order_number_desc: bestelnummer
order_number_short: best.nr.
price_desc: netto prijs
price_short: prijs
order_number_desc: Bestelnummer
order_number_short: Best.nr.
price_desc: Netto prijs
price_short: Prijs
unit_quantity_desc: Groothandelsverpakkingsgrootte
unit_quantity_short: Gr.Eenh.
form:
@ -305,11 +312,13 @@ nl:
price_short: prijs
submit: Alle wissen/bijwerken
title: Artikelen met externe database synchroniseren
unit_quantity_short:
unit_quantity_short: Gr.Eenh.
update:
body:
title:
update_msg:
body: ! '<p><i>Ieder artikel wordt tweemaal getoond. De oude waarden zijn grijs, en de tekstvelden bevatten de huidige waarden.</i></p>
<p><i>Verschillen met de oude artikelen zijn geel gemarkeerd.</i></p>'
title: Bijwerken ...
update_msg: ! 'De volgende artikelen moeten bijgewerkt worden: '
upload:
body:
fields:
@ -416,20 +425,28 @@ nl:
second: seconden
year: jaren
deliveries:
add_stock_change:
how_many_units:
create:
notice:
create_stock_article:
notice:
destroy:
notice:
edit:
title:
form:
add_article:
new_article:
search:
title:
note_new_article:
note_new_article_link:
remove_article:
actions:
article:
category:
create_from_blank:
create_stock_article:
price:
quantity:
title_fill_quantities:
title_finish_delivery:
title_select_stock_articles:
unit:
index:
confirm_delete:
new_delivery:
@ -449,66 +466,99 @@ nl:
title:
title_articles:
unit:
stock_change:
stock_article_for_adding:
action_add_to_delivery:
action_edit:
action_other_price:
stock_article_form:
copy_stock_article:
stock_change_fields:
remove_article:
suppliers_overview:
update:
notice:
update_stock_article:
notice:
documents:
order_by_articles:
filename:
filename: Bestelling %{name}-%{date} - Artikellijst
rows:
title:
- Huishouden
- Hoeveelheid
- Prijs
title: ! 'Artikellijst van bestelling: %{name}, gesloten op %{date}'
order_by_groups:
filename:
filename: Bestelling %{name}-%{date} - Huishoudenslijst
rows:
sum:
title:
- Artikel
- Hoeveelheid
- Prijs
- Gr.Eenh.
- Eenheid
- Som
sum: Som
title: ! 'Huishoudenslijst van bestelling: %{name}, gesloten op %{date}'
order_fax:
filename:
filename: Bestelling %{name}-%{date} - Fax
rows:
total: Totaal
order_matrix:
filename:
heading:
filename: Bestelling %{name}-%{date} - Sorteermatrix
heading: Artikeloverzicht
rows:
title:
- Artikel
- Eenheid
- Gr.Eenh.
- Foodcoop-prijs
- Aantal
title: ! 'Sorteermatrix van bestelling: %{name}, gesloten op %{date}'
total:
one: In totaal éen artikel
other: In totaal %{count} artikelen
errors:
format:
general:
general_again:
general_msg:
format: ! '%{attribute} %{message}'
general: Er is een probleem opgetreden.
general_again: Er is een fout opgetreden. Probeer het opnieuw.
general_msg: ! 'Er is een probleem opgetreden: %{msg}'
messages:
accepted:
blank:
confirmation:
empty:
equal_to:
even:
exclusion:
greater_than:
greater_than_or_equal_to:
inclusion:
invalid:
less_than:
less_than_or_equal_to:
not_a_number:
not_an_integer:
odd:
accepted: moet geaccepteerd worden
blank: moet ingevuld worden
confirmation: komt niet overeen met de bevestiging
empty: moet ingevuld worden
equal_to: moet precies %{count} zijn
even: moet even zijn
exclusion: moet even zijn
greater_than: moet groter dan %{count} zijn
greater_than_or_equal_to: moet groter dan of gelijk zijn aan %{count}
inclusion: geen geldige waarde
invalid: is ongeldig
less_than: moet kleiner dan %{count} zijn
less_than_or_equal_to: moet groter of gelijk aan %{count} zijn
not_a_number: is geen getal
not_an_integer: moet een geheel getal zijn
odd: moet oneven zijn
record_invalid:
taken:
taken_with_deleted:
taken: is al in gebruik
taken_with_deleted: is al in gebruik (verwijderde groep)
too_long:
one: is te lang (niet meer dan éen teken)
other: is te lang (niet meer dan %{count} tekens)
too_short:
one: is te kort (niet minder dan éen teken)
other: is te kort (niet minder dan %{count} tekens)
wrong_length:
one: heeft de verkeerde lengte (moet precies éen zijn)
other: heeft de verkeerde lengte (moet precies %{count} tekens hebben)
template:
body:
body: ! 'Controleer alsjeblieft de volgende velden:'
header:
one: ! 'Kon %{model} niet opslaan: éen fout gevonden.'
other: ! 'Kon %{model} niet opslaan: %{count} fouten gevonden.'
feedback:
create:
notice:
notice: Bericht verstuurd. Vriendelijk bedankt!
new:
first_paragraph:
first_paragraph: Probleem gevonden? Voorstel? Idee? Verbeterpunt? We horen graag je feedback.
second_paragraph:
send:
title:
@ -542,7 +592,7 @@ nl:
total_fc: Som (FC-prijs)
units: Eenheden
index:
title: Gesloten orders
title: Gesloten bestellingen
invoice:
edit: Factuur bewerken
invoice_amount: ! 'Factuurbedrag:'
@ -573,8 +623,8 @@ nl:
orders:
clear: afrekenen
cleared: afgerekend (%{amount})
close: direct afsluiten
confirm: Weet je zeker dat de je bestelling wilt afsluiten?
close: direct afrekenen
confirm: Weet je zeker dat de je bestelling wilt afrekenen?
end: Einde
ended: gesloten
last_edited_by: Laatst aangepast door
@ -673,7 +723,8 @@ nl:
title: Tegoeden beheren
ordergroups:
account_balance: Tegoed
account_statement:
account_statement: Rekeningafschrift
contact:
name: Naam
new_transaction: Nieuwe transactie
update:
@ -681,31 +732,31 @@ nl:
foodcoop:
ordergroups:
index:
name:
only_active:
only_active_desc:
title:
name: Naam ...
only_active: Alleen actieve
only_active_desc: (minstens eenmaal in de laatste 3 maanden besteld)
title: Huishoudens
ordergroups:
last_ordered:
name:
user:
last_ordered: laatste besteld
name: Naam
user: Leden
users:
index:
body:
ph_name:
ph_ordergroup:
profile_link:
title:
body: <p>Hier kun je leden van deze foodcoop een bericht sturen.</p> <p>Om je eigen contactgegevens te laten zien, moet je die vrijgeven op %{profile_link}.</p>
ph_name: Naam ...
ph_ordergroup: Huishouden ...
profile_link: Instellingen
title: Leden
workgroups:
edit:
invite_link:
invite_new:
title:
invite_link: hier
invite_new: Nieuwe leden kun je %{invite_link} uitnodigen.
title: Groep bewerken
index:
body:
title:
body: <p>De groep kan alleen aangepast worden door leden ervan.<br/> Als je lid wilt worden van een groep, stuur dan een bericht aan een van de leden.</p>
title: Werkgroepen
workgroup:
edit:
edit: Groep bewerken
show_tasks:
group_orders:
archive:
@ -755,9 +806,9 @@ nl:
title: Afgerekende bestellingen
finished_orders:
title: Niet afgerekende bestellingen
total_sum:
total_sum: Totaal
funds:
account_balance:
account_balance: Tegoed
available_funds:
finished_orders: niet afgerekende bestellingen
open_orders: Lopende bestellingen
@ -853,12 +904,12 @@ nl:
last_update:
title:
transactions:
amount:
note:
title:
view:
when:
where:
amount: Bedrag
note: Notitie
title: Laatste transacties
view: Rekeningafschrift tonen
when: Wanneer
where: Wie
ordergroup:
title:
tasks_move:
@ -894,7 +945,7 @@ nl:
start_nav:
admin:
finances:
accounts:
accounts: Tegoeden bijwerken
settle:
title: Financiën
foodcoop: Foodcoop
@ -1039,6 +1090,8 @@ nl:
subject:
title:
model:
delivery:
each_stock_article_must_be_unique:
membership:
no_admin_delete:
order_article:
@ -1047,14 +1100,6 @@ nl:
redirect:
user:
no_ordergroup:
notify:
email_is_public:
name_is_public:
negative_balance:
order_finished: Informeer me over mijn bestelresultaat (wanneer de bestelling gesloten wordt).
phone_is_public:
send_as_email:
upcoming_tasks:
navigation:
admin:
home: Overzicht
@ -1092,7 +1137,7 @@ nl:
number:
currency:
format:
delimiter: ! ','
delimiter: ! ' '
format: ! '%n %u'
precision: 2
separator: ! ','
@ -1400,6 +1445,10 @@ nl:
units_to_order:
update_current_price:
stock_article:
copy_stock_article:
name:
edit_stock_article:
price:
supplier:
supplier:
min_order_quantity:
@ -1425,7 +1474,7 @@ nl:
description: Omschrijving
email: Email
note: Notitie
order_number: Order nummer
order_number: Bestelnummer
ordergroup: Huishouden
password: Wachtwoord
password_confirmation: Wachtwoord herhalen
@ -1450,7 +1499,7 @@ nl:
note: Notitie
number: Nummer
order: Bestelling
paid_on: Betaalt op
paid_on: Betaald op
supplier: Leverancier
message:
body:
@ -1472,9 +1521,25 @@ nl:
contact_person: Contactpersoon
contact_phone: Telefoon
ignore_apple_restriction:
name:
page:
body:
parent_id:
settings:
messages:
send_as_email: Berichten als emails ontvangen.
notify:
negative_balance: Informeer me wanneer mijn huishouden een negatief saldo krijgt.
order_finished: Informeer me over mijn bestelresultaat (wanneer de bestelling gesloten wordt).
upcoming_tasks: Herinner me aan aankomende taken.
profile:
email_is_public: E-mail is zichtbaar voor andere leden.
language: Taal
name_is_public: Naam is zichtbaar voor andere leden.
phone_is_public: Telefoonnummer is zichtbaar voor andere leden.
settings_group:
messages: Berichten
privacy: Privacy
stock_article:
supplier:
supplier:
@ -1513,17 +1578,19 @@ nl:
workgroup:
next_weekly_tasks_number:
role_admin:
role_article_meta:
role_finance:
role_article_meta: Artikelbestand
role_finance: Financiën
role_orders:
role_suppliers:
task_description:
task_duration:
task_name:
task_required_users:
weekday:
weekly_task:
role_suppliers: Leveranciers
'no': Nee
options:
settings:
profile:
language:
de: Duits
en: Engels
fr: Frans
nl: Nederlands
required:
mark: ! '*'
text: verplicht
@ -1568,6 +1635,15 @@ nl:
title:
form:
price_hint:
history:
change_quantity:
datetime:
delivery:
new_quantity:
order:
reason:
stock_changes:
stock_taking:
index:
article:
article:
@ -1587,6 +1663,7 @@ nl:
show_stock_takings:
stock_count:
stock_worth:
title:
toggle_unavailable:
view_options:
new:
@ -1617,6 +1694,7 @@ nl:
shared_suppliers:
body:
subscribe:
subscribe_again:
supplier:
title:
show:
@ -1647,12 +1725,15 @@ nl:
notice:
edit:
title:
warning_periodic:
error_not_found:
form:
search:
hint:
noresult:
placeholder:
submit:
periodic:
index:
show_group_tasks:
title:
@ -1674,12 +1755,16 @@ nl:
group_tasks:
my_tasks:
new_task:
pages:
new:
title:
repeated:
set_done:
notice:
show:
accept_task:
confirm_delete_group:
delete_group:
due_date:
hours:
mark_done:
@ -1687,6 +1772,7 @@ nl:
title:
update:
notice:
notice_converted:
user:
more:
tasks_link:
@ -1696,11 +1782,6 @@ nl:
workgroup:
title:
title_all:
weekly:
desc:
edit:
empty:
title:
time:
am: morgen
formats:
@ -1712,9 +1793,12 @@ nl:
close: Sluiten
delete: Verwijder
edit: Bewerk
history:
marks:
close: ! '&times;'
success: <i class="icon icon-ok"></i>
or_cancel: of annuleren
please_wait: Een moment alstublieft...
save: Opslaan
show: Tonen
views:

View file

@ -8,6 +8,7 @@ Foodsoft::Application.routes.draw do
root :to => redirect("/#{FoodsoftConfig.scope}")
scope '/:foodcoop' do
# Root path
@ -96,14 +97,23 @@ Foodsoft::Application.routes.draw do
get :articles_search
get :fill_new_stock_article_form
end
get :history
end
resources :suppliers do
get :shared_suppliers, :on => :collection
resources :deliveries do
post :drop_stock_change, :on => :member
post :add_stock_article, :on => :collection
post :add_stock_change, :on => :collection
get :new_stock_article, :on => :collection
get :copy_stock_article, :on => :collection
get :derive_stock_article, :on => :collection
post :create_stock_article, :on => :collection
get :edit_stock_article, :on => :collection
put :update_stock_article, :on => :collection
end
resources :articles do

View file

@ -3,13 +3,11 @@
# Upcoming tasks notifier
every :day, :at => '7:20 am' do
rake "multicoops:run foodsoft:notify_upcoming_tasks"
rake "multicoops:run TASK=foodsoft:notify_upcoming_tasks"
end
# Weekly taks
every :sunday, :at => '7:14 am' do
rake "multicoops:run foodsoft:create_upcoming_weekly_tasks"
rake "multicoops:run foodsoft:notify_users_of_weekly_task"
rake "multicoops:run TASK=foodsoft:create_upcoming_periodic_tasks"
rake "multicoops:run TASK=foodsoft:notify_users_of_weekly_task"
end

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