Merge branch 'master' into allow-no-nickname
Conflicts: app/views/home/ordergroup.html.haml app/views/login/new_password.html.haml app/views/shared/_auto_complete_users.rhtml app/views/shared/memberships/_current_members.rhtml app/views/shared/memberships/_non_members.rhtml
This commit is contained in:
commit
66ac3be81f
63 changed files with 428 additions and 438 deletions
11
Gemfile
11
Gemfile
|
@ -31,18 +31,20 @@ gem 'client_side_validations'
|
|||
gem 'client_side_validations-simple_form'
|
||||
gem 'inherited_resources'
|
||||
gem 'localize_input', git: "git://github.com/bennibu/localize_input.git"
|
||||
gem 'wikicloth'
|
||||
gem 'daemons'
|
||||
gem 'twitter-bootstrap-rails'
|
||||
gem 'simple-navigation'
|
||||
gem 'simple-navigation-bootstrap'
|
||||
gem 'meta_search'
|
||||
gem 'acts_as_versioned', git: 'git://github.com/technoweenie/acts_as_versioned.git' # Use this instead of rubygem
|
||||
gem 'acts_as_tree'
|
||||
gem "rails-settings-cached", "0.2.4"
|
||||
gem 'resque'
|
||||
gem 'whenever', require: false # For defining cronjobs, see config/schedule.rb
|
||||
|
||||
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
||||
gem 'acts_as_versioned', git: 'git://github.com/technoweenie/acts_as_versioned.git'
|
||||
gem 'foodsoft_wiki', path: 'lib/foodsoft_wiki'
|
||||
|
||||
group :production do
|
||||
gem 'exception_notification'
|
||||
end
|
||||
|
@ -78,10 +80,9 @@ 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'
|
||||
gem 'capybara'
|
||||
# webkit and poltergeist don't seem to work yet
|
||||
gem 'selenium-webdriver', '~> 2.35.1'
|
||||
gem 'selenium-webdriver'
|
||||
gem 'database_cleaner'
|
||||
gem 'simplecov', require: false
|
||||
# need to include rspec components before i18n-spec or rake fails in test environment
|
||||
|
|
24
Gemfile.lock
24
Gemfile.lock
|
@ -18,6 +18,14 @@ GIT
|
|||
acts_as_versioned (0.6.0)
|
||||
activerecord (>= 3.0.9)
|
||||
|
||||
PATH
|
||||
remote: lib/foodsoft_wiki
|
||||
specs:
|
||||
foodsoft_wiki (0.0.1)
|
||||
acts_as_versioned
|
||||
rails (~> 3.2.15)
|
||||
wikicloth
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
|
@ -251,7 +259,7 @@ GEM
|
|||
rspec-mocks (~> 2.14.0)
|
||||
ruby-prof (0.13.0)
|
||||
ruby-rc4 (0.1.5)
|
||||
rubyzip (0.9.9)
|
||||
rubyzip (1.0.0)
|
||||
sass (3.2.12)
|
||||
sass-rails (3.2.6)
|
||||
railties (~> 3.2.0)
|
||||
|
@ -259,10 +267,10 @@ GEM
|
|||
tilt (~> 1.3)
|
||||
select2-rails (3.5.0)
|
||||
thor (~> 0.14)
|
||||
selenium-webdriver (2.35.1)
|
||||
selenium-webdriver (2.37.0)
|
||||
childprocess (>= 0.2.5)
|
||||
multi_json (~> 1.0)
|
||||
rubyzip (< 1.0.0)
|
||||
rubyzip (~> 1.0.0)
|
||||
websocket (~> 1.0.4)
|
||||
simple-navigation (3.11.0)
|
||||
activesupport (>= 2.3.2)
|
||||
|
@ -308,9 +316,9 @@ GEM
|
|||
rails (>= 3.1)
|
||||
railties (>= 3.1)
|
||||
tzinfo (0.3.38)
|
||||
uglifier (2.2.1)
|
||||
uglifier (2.3.0)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (~> 1.0, >= 1.0.2)
|
||||
json (>= 1.8.0)
|
||||
uniform_notifier (1.3.0)
|
||||
vegas (0.1.11)
|
||||
rack (>= 1.0.0)
|
||||
|
@ -336,7 +344,7 @@ DEPENDENCIES
|
|||
bullet
|
||||
capistrano (= 2.13.5)
|
||||
capistrano-ext
|
||||
capybara (~> 2.1.0)
|
||||
capybara
|
||||
client_side_validations
|
||||
client_side_validations-simple_form
|
||||
coffee-rails (~> 3.2.1)
|
||||
|
@ -345,6 +353,7 @@ DEPENDENCIES
|
|||
exception_notification
|
||||
factory_girl_rails (~> 4.0)
|
||||
faker
|
||||
foodsoft_wiki!
|
||||
haml-rails
|
||||
i18n-js!
|
||||
i18n-spec
|
||||
|
@ -369,7 +378,7 @@ DEPENDENCIES
|
|||
ruby-prof
|
||||
sass-rails (~> 3.2.3)
|
||||
select2-rails
|
||||
selenium-webdriver (~> 2.35.1)
|
||||
selenium-webdriver
|
||||
simple-navigation
|
||||
simple-navigation-bootstrap
|
||||
simple_form
|
||||
|
@ -380,4 +389,3 @@ DEPENDENCIES
|
|||
twitter-bootstrap-rails
|
||||
uglifier (>= 1.0.3)
|
||||
whenever
|
||||
wikicloth
|
||||
|
|
|
@ -7,6 +7,8 @@ FoodSoft
|
|||
|
||||
Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling).
|
||||
|
||||
A food cooperative is a group of people that buy food from suppliers of their own choosing. A collective do-it-yourself supermarket. Members order their products online and collect them on a specified day. And all put in a bit of work to make that possible. Foodsoft facilitates the process.
|
||||
|
||||
If you're a food coop considering to use foodsoft, please have a look at the [wiki page for foodcoops](https://github.com/foodcoops/foodsoft/wiki/For-foodcoops). When you'd like to experiment with or develop foodsoft, you can read [how to set it up](https://github.com/foodcoops/foodsoft/blob/master/doc/SETUP_DEVELOPMENT.md) on your own computer.
|
||||
|
||||
More information about using this software and contributing can be found on the [wiki](https://github.com/foodcoops/foodsoft/wiki).
|
||||
|
|
|
@ -65,7 +65,7 @@ class GroupOrdersController < ApplicationController
|
|||
private
|
||||
|
||||
# Returns true if @current_user is member of an Ordergroup.
|
||||
# Used as a :before_filter by OrderingController.
|
||||
# Used as a :before_filter by OrdersController.
|
||||
def ensure_ordergroup_member
|
||||
@ordergroup = @current_user.ordergroup
|
||||
if @ordergroup.nil?
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
class MembershipsController < ApplicationController
|
||||
before_filter :authenticate_membership_or_admin
|
||||
|
||||
def add_member
|
||||
user = User.find(params[:user_id])
|
||||
Membership.create(:group => @group, :user => user)
|
||||
redirect_to :action => 'reload', :id => @group
|
||||
end
|
||||
|
||||
def drop_member
|
||||
begin
|
||||
Membership.find(params[:membership_id]).destroy
|
||||
if User.find(@current_user.id).role_admin?
|
||||
redirect_to :action => 'reload', :id => @group
|
||||
else
|
||||
# If the user drops himself from admin group
|
||||
flash[:notice] = MESG_NO_ADMIN_ANYMORE
|
||||
render(:update) {|page| page.redirect_to :controller => "index"}
|
||||
end
|
||||
rescue => error
|
||||
flash[:error] = error.to_s
|
||||
redirect_to :action => 'reload', :id => @group
|
||||
end
|
||||
end
|
||||
|
||||
def reload
|
||||
render :update do |page|
|
||||
page.replace_html 'members', :partial => 'shared/memberships/current_members', :object => @group
|
||||
page.replace_html 'non_members', :partial => 'shared/memberships/non_members', :object => @group
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -48,6 +48,9 @@ class OrdersController < ApplicationController
|
|||
end
|
||||
send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf'
|
||||
end
|
||||
format.text do
|
||||
send_data text_fax_template, filename: @order.name+'.txt', type: 'text/plain'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -102,11 +105,13 @@ class OrdersController < ApplicationController
|
|||
redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Renders the fax-text-file
|
||||
# e.g. for easier use with online-fax-software, which don't accept pdf-files
|
||||
# TODO move to text template
|
||||
def text_fax_template
|
||||
order = Order.find(params[:id])
|
||||
supplier = order.supplier
|
||||
supplier = @order.supplier
|
||||
contact = FoodsoftConfig[:contact].symbolize_keys
|
||||
text = I18n.t('orders.fax.heading', :name => FoodsoftConfig[:name])
|
||||
text += "\n#{Supplier.human_attribute_name(:customer_number)}: #{supplier.customer_number}" unless supplier.customer_number.blank?
|
||||
|
@ -117,15 +122,13 @@ class OrdersController < ApplicationController
|
|||
text += "****** " + I18n.t('orders.fax.articles') + "\n\n"
|
||||
text += I18n.t('orders.fax.number') + " " + I18n.t('orders.fax.amount') + " " + I18n.t('orders.fax.name') + "\n"
|
||||
# now display all ordered articles
|
||||
order.order_articles.ordered.all(:include => [:article, :article_price]).each do |oa|
|
||||
@order.order_articles.ordered.all(:include => [:article, :article_price]).each do |oa|
|
||||
number = oa.article.order_number
|
||||
(8 - number.size).times { number += " " }
|
||||
quantity = oa.units_to_order.to_i.to_s
|
||||
quantity = " " + quantity if quantity.size < 2
|
||||
text += "#{number} #{quantity} #{oa.article.name}\n"
|
||||
end
|
||||
send_data text,
|
||||
:type => 'text/plain; charset=utf-8; header=present',
|
||||
:disposition => "attachment; filename=#{order.name}"
|
||||
text
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,14 +78,14 @@ module ApplicationHelper
|
|||
# When the 'short' option is true, abbreviations will be used:
|
||||
# When there is a non-empty model attribute 'foo', it looks for
|
||||
# the model attribute translation 'foo_short' and use that as
|
||||
# heading, with an acronym title of 'foo'.
|
||||
# heading, with an abbreviation title of 'foo'.
|
||||
# Other options are passed through to I18n.
|
||||
def heading_helper(model, attribute, options = {})
|
||||
i18nopts = options.select {|a| !['short'].include?(a) }
|
||||
s = model.human_attribute_name(attribute, i18nopts)
|
||||
if options[:short]
|
||||
sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({defaults: ''}))
|
||||
s = raw "<acronym title='#{s}'>#{sshort}</acronym>" unless sshort.empty?
|
||||
s = raw "<abbr title='#{s}'>#{sshort}</abbr>" unless sshort.empty?
|
||||
end
|
||||
s
|
||||
end
|
||||
|
|
|
@ -106,7 +106,7 @@ class Article < ActiveRecord::Base
|
|||
|
||||
# to get the correspondent shared article
|
||||
def shared_article
|
||||
@shared_article ||= self.supplier.shared_supplier.shared_articles.find_by_number(self.order_number)
|
||||
@shared_article ||= self.supplier.shared_supplier.shared_articles.find_by_number(self.order_number) rescue nil
|
||||
end
|
||||
|
||||
# convert units in foodcoop-size
|
||||
|
|
|
@ -90,7 +90,8 @@ class OrderArticle < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ordered_quantities_equal_to_group_orders?
|
||||
(units_to_order * price.unit_quantity) == group_orders_sum[:quantity]
|
||||
# the rescue is a workaround for units_to_order not being defined in integration tests
|
||||
(units_to_order * price.unit_quantity) == group_orders_sum[:quantity] rescue false
|
||||
end
|
||||
|
||||
# Updates order_article and belongings during balancing process
|
||||
|
|
|
@ -17,7 +17,6 @@ class User < ActiveRecord::Base
|
|||
has_many :assignments, :dependent => :destroy
|
||||
has_many :tasks, :through => :assignments
|
||||
has_many :send_messages, :class_name => "Message", :foreign_key => "sender_id"
|
||||
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, :settings_attributes
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
%tr.edit_inline{:id=> "edit_"+@article.id.to_s}
|
||||
%td{:colspan=>"10"}
|
||||
= t('.note', article: h(@article.name), drop_link: link_to(t('.drop'), :controller => 'orders', :action => 'edit', :id => @order)).html_safe
|
||||
= t('.note', article: h(@article.name), drop_link: link_to(t('.drop'), edit_order_path(@order))).html_safe
|
||||
|
|
|
@ -9,7 +9,14 @@
|
|||
// create List for search-feature (using list.js, http://listjs.com)
|
||||
var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}];
|
||||
var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}];
|
||||
new List(document.body, { valueNames: ['name'], engine: 'unlist', plugins: [listjsResetPlugin, listjsDelayPlugin] });
|
||||
new List(document.body, {
|
||||
valueNames: ['name'],
|
||||
engine: 'unlist',
|
||||
plugins: [listjsResetPlugin, listjsDelayPlugin],
|
||||
// make large pages work too (as we don't have paging - articles may disappear!)
|
||||
page: 10000,
|
||||
indexAsync: true
|
||||
});
|
||||
});
|
||||
|
||||
- title t('.title'), false
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
%li.nav-header= t '.foodcoop'
|
||||
%li= link_to t('.members'), foodcoop_users_path
|
||||
%li= link_to t('.tasks'), user_tasks_path
|
||||
%li= link_to t('.write_message'), :controller => "messages", :action => "new"
|
||||
%li= link_to t('.write_message'), new_message_path
|
||||
|
||||
- has_ordergroup = !@current_user.ordergroup.nil?
|
||||
- has_orders_role = @current_user.role_orders?
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
.span4
|
||||
%h2= @ordergroup.name
|
||||
.well
|
||||
- unless @ordergroup.description.blank?
|
||||
%p= @ordergroup.description
|
||||
%p
|
||||
%b= t '.description'
|
||||
= @ordergroup.description
|
||||
%p
|
||||
%b= t '.funds'
|
||||
%b= Ordergroup.human_attribute_name(:available_funds) + ':'
|
||||
= number_to_currency(@ordergroup.get_available_funds())
|
||||
%h2= t '.people'
|
||||
%ul
|
||||
- for membership in @ordergroup.memberships
|
||||
%li= show_user membership.user
|
||||
%p
|
||||
%b= Ordergroup.human_attribute_name(:user_tokens) + ':'
|
||||
= @ordergroup.memberships.map{|m| show_user m.user}.join(', ')
|
||||
= link_to t('.invite'), new_invite_path(:id => @ordergroup), :remote => true, class: 'btn btn-primary'
|
||||
.span8
|
||||
%h2= t('.account_summary')
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
%h3
|
||||
= h(t('.user.title', user: show_user))
|
||||
%small= t '.user.since', when: distance_of_time_in_words(Time.now, @current_user.created_on)
|
||||
= simple_form_for(@current_user, :url => { :action => 'update_profile'}) do |f|
|
||||
= simple_form_for(@current_user, :url => update_profile_path) do |f|
|
||||
= render :partial => 'shared/user_form_fields', :locals => {:f => f}
|
||||
.form-actions
|
||||
= submit_tag t('ui.save'), class: 'btn'
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{"http-equiv" => "content-type", :content => "text/html;charset=UTF-8"}
|
||||
%title= t '.title', title: (yield(:title) or controller.controller_name)
|
||||
= stylesheet_link_tag 'application'
|
||||
= stylesheet_link_tag "print", :media => "print"
|
||||
<!--[if lte IE 7]>
|
||||
= stylesheet_link_tag 'ie_hacks'
|
||||
<![endif]-->
|
||||
= javascript_include_tag 'application'
|
||||
= csrf_meta_tags
|
||||
= yield(:head)
|
||||
%body
|
||||
#logininfo= render :partial => 'shared/loginInfo'
|
||||
|
||||
#header
|
||||
#logo
|
||||
= link_to root_path do
|
||||
= t('layouts.logo').html_safe
|
||||
%span{:style => "color:white; font-size:45%; letter-spacing: -1px;"}= FoodsoftConfig[:name]
|
||||
#nav= render :partial => 'layouts/main_tabnav'
|
||||
|
||||
#main
|
||||
#content
|
||||
- flash.each do |name, msg|
|
||||
= content_tag :h3, msg, :id => "flash#{name.to_s.camelize}", :class => "flash #{name}"
|
||||
#loader{:style => "display:none;"}= image_tag("loader.gif", :border => 0)
|
||||
- if show_title?
|
||||
%h1= yield(:title)
|
||||
= yield
|
||||
#ajax_box(style="display:none")
|
|
@ -1,6 +1,6 @@
|
|||
- title t('.title')
|
||||
= t('.body').html_safe
|
||||
= simple_form_for User.new, url: {action: 'reset_password'} do |form|
|
||||
= simple_form_for User.new, url: reset_password_path do |form|
|
||||
= form.input :email
|
||||
.form-actions
|
||||
= form.submit t('.submit'), class: 'btn'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- title t('.title')
|
||||
= raw t('.body', user: h(show_user(@user)))
|
||||
= simple_form_for @user, :url => {:action => 'update_password', :id => @user.id, :token => @user.reset_password_token} do |form|
|
||||
= simple_form_for @user, :url => update_password_path(@user.id, :token => @user.reset_password_token) do |form|
|
||||
= form.input :password
|
||||
= form.input :password_confirmation
|
||||
.form-actions
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
%li= order_pdf(@order, :articles, t('.download.article_pdf'))
|
||||
%li= order_pdf(@order, :matrix, t('.download.matrix_pdf'))
|
||||
%li= order_pdf(@order, :fax, t('.download.fax_pdf'))
|
||||
%li= link_to t('.download.fax_txt'), {action: 'text_fax_template', id: @order }, {title: t('.download.download_file')}
|
||||
%li= link_to t('.download.fax_txt'), order_path(@order, format: :txt), {title: t('.download.download_file')}
|
||||
|
||||
%section#articles_table
|
||||
= render 'articles', order: @order
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
%ul.autocomplete
|
||||
- for article in @articles
|
||||
- supplier = @supplier ? "" : " - #{truncate(article.supplier.name)}"
|
||||
%li{:id => article.id.to_s}= "#{article.name} (#{article.unit_quantity} * #{article.unit} | #{number_to_currency(article.price)}#{supplier})"
|
|
@ -1,5 +0,0 @@
|
|||
<ul class="autocomplete">
|
||||
<% for user in @users do -%>
|
||||
<li><span class="nick"><%= show_user(user) %></span><span class="informal"> (<%= user.ordergroup.try(:name) %>)</span></li>
|
||||
<% end -%>
|
||||
</ul>
|
|
@ -1,9 +0,0 @@
|
|||
%ul
|
||||
%li
|
||||
= image_tag 'b_user.png' , :size => '7x10', :border => 0, :alt => t('.profile')
|
||||
= link_to h(@current_user.nick), my_profile_path, { :title => t('.edit_profile') }
|
||||
- if FoodsoftConfig[:homepage]
|
||||
%li= link_to FoodsoftConfig[:name], FoodsoftConfig[:homepage], { :title => t('.homepage_title') }
|
||||
%li= link_to t('.help'), FoodsoftConfig[:help_url]
|
||||
%li= link_to t('.feedback.title'), new_feedback_path, :title => t('.feedback.desc')
|
||||
%li= link_to t('.logout'), logout_path
|
|
@ -1,21 +0,0 @@
|
|||
<% if flash[:error] %>
|
||||
<h3 class="error" id="flashError" ><%= flash[:error] %></h3>
|
||||
<%= javascript_tag("new Effect.Highlight('flashError', {delay:0.8, duration:1});") -%>
|
||||
<% end %>
|
||||
|
||||
<% memberships = @group.memberships
|
||||
if memberships.size != 0 %>
|
||||
<ul style="">
|
||||
<% for membership in memberships %>
|
||||
<li style="margin-left:-15px">
|
||||
<%= show_user membership.user, full: true, markup: true %>
|
||||
| <%= link_to_remote t('.drop'),
|
||||
:url => { :controller => '/memberships', :action => 'drop_member', :id => @group, :membership_id => membership },
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')" %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p><i><%= t('.no_members', group: @group.name) %></i></p>
|
||||
<% end %>
|
|
@ -1,23 +0,0 @@
|
|||
<h1><%=h t('.title', group: @group.name) %></h1>
|
||||
<p>
|
||||
<i><%= t('.desc', link: remote_link_to(t('.invite'), :url => new_invite_path(:id => @group))).html_safe %></i>
|
||||
</p>
|
||||
<div class="left_column" style="width:48%">
|
||||
<div class="box_title">
|
||||
<h2><%= t('.already_members') %></h2>
|
||||
</div>
|
||||
<div class="column_content" id="members">
|
||||
<%=render :partial => 'shared/memberships/current_members' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right_column" style="width:48%">
|
||||
<div class="box_title">
|
||||
<h2><%= t('.no_members_yet') %></h2>
|
||||
</div>
|
||||
<div class="column_content" id="non_members">
|
||||
<%= render :partial => 'shared/memberships/non_members' %>
|
||||
<%= remote_link_to(t('.invite_someone'), :url => new_invite_path(:id => @group)) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit_box" style="display:none"></div>
|
|
@ -1,11 +0,0 @@
|
|||
<ul>
|
||||
<% for user in @group.non_members %>
|
||||
<li>
|
||||
<%= show_user user, full: true, markup: true %>
|
||||
| <%= link_to_remote t('.add'),
|
||||
:url => { :controller => '/memberships', :action => 'add_member', :id => @group, :user_id => user },
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')" %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
|
@ -13,5 +13,5 @@
|
|||
- @tasks.each do |task|
|
||||
%tr
|
||||
%td= task.due_date unless task.due_date.nil?
|
||||
%td= link_to t('.task_format', name: task.name, duration: task.duration), :controller => "tasks", :action => "show", :id => task
|
||||
%td= link_to t('.task_format', name: task.name, duration: task.duration), task_path(task)
|
||||
%td= task_assignments task
|
||||
|
|
|
@ -853,10 +853,7 @@ de:
|
|||
no_ordergroups: Leider bist Du kein Mitglied einer Bestellgruppe
|
||||
ordergroup:
|
||||
account_summary: Kontoauszug
|
||||
description: Beschreibung
|
||||
funds: ! 'Verfügbares Guthaben:'
|
||||
invite: Neue Person einladen
|
||||
people: Personen
|
||||
search: Suchen ...
|
||||
title: Meine Bestellgruppe
|
||||
ordergroup_cancelled: Du bist jetzt kein Mitglied der Gruppe %{group} mehr.
|
||||
|
@ -906,8 +903,6 @@ de:
|
|||
ordering:
|
||||
confirm_change: Änderungen an dieser Bestellung gehen verloren, wenn zu einer anderen Bestellung gewechselt wird. Möchtest Du trotzdem wechseln?
|
||||
layouts:
|
||||
application1:
|
||||
title: Foodsoft - %{title}
|
||||
email:
|
||||
footer: ! '--
|
||||
|
||||
|
@ -1367,28 +1362,6 @@ de:
|
|||
search_user: Nach Nutzerin suchen
|
||||
title: Wöchentliche Jobs
|
||||
user_not_found: Keine Nutzerin gefunden
|
||||
loginInfo:
|
||||
edit_profile: Profil bearbeiten
|
||||
feedback:
|
||||
desc: Fehler gefunden? Vorschlag? Idee? Kritik?
|
||||
title: Feedback
|
||||
help: Hilfe
|
||||
homepage_title: Foodcoop Homepage besuchen
|
||||
logout: Abmelden
|
||||
profile: Profil
|
||||
memberships:
|
||||
current_members:
|
||||
drop: entfernen
|
||||
no_members: ! '%{group} hat keine Mitglieder.'
|
||||
members:
|
||||
already_members: Sind schon Mitglieder
|
||||
desc: Hier kannst Du Mitglieder der Gruppe verwalten oder ein neues Foodcoop-Mitglied in die Gruppe %{link}.
|
||||
invite: einladen
|
||||
invite_someone: Person einladen
|
||||
no_members_yet: Sind noch keine Mitglieder
|
||||
title: Mitglieder von %{group}
|
||||
non_members:
|
||||
add: hinzufügen
|
||||
open_orders:
|
||||
ending: Ende
|
||||
no_open_orders: Derzeit gibt es keine laufenden Bestellungen
|
||||
|
@ -1416,7 +1389,6 @@ de:
|
|||
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
|
||||
task:
|
||||
|
|
|
@ -646,7 +646,7 @@ en:
|
|||
title: Invoice %{number}
|
||||
order_articles:
|
||||
edit:
|
||||
stock_alert:
|
||||
stock_alert: The price of stock articles cannot be changed!
|
||||
title: Update article
|
||||
new:
|
||||
title: Add delivered article to order
|
||||
|
@ -858,10 +858,7 @@ en:
|
|||
no_ordergroups: You are unfortunately not a member of an ordergroup.
|
||||
ordergroup:
|
||||
account_summary: Account Statement
|
||||
description: description
|
||||
funds: ! 'Available credit:'
|
||||
invite: Invite a new Person
|
||||
people: People
|
||||
search: Search ...
|
||||
title: My ordergroup
|
||||
ordergroup_cancelled: You cancelled membership of the group %{group}.
|
||||
|
@ -911,8 +908,6 @@ en:
|
|||
ordering:
|
||||
confirm_change: Modifications to this order will be lost when you change the order. Do you want to lose the changes you made and continue?
|
||||
layouts:
|
||||
application1:
|
||||
title: Foodsoft - %{title}
|
||||
email:
|
||||
footer: ! '--
|
||||
|
||||
|
@ -1372,28 +1367,6 @@ en:
|
|||
search_user: Search user
|
||||
title: Weekly jobs
|
||||
user_not_found: No user found
|
||||
loginInfo:
|
||||
edit_profile: Edit profile
|
||||
feedback:
|
||||
desc: Found a bug? Suggestions? Review?
|
||||
title: Feedback
|
||||
help: Help
|
||||
homepage_title: Visit Foodcoop Homepage
|
||||
logout: Logout
|
||||
profile: Profile
|
||||
memberships:
|
||||
current_members:
|
||||
drop: remove
|
||||
no_members: ! '%{group} has no members.'
|
||||
members:
|
||||
already_members: Are already members
|
||||
desc: Here you can manage members of the group or invite a new Foodcoop-member to the group %{link}.
|
||||
invite: invite
|
||||
invite_someone: Invite someone
|
||||
no_members_yet: Are not members yet
|
||||
title: Members of %{group}
|
||||
non_members:
|
||||
add: add
|
||||
open_orders:
|
||||
ending: Ending
|
||||
no_open_orders: There are no current orders
|
||||
|
@ -1421,7 +1394,6 @@ en:
|
|||
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
|
||||
task:
|
||||
|
|
|
@ -879,10 +879,7 @@ fr:
|
|||
no_ordergroups: Tu ne fais encore partie d'aucune cellule
|
||||
ordergroup:
|
||||
account_summary: Relevé de compte
|
||||
description: Description
|
||||
funds: ! 'Crédit disponible:'
|
||||
invite: Engrainer une nouvelle personne
|
||||
people: Personnes
|
||||
search: Rechercher ...
|
||||
title: Ta cellule
|
||||
ordergroup_cancelled: Tu ne fais plus partie de la cellule %{group}.
|
||||
|
@ -932,8 +929,6 @@ fr:
|
|||
ordering:
|
||||
confirm_change:
|
||||
layouts:
|
||||
application1:
|
||||
title: Foodsoft - %{title}
|
||||
email:
|
||||
footer: ! '--
|
||||
|
||||
|
@ -1374,28 +1369,6 @@ fr:
|
|||
search_user: Rechercher par utilisatrice
|
||||
title: Boulots hebdomadaires
|
||||
user_not_found: Aucune utilisatrice n'a été trouvée.
|
||||
loginInfo:
|
||||
edit_profile: Modifier le profil
|
||||
feedback:
|
||||
desc: Tu as détecté une erreur? Tu as des propositions, des idées, des critiques?
|
||||
title: Retours
|
||||
help: Aide
|
||||
homepage_title: Vers la page d'accueil de la bouffecoop
|
||||
logout: Te déconnecter
|
||||
profile: Profil
|
||||
memberships:
|
||||
current_members:
|
||||
drop: désinscrire
|
||||
no_members: ! '%{group} n''a aucun membre pour le moment.'
|
||||
members:
|
||||
already_members: Sont déjà membre
|
||||
desc: Sur cette page, tu peux gérer les membres de l'équipe, et aussi %{link} un nouveau membre.
|
||||
invite: engrainer
|
||||
invite_someone: Inviter quelqu'unE
|
||||
no_members_yet: Ne sont pas encore membre
|
||||
title: Membre de %{group}
|
||||
non_members:
|
||||
add: ajouter
|
||||
open_orders:
|
||||
ending: Clôture le
|
||||
no_open_orders: Il n'y a aucune commande en cours en ce moment
|
||||
|
@ -1423,7 +1396,6 @@ fr:
|
|||
name: Merci de modifier
|
||||
edit_stock_article:
|
||||
price: <ul><li>Modification du prix enregistrée. </li><li>Si nécessaire %{stock_article_copy_link}.</li></ul>
|
||||
supplier:
|
||||
supplier:
|
||||
min_order_quantity: La quantité minimum à commander est affichée pendant la commande et doit motiver
|
||||
task:
|
||||
|
|
|
@ -26,15 +26,15 @@ nl:
|
|||
description: Omschrijving
|
||||
name: Naam
|
||||
delivery:
|
||||
delivered_on:
|
||||
note:
|
||||
supplier:
|
||||
delivered_on: Leverdatum
|
||||
note: Notitie
|
||||
supplier: Leverancier
|
||||
financial_transaction:
|
||||
amount: bedrag
|
||||
note: notitie
|
||||
group_order_article:
|
||||
ordergroup_id:
|
||||
result:
|
||||
ordergroup_id: Huishouden
|
||||
result: Hoeveelheid
|
||||
invoice:
|
||||
amount: Bedrag
|
||||
date: Factuurdatum
|
||||
|
@ -47,12 +47,12 @@ nl:
|
|||
paid_on: Betaald op
|
||||
supplier: Leverancier
|
||||
message:
|
||||
body:
|
||||
group_id:
|
||||
private:
|
||||
recipient_tokens:
|
||||
sent_to_all:
|
||||
subject:
|
||||
body: Bericht
|
||||
group_id: Groep
|
||||
private: Privé
|
||||
recipient_tokens: Geadresseerden
|
||||
sent_to_all: Aan alle leden sturen
|
||||
subject: Onderwerp
|
||||
order:
|
||||
ends: Eindigt op
|
||||
note: Notitie
|
||||
|
@ -71,22 +71,22 @@ nl:
|
|||
name: Naam
|
||||
user_tokens: Leden
|
||||
page:
|
||||
body:
|
||||
body: Bericht
|
||||
parent_id:
|
||||
title:
|
||||
title: Titel
|
||||
stock_article:
|
||||
price: Prijs
|
||||
quantity: Aantal
|
||||
quantity_available: Beschikbaar
|
||||
supplier: Leverancier
|
||||
stock_taking:
|
||||
date:
|
||||
note:
|
||||
date: Datum
|
||||
note: Notitie
|
||||
supplier:
|
||||
address: Adres
|
||||
contact_person: Contactpersoon
|
||||
customer_number: Klantnummer
|
||||
customer_number_short:
|
||||
customer_number_short: Klantnr.
|
||||
delivery_days: Bezorgdagen
|
||||
email: Email
|
||||
fax: Fax
|
||||
|
@ -99,14 +99,14 @@ nl:
|
|||
phone2: Telefoon 2
|
||||
url: Homepage
|
||||
task:
|
||||
description:
|
||||
done:
|
||||
due_date:
|
||||
duration:
|
||||
name:
|
||||
required_users:
|
||||
user_list:
|
||||
workgroup:
|
||||
description: Beschrijving
|
||||
done: Gedaan?
|
||||
due_date: Voor wanneer?
|
||||
duration: Tijdsduur
|
||||
name: Naam
|
||||
required_users: Aantal
|
||||
user_list: Verantwoordelijken
|
||||
workgroup: Werkgroep
|
||||
user:
|
||||
email: Email
|
||||
first_name: Voornaam
|
||||
|
@ -123,7 +123,7 @@ nl:
|
|||
workgroup:
|
||||
description: Omschrijving
|
||||
name: Naam
|
||||
next_weekly_tasks_number:
|
||||
next_weekly_tasks_number: Hoeveel weken vooruit moet de taak zichtbaar zijn?
|
||||
role_admin: Beheer
|
||||
role_article_meta: Artikelen
|
||||
role_finance: Financiën
|
||||
|
@ -297,7 +297,7 @@ nl:
|
|||
error_parse: ! '%{msg} ... in regel %{line}'
|
||||
error_update: ! 'Er trad een fout op bij het bijwerken van artikel ''%{article}'': %{msg}'
|
||||
parse_upload:
|
||||
notice: ! '%{count} artikel zijn geanalyseerd'
|
||||
notice: ! '%{count} artikelen zijn geanalyseerd'
|
||||
sync:
|
||||
notice: Catalogus is bijgewerkt
|
||||
shared_alert: ! '%{supplier} is niet aan een externe database gekoppeld'
|
||||
|
@ -393,9 +393,9 @@ nl:
|
|||
title:
|
||||
form:
|
||||
actions:
|
||||
article:
|
||||
category:
|
||||
create_from_blank:
|
||||
article: Artikel
|
||||
category: Categorie
|
||||
create_from_blank: Nieuwe artikel invoeren
|
||||
create_stock_article:
|
||||
price:
|
||||
quantity:
|
||||
|
@ -639,7 +639,7 @@ nl:
|
|||
title: Factuur %{number}
|
||||
order_articles:
|
||||
edit:
|
||||
stock_alert:
|
||||
stock_alert: De prijs van voorraadartikelen kan niet aangepast worden!
|
||||
title: Artikel bijwerken
|
||||
new:
|
||||
title: Geleverd artikel aan bestelling toevoegen
|
||||
|
@ -850,10 +850,7 @@ nl:
|
|||
no_ordergroups: Jammergenoeg ben je niet aangesloten bij een huishouden.
|
||||
ordergroup:
|
||||
account_summary: Rekeningafschrift
|
||||
description: Omschrijving
|
||||
funds: ! 'Beschikbaar krediet:'
|
||||
invite: Iemand uitnodigen
|
||||
people: Personen
|
||||
search: Zoeken ...
|
||||
title: Mijn huishouden
|
||||
ordergroup_cancelled: Je bent geen lid meer van de groep %{group}.
|
||||
|
@ -903,8 +900,6 @@ nl:
|
|||
ordering:
|
||||
confirm_change:
|
||||
layouts:
|
||||
application1:
|
||||
title: Foodsoft - %{title}
|
||||
email:
|
||||
footer: ! '--
|
||||
|
||||
|
@ -1279,7 +1274,7 @@ nl:
|
|||
price: Totaalprijs
|
||||
articles_by_groups:
|
||||
fc_price: FC-Prijs
|
||||
fc_price_desc: Prijs inclusief belasting, borg en foodcoop-toeslag
|
||||
fc_price_desc: Prijs inclusief belasting, statiegeld en foodcoop marge
|
||||
name: Naam
|
||||
price: Totaalprijs
|
||||
unit: Eenheid
|
||||
|
@ -1301,28 +1296,6 @@ nl:
|
|||
search_user: Gebruiker zoeken
|
||||
title: Wekelijkse taken
|
||||
user_not_found: Geen gebruiker gevonden
|
||||
loginInfo:
|
||||
edit_profile: Profiel aanpassen
|
||||
feedback:
|
||||
desc: Fout gevonden? Opmerking? Idee?
|
||||
title: Feedback
|
||||
help: Help
|
||||
homepage_title: Foodcoop startpagina bezoeken
|
||||
logout: Uitloggen
|
||||
profile: Profiel
|
||||
memberships:
|
||||
current_members:
|
||||
drop: verwijderen
|
||||
no_members: ! '%{group} heeft geen leden.'
|
||||
members:
|
||||
already_members: Zijn al lid
|
||||
desc:
|
||||
invite: uitnodiging
|
||||
invite_someone: Iemand uitnodigen
|
||||
no_members_yet: Zijn nog geen lid
|
||||
title: Leden van %{group}
|
||||
non_members:
|
||||
add: toevoegen
|
||||
open_orders:
|
||||
ending: Einde
|
||||
no_open_orders: Er zijn momenteel geen lopende bestellingen.
|
||||
|
@ -1350,7 +1323,6 @@ nl:
|
|||
name:
|
||||
edit_stock_article:
|
||||
price:
|
||||
supplier:
|
||||
supplier:
|
||||
min_order_quantity:
|
||||
task:
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
|
||||
SimpleNavigation::Configuration.run do |navigation|
|
||||
|
||||
# allow engines to add to the menu - https://gist.github.com/mjtko/4873ee0c112b6bd646f8
|
||||
engines = Rails.application.railties.engines.select { |e| e.respond_to?(:navigation) }
|
||||
# to include an engine but keep it from modifying the menu:
|
||||
#engines.reject! { |e| e.instance_of? FoodsoftMyplugin::Engine }
|
||||
|
||||
navigation.items do |primary|
|
||||
primary.dom_class = 'nav'
|
||||
|
||||
|
@ -16,11 +21,6 @@ SimpleNavigation::Configuration.run do |navigation|
|
|||
subnav.item :tasks, I18n.t('navigation.tasks'), tasks_path, id: nil
|
||||
end
|
||||
|
||||
primary.item :wiki, I18n.t('navigation.wiki.title'), '#', id: nil do |subnav|
|
||||
subnav.item :wiki_home, I18n.t('navigation.wiki.home'), wiki_path, id: nil
|
||||
subnav.item :all_pages, I18n.t('navigation.wiki.all_pages'), all_pages_path, id: nil
|
||||
end
|
||||
|
||||
primary.item :orders, I18n.t('navigation.orders.title'), '#', id: nil do |subnav|
|
||||
subnav.item :ordering, I18n.t('navigation.orders.ordering'), group_orders_path, id: nil
|
||||
subnav.item :ordering_archive, I18n.t('navigation.orders.archive'), archive_group_orders_path, id: nil
|
||||
|
@ -47,6 +47,8 @@ SimpleNavigation::Configuration.run do |navigation|
|
|||
subnav.item :ordergroups, I18n.t('navigation.admin.ordergroups'), admin_ordergroups_path, id: nil
|
||||
subnav.item :workgroups, I18n.t('navigation.admin.workgroups'), admin_workgroups_path, id: nil
|
||||
end
|
||||
|
||||
engines.each { |e| e.navigation(primary, self) }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -18,27 +18,20 @@ Foodsoft::Application.routes.draw do
|
|||
|
||||
match '/login' => 'sessions#new', :as => 'login'
|
||||
match '/logout' => 'sessions#destroy', :as => 'logout'
|
||||
get '/login/forgot_password' => 'login#forgot_password', as: :forgot_password
|
||||
get '/login/new_password' => 'login#new_password', as: :new_password
|
||||
get '/login/forgot_password' => 'login#forgot_password', as: :forgot_password
|
||||
post '/login/reset_password' => 'login#reset_password', as: :reset_password
|
||||
get '/login/new_password' => 'login#new_password', as: :new_password
|
||||
get '/login/update_password' => 'login#update_password', as: :update_password
|
||||
match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation
|
||||
resources :sessions, :only => [:new, :create, :destroy]
|
||||
|
||||
########### User specific
|
||||
|
||||
match '/home/profile' => 'home#profile', :as => 'my_profile'
|
||||
put '/home/update_profile' => 'home#update_profile', :as => 'update_profile'
|
||||
match '/home/ordergroup' => 'home#ordergroup', :as => 'my_ordergroup'
|
||||
match '/home/cancel_membership' => 'home#cancel_membership', :as => 'cancel_membership'
|
||||
|
||||
############ Wiki
|
||||
|
||||
resources :pages do
|
||||
get :all, :on => :collection
|
||||
get :version, :on => :member
|
||||
get :revert, :on => :member
|
||||
end
|
||||
match '/wiki/:permalink' => 'pages#show', :as => 'wiki_page' # , :constraints => {:permalink => /[^\s]+/}
|
||||
match '/wiki' => 'pages#show', :defaults => {:permalink => 'Home'}, :as => 'wiki'
|
||||
|
||||
############ Orders, ordering
|
||||
|
||||
resources :orders do
|
||||
|
@ -191,8 +184,5 @@ Foodsoft::Application.routes.draw do
|
|||
|
||||
resources :users, :only => [:index]
|
||||
|
||||
# TODO: This is very error prone. Better deactivate this catch all route
|
||||
match ':controller(/:action(/:id))(.:format)'
|
||||
|
||||
end # End of /:foodcoop scope
|
||||
end
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# This migration comes from foodsoft_wiki_engine (originally 20090325175756)
|
||||
class CreatePages < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :pages do |t|
|
||||
t.string :title
|
||||
t.text :body
|
||||
t.string :permalink
|
||||
t.integer :lock_version, :default => 0
|
||||
t.integer :updated_by
|
||||
t.integer :redirect
|
||||
t.integer :parent_id
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
add_index :pages, :title
|
||||
add_index :pages, :permalink
|
||||
Page.create_versioned_table # Automaticly creates pages_versions table
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :pages
|
||||
Page.drop_versioned_table
|
||||
end
|
||||
end
|
93
doc/BESTELLEN.md
Normal file
93
doc/BESTELLEN.md
Normal file
|
@ -0,0 +1,93 @@
|
|||
|
||||
# Bestellen
|
||||
Das Bestellen ist der Hauptteil dieser Software und ein wenig kompliziert.
|
||||
Hier starte ich den Versuch die Programmlogik in Text umzusetzen und
|
||||
verweise auf die enstprechenden Controller bzw. Modelle.
|
||||
Der relevante Controller ist `OrdersController`.
|
||||
|
||||
## Bestellung "in Netz stellen"
|
||||
Darunter verstehen wir die Auswahl von Artikeln eines bestimmten Lieferanten fuer eine zeitlich begrenzte
|
||||
Bestellung im Internet. Die relevanten Methoden sind `OrdersController#newOrder` und folgende.
|
||||
Jede Bestellung wird durch die Klasse Order abgebildet.
|
||||
|
||||
Die zugehoerigen Artikel werden duch die Klasse `OrderArticle` mit den Artikeln verknuepft.
|
||||
Dabei werden auch die Attribute `quantity`, `tolerance` und `quantity_to_order` gespeichert.
|
||||
Diese Mengen repraesentieren die Gesamtbestellung, also alle Bestellgruppen.
|
||||
|
||||
## Eine Bestellgruppe bestellt...
|
||||
Die Methode `OrdersController#order` schickt uns die Bestellenseite. Mit dieser
|
||||
Oberflaeche koennen die Bestellgruppena die vorher ausgewaehlten Artikel
|
||||
bestellen. Mittels den Buttons werden dabei live, also clientseitig, die
|
||||
Preise ermittelt und der Gesamtpreis berechnet. Ist der Gesamtpreis groeßer als
|
||||
der aktuelle Gruppenkontostand, so wird die Preisspalte rot unterlegt und die
|
||||
Bestellung kann nicht gespeichert werden.
|
||||
|
||||
## (gruppen)-Bestellung wird gespeichert
|
||||
|
||||
Die Gruppenbestellung wird durch die Tabelle `group_oders` (`GroupOrder`)
|
||||
abgebildet, bzw. die Bestellung und Bestellgruppe wird dort verknuepft.
|
||||
|
||||
Die bestellten Artikel der Bestellgruppe werden durch die Tabelle `group_order_articles`
|
||||
(`GroupOrderArticle`) registriert. Dort werden nun die Modelle GroupOrder
|
||||
und OrderArticle miteinander verbunden.
|
||||
|
||||
Bei jeder Bestellung wird außerdem die Summe der Menge, Toleranz in `GroupOrderArticle`
|
||||
abgelegt. Allerdings muss jede Aenderung dieser Mengen mit protokolliert werden.
|
||||
Dies ist wichtig, weil spaeter die Zuteilung der Einheiten pro bestellten Artikel
|
||||
nach der chronologischen Reihenfolge erfolgt. (s.u.)
|
||||
Das passiert dann in der Tabelle `group_order_article_quantities`
|
||||
(`GroupOrderArticleQuantity`).
|
||||
|
||||
## Aenderunug einer Bestellung
|
||||
|
||||
Knifflig ist die Aenderung einer gruppenbestellung, weil die zeitliche
|
||||
Reihenfolge dabei nicht durcheinander geraten darf.
|
||||
Wir unterscheiden dehalb zwei Faelle:
|
||||
|
||||
### Erhoehe die Menge des Arikels.
|
||||
Jetzt wird eine Zeile in `group_order_article_quantities` angelegt.
|
||||
und zwar mit genau den Mengen, die zusatzlich bestellt wurden.
|
||||
Quantity und Tolerance funktionieren analog.
|
||||
|
||||
Beispiel:
|
||||
* Urspruenglich bestellt: 2(2) um 17uhr.
|
||||
* Erhoehe Bestellung auf 4(2) um 18hur.
|
||||
=> neue Zeile mit quantity = 2, tolerance = 0, und created_on = 18uhr
|
||||
* Jetzt gibt es zwei zeilen die insgesamt 4(2) ergeben.
|
||||
(die summen in `GroupOrderArticle` werden aktualisiert)
|
||||
|
||||
### Verringere die Mengen des Artikels.
|
||||
Jetzt muss chronologisch zurueckgegangen werden und um die urspruenglich bestellten
|
||||
Mengen zu verringern.
|
||||
|
||||
Beispiel von oben:
|
||||
* Verringe Bestellung auf 2(1) um 19uhr.
|
||||
=> Zeile mit created_on = 18uhr wird gelöscht und
|
||||
in der Zeile mit created_on = 17uhr wird der Wert tolerance auf 1 gaendert.
|
||||
|
||||
## Wer bekommt wieviel?
|
||||
|
||||
Diese Frage wird wie schon erwaehnt mittels der `group_order_article_quantites`-Tabelle
|
||||
geloest.
|
||||
|
||||
Beispiel.
|
||||
|
||||
* articel x mit unit_quantity = 5.
|
||||
* 17uhr: gruppe a bestellt 2(3), weil sie auf jeden fall was von x bekommen will
|
||||
* 18uhr: gruppe b bestellt 2(0)
|
||||
* 19uhr: gruppe a faellt ein dass sie doch noch mehr braucht von x und aendert auf 4(1).
|
||||
|
||||
* jetzt gibt es drei zeilen in der tabelle, die so aussehen:
|
||||
* (gruppe a), 2(1), 17uhr (wurde um 19uhr von 2(3) auf 2(1) geaendert)
|
||||
* (gruppe b), 2(0), 18uhr
|
||||
* (gruppe a), 2(0), 19uhr.
|
||||
|
||||
* die zuteilung wird dann wie folgt ermittelt:
|
||||
* zeile 1: gruppe a bekommt 2
|
||||
* zeile 2: gruppe b bekommt 2
|
||||
* zeile 3: gruppe a bekommt 1, weil jetzt das gebinde schon voll ist.
|
||||
|
||||
* Endstand: insg. Bestellt wurden 6(1)
|
||||
* Gruppe a bekommt 3 einheiten.
|
||||
* gruppe b bekommt 2 einheiten.
|
||||
* eine Einheit verfaellt.
|
|
@ -1,94 +0,0 @@
|
|||
Use this README file to introduce your application and point to useful places in the API for learning more.
|
||||
Run "rake doc:app" to generate API documentation for your models and controllers.
|
||||
|
||||
= The Foodsoft
|
||||
|
||||
is a Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling).
|
||||
|
||||
|
||||
== Bestellen
|
||||
Das Bestellen ist der Hauptteil dieser Software und ein wenig kompliziert.
|
||||
Hier starte ich den Versuch die Programmlogik in Text umzusetzen und
|
||||
verweise auf die enstprechenden Controller bzw. Modelle.
|
||||
Der relevante Controller ist OrderingController.
|
||||
|
||||
=== Bestellung "in Netz stellen"
|
||||
Darunter verstehen wir die Auswahl von Artikeln eines bestimmten Lieferanten fuer eine zeitlich begrenzte
|
||||
Bestellung im Internet. Die relevanten Methoden sind OrdersController#newOrder und folgende.
|
||||
Jede Bestellung wird durch die Klasse Order abgebildet.
|
||||
|
||||
Die zugehoerigen Artikel werden duch die Klasse OrderArticle mit den Artikeln verknuepft.
|
||||
Dabei werden auch die Attribute quantity, tolerance und quantity_to_order gespeichert.
|
||||
Diese Mengen repraesentieren die Gesamtbestellung, also alle Bestellgruppen.
|
||||
|
||||
=== Eine Bestellgruppe bestellt...
|
||||
Die Methode OrdersController#order schickt uns die Bestellenseite. Mit dieser Oberflaeche
|
||||
koennen die Bestellgruppena die vorher ausgewaehlten Artikel bestellen.
|
||||
Mittels den Buttons werden dabei live, also clientseitig, die Preise ermittelt
|
||||
und der Gesamtpreis berechnet. Ist der Gesamtpreis groeßer als der aktuelle
|
||||
Gruppenkontostand, so wird die Preisspalte rot unterlegt und die Bestellung
|
||||
kann nicht gespeichert werden.
|
||||
|
||||
=== (gruppen)-Bestellung wird gespeichert
|
||||
|
||||
Die Gruppenbestellung wird durch die Tabelle group_oders (GroupOrder)
|
||||
abgebildet, bzw. die Bestellung und Bestellgruppe wird dort verknuepft.
|
||||
|
||||
Die bestellten Artikel der Bestellgruppe werden durch die Tablle group_order_articles
|
||||
(GroupOrderArticle) registriert. Dort werden nun die Modelle GroupOrder
|
||||
und OrderArticle miteinander verbunden.
|
||||
|
||||
Bei jeder Bestellung wird außerdem die Summe der Menge, Toleranz in GroupOrderArticle
|
||||
abgelegt. Allerdings muss jede Aenderung dieser Mengen mit protokolliert werden.
|
||||
Dies ist wichtig, weil spaeter die Zuteilung der Einheiten pro bestellten Artikel
|
||||
nach der chronologischen Reihenfolge erfolgt. (s.u.)
|
||||
Das passiert dann in der Tabelle group_order_article_quantities.
|
||||
(GroupOrderArticleQuantity)
|
||||
|
||||
=== Aenderunug einer Bestellung
|
||||
|
||||
Knifflig ist die Aenderung einer gruppenbestellung, weil die zeitliche
|
||||
Reihenfolge dabei nicht durcheinander geraten darf.
|
||||
Wir unterscheiden dehalb zwei Faelle:
|
||||
==== Erhoehe die Menge des Arikels.
|
||||
Jetzt wird eine Zeile in group_order_article_quantities angelegt.
|
||||
und zwar mit genau den Mengen, die zusatzlich bestellt wurden.
|
||||
Quantity und Tolerance funktionieren analog.
|
||||
Beispiel:
|
||||
Urspruenglich bestellt: 2(2) um 17uhr.
|
||||
Erhoehe Bestellung auf 4(2) um 18hur.
|
||||
=> neue Zeile mit quantity= 2, tolerance = 0, und created_on = 18uhr
|
||||
Jetzt gibt es zwei zeilen die insgesamt 4(2) ergeben.
|
||||
(die summen in GroupOrderArticle werden aktualisiert)
|
||||
==== Verringere die Mengen des Artikels.
|
||||
Jetzt muss chronologisch zurueckgegangen werden und um die urspruenglich bestellten
|
||||
Mengen zu verringern.
|
||||
Beispiel von oben:
|
||||
Verringe Bestellung auf 2(1) um 19uhr.
|
||||
=> Zeile mit created_on = 18uhr wird gelöscht und
|
||||
in der Zeile mit created_on = 17uhr wird der Wert tolerance auf 1 gaendert.
|
||||
|
||||
=== Wer bekommt wieviel?
|
||||
|
||||
Diese Frage wird wie schon erwaehnt mittels der group_order_article_quantites -Tabelle
|
||||
geloest.
|
||||
Beipspiel.
|
||||
articel x mit unit_quantity = 5.
|
||||
17uhr: gruppe a bestellt 2(3), weil sie auf jeden fall was von x bekommen will
|
||||
18uhr: gruppe b bestellt 2(0)
|
||||
19uhr: gruppe a faellt ein dass sie doch noch mehr braucht von x und aendert auf 4(1).
|
||||
|
||||
jetzt gibt es drei zeilen in der tabelle, die so aussehen:
|
||||
(gruppe a), 2(1), 17uhr (wurde um 19uhr von 2(3) auf 2(1) geaendert)
|
||||
(gruppe b), 2(0), 18uhr
|
||||
(gruppe a), 2(0), 19uhr.
|
||||
|
||||
die zuteilung wird dann wie folgt ermittelt:
|
||||
zeile 1: gruppe a bekommt 2
|
||||
zeile 2: gruppe b bekommt 2
|
||||
zeile 3: gruppe a bekommt 1, weil jetzt das gebinde schon voll ist.
|
||||
|
||||
Endstand: insg. Bestellt wurden 6(1)
|
||||
Gruppe a bekommt 3 einheiten.
|
||||
gruppe b bekommt 2 einheiten.
|
||||
eine Einheit verfaellt.
|
|
@ -66,7 +66,7 @@ If you want to have more control, you can do these steps manually as
|
|||
explained here.
|
||||
|
||||
|
||||
1. **Configure datebase**
|
||||
1. **Configure database**
|
||||
|
||||
Create the database configuration from the default:
|
||||
```sh
|
||||
|
|
18
lib/foodsoft_wiki/README.md
Normal file
18
lib/foodsoft_wiki/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
FoodsoftWiki
|
||||
============
|
||||
|
||||
This plugin adds wiki pages to foodsoft. A new 'Wiki' menu is added next to
|
||||
the 'Foodcoops' menu in the navigation bar.
|
||||
|
||||
This plugin is enabled by default in foodsoft, so you don't need to do anything
|
||||
to install it. If you still want to, for example when it has been disabled,
|
||||
add the following to foodsoft's Gemfile:
|
||||
|
||||
```Gemfile
|
||||
# we use the git version of acts_as_versioned, so this needs to be in foodsoft's Gemfile
|
||||
gem 'acts_as_versioned', git: 'git://github.com/technoweenie/acts_as_versioned.git'
|
||||
gem 'foodsoft_wiki', path: 'lib/foodsoft_wiki'
|
||||
```
|
||||
|
||||
This plugin is part of the foodsoft package and uses the GPL-3 license (see
|
||||
foodsoft's LICENSE for the full license text).
|
40
lib/foodsoft_wiki/Rakefile
Normal file
40
lib/foodsoft_wiki/Rakefile
Normal file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env rake
|
||||
begin
|
||||
require 'bundler/setup'
|
||||
rescue LoadError
|
||||
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||
end
|
||||
begin
|
||||
require 'rdoc/task'
|
||||
rescue LoadError
|
||||
require 'rdoc/rdoc'
|
||||
require 'rake/rdoctask'
|
||||
RDoc::Task = Rake::RDocTask
|
||||
end
|
||||
|
||||
RDoc::Task.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'FoodsoftWiki'
|
||||
rdoc.options << '--line-numbers'
|
||||
rdoc.rdoc_files.include('README.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
||||
load 'rails/tasks/engine.rake'
|
||||
|
||||
|
||||
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
|
||||
task :default => :test
|
15
lib/foodsoft_wiki/config/routes.rb
Normal file
15
lib/foodsoft_wiki/config/routes.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
Rails.application.routes.draw do
|
||||
|
||||
scope '/:foodcoop' do
|
||||
|
||||
resources :pages do
|
||||
get :all, :on => :collection
|
||||
get :version, :on => :member
|
||||
get :revert, :on => :member
|
||||
end
|
||||
match '/wiki/:permalink' => 'pages#show', :as => 'wiki_page' # , :constraints => {:permalink => /[^\s]+/}
|
||||
match '/wiki' => 'pages#show', :defaults => {:permalink => 'Home'}, :as => 'wiki'
|
||||
|
||||
end
|
||||
|
||||
end
|
24
lib/foodsoft_wiki/foodsoft_wiki.gemspec
Normal file
24
lib/foodsoft_wiki/foodsoft_wiki.gemspec
Normal file
|
@ -0,0 +1,24 @@
|
|||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require "foodsoft_wiki/version"
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "foodsoft_wiki"
|
||||
s.version = FoodsoftWiki::VERSION
|
||||
s.authors = ["wvengen"]
|
||||
s.email = ["dev-foodsoft@willem.engen.nl"]
|
||||
s.homepage = "https://github.com/foodcoops/foodsoft"
|
||||
s.summary = "Wiki plugin for foodsoft."
|
||||
s.description = "Adds a wiki to foodsoft."
|
||||
|
||||
s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"]
|
||||
s.test_files = Dir["test/**/*"]
|
||||
|
||||
s.add_dependency "rails", "~> 3.2.15"
|
||||
s.add_dependency 'wikicloth'
|
||||
s.add_dependency 'acts_as_versioned' # need git version, make sure that is included in foodsoft's Gemfile
|
||||
|
||||
s.add_development_dependency "sqlite3"
|
||||
end
|
6
lib/foodsoft_wiki/lib/foodsoft_wiki.rb
Normal file
6
lib/foodsoft_wiki/lib/foodsoft_wiki.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
require 'wikicloth'
|
||||
require 'acts_as_versioned'
|
||||
require 'foodsoft_wiki/engine'
|
||||
|
||||
module FoodsoftWiki
|
||||
end
|
14
lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb
Normal file
14
lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module FoodsoftWiki
|
||||
class Engine < ::Rails::Engine
|
||||
def navigation(primary, ctx)
|
||||
primary.item :wiki, I18n.t('navigation.wiki.title'), '#', id: nil do |subnav|
|
||||
subnav.item :wiki_home, I18n.t('navigation.wiki.home'), ctx.wiki_path, id: nil
|
||||
subnav.item :all_pages, I18n.t('navigation.wiki.all_pages'), ctx.all_pages_path, id: nil
|
||||
end
|
||||
# move this last added item to just after the foodcoop menu
|
||||
if i = primary.items.index(primary[:foodcoop])
|
||||
primary.items.insert(i+1, primary.items.delete_at(-1))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
3
lib/foodsoft_wiki/lib/foodsoft_wiki/version.rb
Normal file
3
lib/foodsoft_wiki/lib/foodsoft_wiki/version.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
module FoodsoftWiki
|
||||
VERSION = "0.0.1"
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
begin
|
||||
require 'rspec/core/rake_task'
|
||||
task(:spec).clear
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
task :default => :spec
|
||||
rescue LoadError
|
||||
|
|
|
@ -52,4 +52,58 @@ describe Article do
|
|||
order = create :order, supplier: supplier, article_ids: [article.id]
|
||||
expect(article.in_open_order).to eq(order)
|
||||
end
|
||||
|
||||
|
||||
it 'has no shared article by default' do
|
||||
expect(article.shared_article).to be_nil
|
||||
end
|
||||
|
||||
describe 'connected to a shared database', :type => :feature do
|
||||
let(:shared_supplier) { create(:supplier) }
|
||||
let(:shared_article) { create :article, supplier: shared_supplier, order_number: Faker::Lorem.characters(rand(1..12)) }
|
||||
let(:supplier) { create :supplier, shared_supplier_id: shared_supplier.id }
|
||||
let(:article) { create :article, supplier: supplier, order_number: shared_article.order_number }
|
||||
|
||||
it 'can be found in the shared database' do
|
||||
expect(article.shared_article).to_not be_nil
|
||||
end
|
||||
|
||||
it 'can find updates' do
|
||||
changed = article.shared_article_changed?
|
||||
expect(changed).to_not be_false
|
||||
expect(changed.length).to be > 1
|
||||
end
|
||||
|
||||
it 'can be synchronised' do
|
||||
# TODO move article sync from supplier to article
|
||||
article # need to reference for it to exist when syncing
|
||||
updated_article = supplier.sync_all[0].select{|s| s[0].id==article.id}.first[0]
|
||||
article.update_attributes updated_article.attributes.reject{|k,v| k=='id' or k=='type'}
|
||||
expect(article.name).to eq(shared_article.name)
|
||||
# now synchronising shouldn't change anything anymore
|
||||
expect(article.shared_article_changed?).to be_false
|
||||
end
|
||||
|
||||
it 'does not need to synchronise an imported article' do
|
||||
article = SharedArticle.find(shared_article.id).build_new_article(supplier)
|
||||
expect(article.shared_article_changed?).to be_false
|
||||
end
|
||||
|
||||
it 'adapts to foodcoop units when synchronising' do
|
||||
shared_article.unit = '1kg'
|
||||
shared_article.unit_quantity = 1
|
||||
shared_article.save!
|
||||
article = SharedArticle.find(shared_article.id).build_new_article(supplier)
|
||||
article.article_category = create :article_category
|
||||
article.unit = '200g'
|
||||
article.shared_updated_on -= 1 # to make update do something
|
||||
article.save!
|
||||
# TODO get sync functionality in article
|
||||
updated_article = supplier.sync_all[0].select{|s| s[0].id==article.id}.first[0]
|
||||
article.update_attributes! updated_article.attributes.reject{|k,v| k=='id' or k=='type'}
|
||||
expect(article.unit).to eq '200g'
|
||||
expect(article.unit_quantity).to eq 5
|
||||
expect(article.price).to be_within(1e-3).of(shared_article.price/5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,6 +29,8 @@ RSpec.configure do |config|
|
|||
config.before(:each) do
|
||||
DatabaseCleaner.strategy = (example.metadata[:js] ? :truncation : :transaction)
|
||||
DatabaseCleaner.start
|
||||
# maximise window so that buttons can be found on popups
|
||||
example.metadata[:js] and page.driver.browser.manage.window.maximize
|
||||
end
|
||||
config.after(:each) do
|
||||
DatabaseCleaner.clean
|
||||
|
|
29
spec/support/shared_database.rb
Normal file
29
spec/support/shared_database.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# http://stackoverflow.com/questions/8774227
|
||||
# http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite
|
||||
class ActiveRecord::Base
|
||||
mattr_accessor :shared_connection
|
||||
@@shared_connection = nil
|
||||
|
||||
def self.connection
|
||||
@@shared_connection || retrieve_connection
|
||||
end
|
||||
end
|
||||
# Forces all threads to share the same connection. This works on
|
||||
# Capybara because it starts the web server in a thread.
|
||||
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
|
||||
|
||||
ActiveSupport.on_load(:after_initialize) do
|
||||
# We simulate the shared database by pointing to our own database.
|
||||
# This allows running tests without additional database setup.
|
||||
# But take care when designing tests using the shared database.
|
||||
SharedSupplier.establish_connection Rails.env
|
||||
SharedArticle.establish_connection Rails.env
|
||||
# hack for different structure of shared database
|
||||
SharedArticle.class_eval do
|
||||
alias_attribute :number, :order_number
|
||||
alias_attribute :updated_on, :updated_at
|
||||
def self.find_by_number(n)
|
||||
find_by_order_number(n)
|
||||
end
|
||||
end
|
||||
end
|
0
vendor/plugins/.gitkeep
vendored
0
vendor/plugins/.gitkeep
vendored
Loading…
Reference in a new issue