Merge branch 'master' into updated-gems

Conflicts:
	Gemfile.lock
This commit is contained in:
wvengen 2013-09-18 13:00:26 +02:00
commit 0abb63279f
55 changed files with 2898 additions and 136 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ doc/app/
Capfile Capfile
config/deploy.rb config/deploy.rb
config/deploy/* 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

24
Gemfile
View File

@ -53,10 +53,6 @@ group :development do
gem 'binding_of_caller' gem 'binding_of_caller'
# gem "rails-i18n-debug" # gem "rails-i18n-debug"
# Re-enable rails benchmarker/profiler
gem 'ruby-prof'
gem 'test-unit'
# Get infos when not using proper eager loading # Get infos when not using proper eager loading
gem 'bullet' gem 'bullet'
@ -71,6 +67,22 @@ group :development do
gem 'thin' gem 'thin'
end end
# Gems left for backwards compatibility group :development, :test do
gem 'acts_as_configurable', git: 'git://github.com/bwalding/acts_as_configurable.git' # user settings migration needs it 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: specs:
localize_input (0.1.0) 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 GIT
remote: git://github.com/technoweenie/acts_as_versioned.git remote: git://github.com/technoweenie/acts_as_versioned.git
revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b revision: 63b1fc8529d028fae632fe80ec0cb25df56cd76b
@ -71,6 +64,14 @@ GEM
net-ssh-gateway (>= 1.1.0) net-ssh-gateway (>= 1.1.0)
capistrano-ext (1.2.1) capistrano-ext (1.2.1)
capistrano (>= 1.0.0) 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.1) chronic (0.9.1)
client_side_validations (3.2.5) client_side_validations (3.2.5)
client_side_validations-simple_form (2.1.0) client_side_validations-simple_form (2.1.0)
@ -86,7 +87,9 @@ GEM
coffee-script-source (1.6.3) coffee-script-source (1.6.3)
commonjs (0.2.6) commonjs (0.2.6)
daemons (1.1.9) daemons (1.1.9)
database_cleaner (0.7.1)
debug_inspector (0.0.2) debug_inspector (0.0.2)
diff-lcs (1.2.4)
erubis (2.7.0) erubis (2.7.0)
eventmachine (1.0.3) eventmachine (1.0.3)
exception_notification (4.0.0) exception_notification (4.0.0)
@ -95,6 +98,14 @@ GEM
execjs (1.4.0) execjs (1.4.0)
multi_json (~> 1.0) multi_json (~> 1.0)
expression_parser (0.9.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 (4.0.3) haml (4.0.3)
tilt tilt
haml-rails (0.4) haml-rails (0.4)
@ -107,9 +118,13 @@ GEM
highline (1.6.19) highline (1.6.19)
hike (1.2.3) hike (1.2.3)
i18n (0.6.1) i18n (0.6.1)
i18n-spec (0.4.0)
iso
inherited_resources (1.4.0) inherited_resources (1.4.0)
has_scope (~> 0.5.0) has_scope (~> 0.5.0)
responders (~> 0.9) responders (~> 0.9)
iso (0.2.0)
i18n
journey (1.0.4) journey (1.0.4)
jquery-rails (3.0.4) jquery-rails (3.0.4)
railties (>= 3.0, < 5.0) railties (>= 3.0, < 5.0)
@ -142,6 +157,7 @@ GEM
activesupport (~> 3.1) activesupport (~> 3.1)
polyamorous (~> 0.5.0) polyamorous (~> 0.5.0)
mime-types (1.23) mime-types (1.23)
mini_portile (0.5.1)
mono_logger (1.1.0) mono_logger (1.1.0)
multi_json (1.7.7) multi_json (1.7.7)
mysql2 (0.3.11) mysql2 (0.3.11)
@ -152,6 +168,8 @@ GEM
net-ssh (2.6.8) net-ssh (2.6.8)
net-ssh-gateway (1.2.0) net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
nokogiri (1.6.0)
mini_portile (~> 0.5.0)
pdf-reader (1.3.3) pdf-reader (1.3.3)
Ascii85 (~> 1.0.0) Ascii85 (~> 1.0.0)
afm (~> 0.2.0) afm (~> 0.2.0)
@ -196,7 +214,7 @@ GEM
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redis (3.0.4) redis (3.0.4)
redis-namespace (1.3.0) redis-namespace (1.3.1)
redis (~> 3.0.0) redis (~> 3.0.0)
ref (1.0.5) ref (1.0.5)
responders (0.9.3) responders (0.9.3)
@ -207,8 +225,20 @@ GEM
redis-namespace (~> 1.2) redis-namespace (~> 1.2)
sinatra (>= 0.9.2) sinatra (>= 0.9.2)
vegas (~> 0.1.2) vegas (~> 0.1.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-prof (0.13.0)
ruby-rc4 (0.1.5) ruby-rc4 (0.1.5)
rubyzip (0.9.9)
sass (3.2.9) sass (3.2.9)
sass-rails (3.2.6) sass-rails (3.2.6)
railties (~> 3.2.0) railties (~> 3.2.0)
@ -217,6 +247,11 @@ GEM
select2-rails (3.4.3) select2-rails (3.4.3)
sass-rails sass-rails
thor (~> 0.14) 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.11.0) simple-navigation (3.11.0)
activesupport (>= 2.3.2) activesupport (>= 2.3.2)
simple-navigation-bootstrap (1.0.0) simple-navigation-bootstrap (1.0.0)
@ -225,6 +260,10 @@ GEM
simple_form (2.1.0) simple_form (2.1.0)
actionpack (~> 3.0) actionpack (~> 3.0)
activemodel (~> 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.4.3) sinatra (1.4.3)
rack (~> 1.4) rack (~> 1.4)
rack-protection (~> 1.4) rack-protection (~> 1.4)
@ -238,7 +277,6 @@ GEM
rack (~> 1.0) rack (~> 1.0)
tilt (~> 1.1, != 1.3.0) tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.7) sqlite3 (1.3.7)
test-unit (2.5.5)
therubyracer (0.11.4) therubyracer (0.11.4)
libv8 (~> 3.11.8.12) libv8 (~> 3.11.8.12)
ref ref
@ -264,18 +302,20 @@ GEM
uniform_notifier (1.2.0) uniform_notifier (1.2.0)
vegas (0.1.11) vegas (0.1.11)
rack (>= 1.0.0) rack (>= 1.0.0)
websocket (1.0.7)
whenever (0.8.3) whenever (0.8.3)
activesupport (>= 2.3.4) activesupport (>= 2.3.4)
chronic (>= 0.6.3) chronic (>= 0.6.3)
wikicloth (0.8.0) wikicloth (0.8.0)
builder builder
expression_parser expression_parser
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
acts_as_configurable!
acts_as_tree acts_as_tree
acts_as_versioned! acts_as_versioned!
better_errors better_errors
@ -284,12 +324,17 @@ DEPENDENCIES
bullet bullet
capistrano (= 2.13.5) capistrano (= 2.13.5)
capistrano-ext capistrano-ext
capybara (~> 2.1.0)
client_side_validations client_side_validations
client_side_validations-simple_form client_side_validations-simple_form
coffee-rails (~> 3.2.1) coffee-rails (~> 3.2.1)
daemons daemons
database_cleaner
exception_notification exception_notification
factory_girl_rails (~> 4.0)
faker
haml-rails haml-rails
i18n-spec
inherited_resources inherited_resources
jquery-rails jquery-rails
kaminari kaminari
@ -303,14 +348,18 @@ DEPENDENCIES
rails (~> 3.2.9) rails (~> 3.2.9)
rails-settings-cached (= 0.2.4) rails-settings-cached (= 0.2.4)
resque resque
rspec-core
rspec-expectations
rspec-rails
ruby-prof ruby-prof
sass-rails (~> 3.2.3) sass-rails (~> 3.2.3)
select2-rails select2-rails
selenium-webdriver (~> 2.35.1)
simple-navigation simple-navigation
simple-navigation-bootstrap simple-navigation-bootstrap
simple_form simple_form
simplecov
sqlite3 sqlite3
test-unit
therubyracer therubyracer
thin thin
twitter-bootstrap-rails 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 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) [![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) [![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft)

View File

@ -209,7 +209,7 @@ class ArticlesController < ApplicationController
# fills a form whith values of the selected shared_article # fills a form whith values of the selected shared_article
def import 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 render :action => 'new', :layout => false
end end

View File

@ -18,7 +18,7 @@ class SuppliersController < ApplicationController
def new def new
if params[:shared_supplier_id] if params[:shared_supplier_id]
shared_supplier = SharedSupplier.find(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 else
@supplier = Supplier.new @supplier = Supplier.new
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -13,11 +13,9 @@ class Supplier < ActiveRecord::Base
:delivery_days, :order_howto, :note, :shared_supplier_id, :min_order_quantity :delivery_days, :order_howto, :note, :shared_supplier_id, :min_order_quantity
validates :name, :presence => true, :length => { :in => 4..30 } 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 :address, :presence => true, :length => { :in => 8..50 }
validates_length_of :order_howto, :note, maximum: 250 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 validate :uniqueness_of_name
scope :undeleted, -> { where(deleted_at: nil) } scope :undeleted, -> { where(deleted_at: nil) }

View File

@ -66,7 +66,7 @@ class User < ActiveRecord::Base
end end
def receive_email? def receive_email?
settings.messages['send_as_email'] == "1" && email.present? settings.messages['send_as_email'] && email.present?
end end
# Sets the user's password. It will be stored encrypted along with a random salt. # Sets the user's password. It will be stored encrypted along with a random salt.

View File

@ -3,7 +3,7 @@ class Workgroup < Group
has_many :tasks has_many :tasks
# returns all non-finished 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_uniqueness_of :name
validate :last_admin_on_earth, :on => :update validate :last_admin_on_earth, :on => :update

View File

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

View File

@ -5,7 +5,7 @@
%thead %thead
%tr %tr
%th= sort_link_helper t('.name'), "name", :per_page => @per_page %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.numeric= sort_link_helper t('.account_balance'), "account_balance", :per_page => @per_page
%th %th
%tbody %tbody
@ -17,4 +17,4 @@
%td %td
= link_to t('.new_transaction'), new_finance_ordergroup_transaction_path(ordergroup), class: 'btn btn-mini' = link_to t('.new_transaction'), new_finance_ordergroup_transaction_path(ordergroup), class: 'btn btn-mini'
= link_to t('.account_statement'), finance_ordergroup_transactions_path(ordergroup), class: 'btn btn-mini' = link_to t('.account_statement'), finance_ordergroup_transactions_path(ordergroup), class: 'btn btn-mini'

View File

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

View File

@ -4,4 +4,4 @@
= f.input :date = f.input :date
= f.input :note = f.input :note
= f.submit = 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 = render :partial => 'stock_change', :collection => @stock_taking.stock_changes
.form-actions .form-actions
= f.submit class: 'btn' = f.submit class: 'btn'
= link_to t('ui.cancel'), stock_takings_path = link_to t('ui.or_cancel'), stock_takings_path

View File

@ -1,4 +1,4 @@
- title "Lager (#{StockArticle.available.count})" - title t('.title', article_count: StockArticle.available.count)
- content_for :javascript do - content_for :javascript do
:javascript :javascript
$(function() { $(function() {

View File

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

View File

@ -4,7 +4,7 @@
- content_for :sidebar do - content_for :sidebar do
.well.well-small .well.well-small
%ul.nav.nav-list %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('.my_tasks'), user_tasks_path
%li= link_to t('.all_tasks'), tasks_path %li= link_to t('.all_tasks'), tasks_path
%li= link_to t('.archive'), archive_tasks_path %li= link_to t('.archive'), archive_tasks_path

View File

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

View File

@ -505,6 +505,7 @@ de:
- Gebinde - Gebinde
- Einheit - Einheit
- Preis/Einheit - Preis/Einheit
total: Gesamtpreis
order_matrix: order_matrix:
filename: Bestellung %{name}-%{date} - Sortiermatrix filename: Bestellung %{name}-%{date} - Sortiermatrix
heading: Artikelübersicht heading: Artikelübersicht
@ -719,6 +720,7 @@ de:
ordergroups: ordergroups:
account_balance: Kontostand account_balance: Kontostand
account_statement: Kontoauszug account_statement: Kontoauszug
contact: Kontakt
name: Name name: Name
new_transaction: Neue Transaktion new_transaction: Neue Transaktion
update: update:
@ -1602,6 +1604,7 @@ de:
contact_person: Kontaktperson contact_person: Kontaktperson
contact_phone: Telefon contact_phone: Telefon
ignore_apple_restriction: Bestellstop bei zu wenig Äpfeln ignorieren ignore_apple_restriction: Bestellstop bei zu wenig Äpfeln ignorieren
name: Name
page: page:
body: Inhalt body: Inhalt
parent_id: Oberseite parent_id: Oberseite
@ -1669,6 +1672,7 @@ de:
language: language:
de: Deutsch de: Deutsch
en: English en: English
fr: Französisch
nl: Niederländisch nl: Niederländisch
required: required:
mark: ! '*' mark: ! '*'
@ -1742,6 +1746,7 @@ de:
show_stock_takings: Inventurübersicht show_stock_takings: Inventurübersicht
stock_count: ! 'Artikelanzahl:' stock_count: ! 'Artikelanzahl:'
stock_worth: ! 'Aktueller Lagerwert:' stock_worth: ! 'Aktueller Lagerwert:'
title: Lager (%{article_count})
toggle_unavailable: Nicht verfügbare Artikel zeigen/verstecken toggle_unavailable: Nicht verfügbare Artikel zeigen/verstecken
view_options: Ansichtsoptionen view_options: Ansichtsoptionen
new: new:
@ -1772,6 +1777,7 @@ de:
shared_suppliers: 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> 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: abonnieren
subscribe_again: erneut abonnieren
supplier: Lieferantin supplier: Lieferantin
title: Externe Listen title: Externe Listen
show: show:
@ -1832,6 +1838,7 @@ de:
group_tasks: Gruppenaufgaben group_tasks: Gruppenaufgaben
my_tasks: Meine Aufgaben my_tasks: Meine Aufgaben
new_task: Neue Aufgabe erstellen new_task: Neue Aufgabe erstellen
pages: Seiten
new: new:
title: Neue Aufgabe erstellen title: Neue Aufgabe erstellen
repeated: Aufgabe wird wöchentlich wiederholt repeated: Aufgabe wird wöchentlich wiederholt

View File

@ -236,7 +236,7 @@ en:
option_available: Make articles available option_available: Make articles available
option_delete: Delete article option_delete: Delete article
option_not_available: Make articles unavailable option_not_available: Make articles unavailable
option_select: Choose special offer ... option_select: Select action ...
price_netto: Price price_netto: Price
unit_quantity_desc: Unit quantity unit_quantity_desc: Unit quantity
unit_quantity_short: Quantity unit_quantity_short: Quantity
@ -440,7 +440,7 @@ en:
article: Article article: Article
category: Category category: Category
create_from_blank: Create new article create_from_blank: Create new article
create_stock_article: Create stock articles create_stock_article: Create stock article
price: Netprice price: Netprice
quantity: Quantity quantity: Quantity
title_fill_quantities: 2. Set delivery quantities title_fill_quantities: 2. Set delivery quantities
@ -493,7 +493,7 @@ en:
- Article - Article
- Amount - Amount
- Price - Price
- Unit Quantity - Unit quantity
- Unit - Unit
- Sum - Sum
sum: Sum sum: Sum
@ -504,20 +504,24 @@ en:
- Order Number - Order Number
- Amount - Amount
- Name - Name
- Barrel - Unit quantity
- Unit - Unit
- Price/Unit - Price/Unit
- Subtotal
total: Total
order_matrix: order_matrix:
filename: Order %{name}-%{date} - sorting matrix filename: Order %{name}-%{date} - sorting matrix
heading: Article overview heading: Article overview
rows: rows:
- Article - Article
- Unit - Unit
- Barrel - Unit quantity
- FC-Price - FC-Price
- Amount - Amount
title: ! 'Order sorting matrix: %{name}, closed at %{date}' 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: errors:
format: ! '%{attribute} %{message}' format: ! '%{attribute} %{message}'
general: A problem has occured. general: A problem has occured.
@ -721,6 +725,7 @@ en:
ordergroups: ordergroups:
account_balance: Account balance account_balance: Account balance
account_statement: Account statement account_statement: Account statement
contact: Contact
name: Name name: Name
new_transaction: New transaction new_transaction: New transaction
update: update:
@ -1604,6 +1609,7 @@ en:
contact_person: Contact person contact_person: Contact person
contact_phone: Phone contact_phone: Phone
ignore_apple_restriction: Ignore order stop by apple points restriction ignore_apple_restriction: Ignore order stop by apple points restriction
name: Name
page: page:
body: Body body: Body
parent_id: Parent page parent_id: Parent page
@ -1671,6 +1677,7 @@ en:
language: language:
de: German de: German
en: English en: English
fr: French
nl: Dutch nl: Dutch
required: required:
mark: ! '*' mark: ! '*'
@ -1744,6 +1751,7 @@ en:
show_stock_takings: Inventory overview show_stock_takings: Inventory overview
stock_count: ! 'Number of articles:' stock_count: ! 'Number of articles:'
stock_worth: ! 'Current stock value:' stock_worth: ! 'Current stock value:'
title: Stock (%{article_count})
toggle_unavailable: Show/hide unavailable articles toggle_unavailable: Show/hide unavailable articles
view_options: View options view_options: View options
new: new:
@ -1774,6 +1782,7 @@ en:
shared_suppliers: 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> 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: Subscribe
subscribe_again: Subscribe again
supplier: Supplier supplier: Supplier
title: External lists title: External lists
show: show:
@ -1834,6 +1843,7 @@ en:
group_tasks: Group tasks group_tasks: Group tasks
my_tasks: My tasks my_tasks: My tasks
new_task: Create new task new_task: Create new task
pages: Pages
new: new:
title: Create new tasks title: Create new tasks
repeated: Task is repeated weekly repeated: Task is repeated weekly

1919
config/locales/fr.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -479,48 +479,60 @@ nl:
notice: notice:
documents: documents:
order_by_articles: order_by_articles:
filename: filename: Bestelling %{name}-%{date} - Artikellijst
rows: rows:
title: - Huishouden
- Hoeveelheid
- Prijs
title: ! 'Artikellijst van bestelling: %{name}, gesloten op %{date}'
order_by_groups: order_by_groups:
filename: filename: Bestelling %{name}-%{date} - Huishoudenslijst
rows: rows:
sum: - Artikel
title: - Hoeveelheid
- Prijs
- Gr.Eenh.
- Eenheid
- Som
sum: Som
title: ! 'Huishoudenslijst van bestelling: %{name}, gesloten op %{date}'
order_fax: order_fax:
filename: filename: Bestelling %{name}-%{date} - Fax
rows: rows: ! '[]'
total: Totaal
order_matrix: order_matrix:
filename: filename: Bestelling %{name}-%{date} - Sorteermatrix
heading: heading: Artikeloverzicht
rows: rows:
title: title: ! 'Sorteermatrix van bestelling: %{name}, gesloten op %{date}'
total: total:
one: In totaal éen artikel
other: In totaal %{count} artikelen
errors: errors:
format: format:
general: general: Er is een probleem opgetreden.
general_again: general_again:
general_msg: general_msg: ! 'Er is een probleem opgetreden: %{msg}'
messages: messages:
accepted: accepted: moet geaccepteerd worden
blank: blank: moet ingevuld worden
confirmation: confirmation:
empty: empty: moet ingevuld worden
equal_to: equal_to: moet precies %{count} zijn
even: even:
exclusion: exclusion: moet even zijn
greater_than: greater_than: moet groter dan %{count} zijn
greater_than_or_equal_to: greater_than_or_equal_to:
inclusion: inclusion:
invalid: invalid:
less_than: less_than: moet kleiner dan %{count} zijn
less_than_or_equal_to: less_than_or_equal_to: moet groter of gelijk aan %{count} zijn
not_a_number: not_a_number: is geen getal
not_an_integer: not_an_integer: moet een geheel getal zijn
odd: odd: moet oneven zijn
record_invalid: record_invalid:
taken: taken: is al in gebruik
taken_with_deleted: taken_with_deleted: is al in gebruik (verwijderde groep)
too_long: too_long:
too_short: too_short:
wrong_length: wrong_length:
@ -697,6 +709,7 @@ nl:
ordergroups: ordergroups:
account_balance: Tegoed account_balance: Tegoed
account_statement: account_statement:
contact:
name: Naam name: Naam
new_transaction: Nieuwe transactie new_transaction: Nieuwe transactie
update: update:
@ -1493,6 +1506,7 @@ nl:
contact_person: Contactpersoon contact_person: Contactpersoon
contact_phone: Telefoon contact_phone: Telefoon
ignore_apple_restriction: ignore_apple_restriction:
name:
page: page:
body: body:
parent_id: parent_id:
@ -1560,6 +1574,7 @@ nl:
language: language:
de: Duits de: Duits
en: Engels en: Engels
fr: Frans
nl: Nederlands nl: Nederlands
required: required:
mark: ! '*' mark: ! '*'
@ -1633,6 +1648,7 @@ nl:
show_stock_takings: show_stock_takings:
stock_count: stock_count:
stock_worth: stock_worth:
title:
toggle_unavailable: toggle_unavailable:
view_options: view_options:
new: new:
@ -1663,6 +1679,7 @@ nl:
shared_suppliers: shared_suppliers:
body: body:
subscribe: subscribe:
subscribe_again:
supplier: supplier:
title: title:
show: show:
@ -1723,6 +1740,7 @@ nl:
group_tasks: group_tasks:
my_tasks: my_tasks:
new_task: new_task:
pages:
new: new:
title: title:
repeated: repeated:
@ -1763,9 +1781,9 @@ nl:
history: history:
marks: marks:
close: ! '&times;' close: ! '&times;'
success: success: <i class="icon icon-ok"></i>
or_cancel: of annuleren or_cancel: of annuleren
please_wait: please_wait: Een moment alstublieft...
save: Opslaan save: Opslaan
show: Tonen show: Tonen
views: views:

View File

@ -35,6 +35,8 @@ class MoveWeeklyTasks < ActiveRecord::Migration
private private
def weekly_task?(workgroup, task) def weekly_task?(workgroup, task)
return false if task.due_date.nil?
group_task = { group_task = {
weekday: workgroup.weekday, weekday: workgroup.weekday,
name: workgroup.task_name, name: workgroup.task_name,

View File

@ -1,34 +1,55 @@
class MigrateUserSettings < ActiveRecord::Migration class MigrateUserSettings < ActiveRecord::Migration
def up def up
old_settings = ConfigurableSetting.all say_with_time 'Save old user settings in new RailsSettings module' do
old_settings.each do |old_setting| # Allow setting default locale via env parameter
# get target (user) # This is used, when setting users language settings
type = old_setting.configurable_type default_locale = I18n.default_locale
id = old_setting.configurable_id tmp_locale = ENV['DEFAULT_LOCALE'].present? ? ENV['DEFAULT_LOCALE'].to_sym : default_locale
user = type.constantize.find(id) I18n.default_locale = tmp_locale
# get the data (settings) old_settings = ConfigurableSetting.all
name = old_setting.name
namespace = name.split('.')[0] old_settings.each do |old_setting|
key = name.split('.')[1].underscore # Camelcase to underscore # get target (user)
type = old_setting.configurable_type
# prepare value id = old_setting.configurable_id
value = YAML.load(old_setting.value) begin
value = value.nil? ? false : value user = type.constantize.find(id)
rescue ActiveRecord::RecordNotFound
# set the settings_attributes (thanks to settings.merge! we can set them one by one) Rails.logger.debug "Can't find configurable object with type: #{type.inspect}, id: #{id.inspect}"
user.settings_attributes = { next
"#{namespace}" => { end
"#{key}" => value
# get the data (settings)
name = old_setting.name
namespace = name.split('.')[0]
key = name.split('.')[1].underscore # Camelcase to underscore
# prepare value
value = YAML.load(old_setting.value)
value = value.nil? ? false : value
# set the settings_attributes (thanks to settings.merge! we can set them one by one)
user.settings_attributes = {
"#{namespace}" => {
"#{key}" => value
}
} }
}
# save the user to apply after_save callback
# save the user to apply after_save callback user.save
user.save end
I18n.default_locale = default_locale
end end
drop_table :configurable_settings
end end
def down def down
end end
end end
# this is the base class of all configurable settings
class ConfigurableSetting < ActiveRecord::Base; end

View File

@ -66,18 +66,6 @@ ActiveRecord::Schema.define(:version => 20130718183101) do
add_index "assignments", ["user_id", "task_id"], :name => "index_assignments_on_user_id_and_task_id", :unique => true add_index "assignments", ["user_id", "task_id"], :name => "index_assignments_on_user_id_and_task_id", :unique => true
create_table "configurable_settings", :force => true do |t|
t.integer "configurable_id"
t.string "configurable_type"
t.integer "targetable_id"
t.string "targetable_type"
t.string "name", :default => "", :null => false
t.string "value_type"
t.text "value"
end
add_index "configurable_settings", ["name"], :name => "index_configurable_settings_on_name"
create_table "deliveries", :force => true do |t| create_table "deliveries", :force => true do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.date "delivered_on" t.date "delivered_on"

View File

@ -11,7 +11,7 @@ module FoodsoftFile
def self.parse(file) def self.parse(file)
articles, outlisted_articles = Array.new, Array.new articles, outlisted_articles = Array.new, Array.new
row_index = 2 row_index = 2
::CSV.parse(file.read, {:col_sep => ";", :headers => true}) do |row| ::CSV.parse(file.read.force_encoding('utf-8'), {:col_sep => ";", :headers => true}) do |row|
# check if the line is empty # check if the line is empty
unless row[2] == "" || row[2].nil? unless row[2] == "" || row[2].nil?
article = {:number => row[1], article = {:number => row[1],

View File

@ -38,6 +38,15 @@ namespace :foodsoft do
puts yellow "All done! Your foodcoft should be running smoothly." puts yellow "All done! Your foodcoft should be running smoothly."
start_server start_server
end end
namespace :setup do
desc "Initialize stock configuration"
task :stock_config do
setup_app_config
setup_development
setup_secret_token
end
end
end end
def setup_bundler def setup_bundler

View File

@ -1,17 +1,21 @@
# This namespace is used for a collection of tasks to maintain a hosting environment with multiple foodcoops
# This tasks are a kind of wrapper for other tasks. The wrapper makes sure, that the appropriate database and config
# for each foodcoop is used.
namespace :multicoops do namespace :multicoops do
desc 'Runs a specific rake task for each registered foodcoop, use rake multicoops:run db:migrate' desc 'Runs a specific rake task for each registered foodcoop, use rake multicoops:run TASK=db:migrate'
task :run => :environment do task :run => :environment do
task_to_run = ARGV[1] task_to_run = ENV['TASK']
FoodsoftConfig.each_coop do |coop| FoodsoftConfig.each_coop do |coop|
puts "Run '#{task_to_run}' for #{coop}" puts "Run '#{task_to_run}' for #{coop}"
Rake::Task[task_to_run].execute Rake::Task[task_to_run].execute
end end
end end
desc 'Runs a specific rake task for a single coop, use rake mutlicoops:run_single db:migrate FOODCOOP=demo' desc 'Runs a specific rake task for a single coop, use rake mutlicoops:run_single TASK=db:migrate FOODCOOP=demo'
task :run_single => :environment do task :run_single => :environment do
task_to_run = ARGV[1] task_to_run = ENV['TASK']
FoodsoftConfig.select_foodcoop ENV['FOODCOOP'] FoodsoftConfig.select_foodcoop ENV['FOODCOOP']
puts "Run '#{task_to_run}' for #{ENV['FOODCOOP']}" puts "Run '#{task_to_run}' for #{ENV['FOODCOOP']}"
Rake::Task[task_to_run].execute Rake::Task[task_to_run].execute

3
lib/tasks/rspec.rake Normal file
View File

@ -0,0 +1,3 @@
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task :default => :spec

View File

@ -55,12 +55,16 @@ fi
sed -i "s|^\\(\\s*gem\\s\\+'sqlite3'\\)|#\1|" Gemfile sed -i "s|^\\(\\s*gem\\s\\+'sqlite3'\\)|#\1|" Gemfile
sed -i "s|^\\(\\s*sqlite3\\b\)|#\1|" Gemfile.lock sed -i "s|^\\(\\s*sqlite3\\b\)|#\1|" Gemfile.lock
# make sure postgresql db is present, as it is the default heroku db # make sure postgresql db is present, as it is the default heroku db
echo $'\ngem "pg"' >>Gemfile echo "
echo $'\ngem "localeapp"' >>Gemfile gem 'pg'" >>Gemfile
# always use unicorn # always use unicorn
echo $'\ngem "unicorn"' >>Gemfile echo "
gem 'unicorn'" >>Gemfile
echo 'web: bundle exec unicorn -p $PORT -E $RACK_ENV' >Procfile echo 'web: bundle exec unicorn -p $PORT -E $RACK_ENV' >Procfile
bundle install --quiet # to update Gemfile.lock # don't complain when mail cannot be sent,
# XXX when you're hosting a production instance, use a real smtp server instead
sed -i 's|\(#\s*\)\?\(config\.action_mailer\.raise_delivery_errors\)\s*=.*|\2 = false|' config/environments/${RAILS_ENV}.rb
sed -i 's|\(#\s*\)\?\(config\.action_mailer\.delivery_method\)\s*=.*|\2 = :smtp|' config/environments/${RAILS_ENV}.rb
# do not ignore deployment files # do not ignore deployment files
sed -i 's|^\(config/\*.yml\)|#\1|' .gitignore sed -i 's|^\(config/\*.yml\)|#\1|' .gitignore
sed -i 's|^\(config/initializers/secret_token.rb\)|#\1|' .gitignore sed -i 's|^\(config/initializers/secret_token.rb\)|#\1|' .gitignore
@ -92,9 +96,13 @@ Localeapp.configure do |config|
config.polling_environments = ['$RAILS_ENV'] config.polling_environments = ['$RAILS_ENV']
end end
EOF EOF
echo "
gem 'localeapp'" >>Gemfile
# also do not cache so we get locale updates # also do not cache so we get locale updates
sed -i 's|config\.cache_classes\s*=.*|config.cache_classes = false|' config/environments/${RAILS_ENV}.rb sed -i 's|\(#\s*\)\?\(config\.cache_classes\)\s*=.*|\2 = false|' config/environments/${RAILS_ENV}.rb
fi fi
# update Gemfile.lock after Gemfile updates (required by heroku)
bundle install --quiet
# TODO add more extensive database seed # TODO add more extensive database seed
# and push = deploy # and push = deploy

20
spec/factories/article.rb Normal file
View File

@ -0,0 +1,20 @@
require 'factory_girl'
FactoryGirl.define do
factory :article do
sequence(:name) { |n| Faker::Lorem.words(rand(2..4)).join(' ') + " ##{n}" }
unit { Faker::Unit.unit }
price { rand(2600) / 100 }
tax { [6, 21].sample }
deposit { rand(10) < 8 ? 0 : [0.0, 0.80, 1.20, 12.00].sample }
unit_quantity { rand(5) < 3 ? 1 : rand(1..20) }
#supplier_id
article_category { FactoryGirl.create :article_category }
end
factory :article_category do
sequence(:name) { |n| Faker::Lorem.characters(rand(2..12)) + " ##{n}" }
end
end

View File

@ -0,0 +1,10 @@
require 'factory_girl'
FactoryGirl.define do
# requires order
factory :group_order do
ordergroup { FactoryGirl.create(:user, groups: [FactoryGirl.create(:ordergroup)]).ordergroup }
end
end

View File

@ -0,0 +1,9 @@
require 'factory_girl'
FactoryGirl.define do
# requires order_article
factory :group_order_article do
end
end

31
spec/factories/order.rb Normal file
View File

@ -0,0 +1,31 @@
require 'factory_girl'
FactoryGirl.define do
factory :order do
starts { Time.now }
supplier { FactoryGirl.create :supplier, article_count: (article_count.nil? ? true : article_count) }
article_ids { supplier.articles.map(&:id) unless supplier.nil? }
ignore do
article_count true
end
# for an order from stock; need to add articles
factory :stock_order do
supplier_id 0
# article_ids needs to be supplied
end
# In the order's after_save callback order articles are created, so
# until the order is saved, these articles do not yet exist.
after :create do |order|
order.reload
end
end
# requires order and article
factory :order_article do
end
end

View File

@ -0,0 +1,21 @@
require 'factory_girl'
FactoryGirl.define do
factory :supplier do
name { Faker::Company.name.truncate(30) }
phone { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
ignore do
article_count 0
end
after :create do |supplier, evaluator|
article_count = evaluator.article_count
article_count = rand(1..99) if article_count == true
FactoryGirl.create_list :article, article_count, supplier: supplier
end
end
end

36
spec/factories/user.rb Normal file
View File

@ -0,0 +1,36 @@
require 'factory_girl'
FactoryGirl.define do
factory :user do
sequence(:nick) { |n| "user#{n}"}
first_name { Faker::Name.first_name }
email { Faker::Internet.email }
password { new_random_password }
factory :admin do
sequence(:nick) { |n| "admin#{n}" }
first_name 'Administrator'
after :create do |user, evaluator|
FactoryGirl.create :workgroup, role_admin: true, user_ids: [user.id]
end
end
end
factory :group do
sequence(:name) {|n| "Group ##{n}"}
factory :workgroup do
type ''
end
factory :ordergroup do
type 'Ordergroup'
sequence(:name) {|n| "Order group ##{n}"}
# workaround to avoid needing to save the ordergroup
# avoids e.g. error after logging in related to applebar
after :create do |group| Ordergroup.find(group.id).update_stats! end
end
end
end

12
spec/i18n_spec.rb Normal file
View File

@ -0,0 +1,12 @@
require 'spec_helper'
require 'i18n-spec'
Dir.glob('config/locales/*.yml').each do |locale_file|
describe "#{locale_file}" do
it_behaves_like 'a valid locale file', locale_file
# We're currently allowing both German and English as source language
# besides, we're using localeapp, so that it's ok if pull requests
# don't have this - a localapp pull will fix that right away.
#it { expect(locale_file).to be_a_subset_of 'config/locales/en.yml' }
end
end

View File

@ -0,0 +1,55 @@
require 'spec_helper'
describe 'settling an order', :type => :feature do
let(:admin) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_finance: true)] }
let(:supplier) { FactoryGirl.create :supplier }
let(:article) { FactoryGirl.create :article, supplier: supplier, unit_quantity: 1 }
let(:order) { FactoryGirl.create :order, supplier: supplier, article_ids: [article.id] } # need to ref article
let(:go1) { FactoryGirl.create :group_order, order: order }
let(:go2) { FactoryGirl.create :group_order, order: order }
let(:oa) { order.order_articles.find_by_article_id(article.id) }
let(:goa1) { FactoryGirl.create :group_order_article, group_order: go1, order_article: oa }
let(:goa2) { FactoryGirl.create :group_order_article, group_order: go2, order_article: oa }
before do
goa1.update_quantities(3, 0)
goa2.update_quantities(1, 0)
oa.update_results!
order.finish!(admin)
goa1.reload
goa2.reload
end
it 'has correct order result' do
expect(oa.quantity).to eq(4)
expect(oa.tolerance).to eq(0)
expect(goa1.result).to eq(3)
expect(goa2.result).to eq(1)
end
describe :type => :feature, :js => true do
before { login admin }
before { visit new_finance_order_path(order_id: order.id) }
it 'has product ordered visible' do
expect(page).to have_content(article.name)
expect(page).to have_selector("#order_article_#{oa.id}")
end
it 'shows order result' do
click_link article.name
expect(page).to have_selector("#group_order_articles_#{oa.id}")
within("#group_order_articles_#{oa.id}") do
# make sure these ordergroup names are in the list for this product
expect(page).to have_content(go1.ordergroup.name)
expect(page).to have_content(go2.ordergroup.name)
# and that their order results match what we expect
expect(page).to have_selector("#group_order_article_#{goa1.id}_quantity")
expect(find("#group_order_article_#{goa1.id}_quantity").text.to_f).to eq(3)
expect(page).to have_selector("#group_order_article_#{goa2.id}_quantity")
expect(find("#group_order_article_#{goa2.id}_quantity").text.to_f).to eq(1)
end
end
end
end

View File

@ -0,0 +1,57 @@
require 'spec_helper'
describe 'product distribution', :type => :feature do
let(:admin) { FactoryGirl.create :admin }
let(:user_a) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] }
let(:user_b) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] }
let(:supplier) { FactoryGirl.create :supplier }
let(:article) { FactoryGirl.create :article, supplier: supplier, unit_quantity: 5 }
let(:order) { FactoryGirl.create(:order, supplier: supplier, article_ids: [article.id]) }
let(:oa) { order.order_articles.first }
describe :type => :feature do
# make sure users have enough money to order
before do
[user_a, user_b].each do |user|
ordergroup = Ordergroup.find(user.ordergroup.id)
ordergroup.add_financial_transaction! 5000, 'for ordering', admin
end
end
it 'agrees to documented example', :js => true do
# gruppe a bestellt 2(3), weil sie auf jeden fall was von x bekommen will
login user_a
visit new_group_order_path(:order_id => order.id)
2.times { find("[data-increase_quantity='#{oa.id}']").click }
3.times { find("[data-increase_tolerance='#{oa.id}']").click }
find('input[type=submit]').click
expect(page).to have_selector('body')
# gruppe b bestellt 2(0)
login user_b
visit new_group_order_path(:order_id => order.id)
2.times { find("[data-increase_quantity='#{oa.id}']").click }
find('input[type=submit]').click
expect(page).to have_selector('body')
# gruppe a faellt ein dass sie doch noch mehr braucht von x und aendert auf 4(1).
login user_a
visit edit_group_order_path(order.group_order(user_a.ordergroup), :order_id => order.id)
2.times { find("[data-increase_quantity='#{oa.id}']").click }
2.times { find("[data-decrease_tolerance='#{oa.id}']").click }
find('input[type=submit]').click
expect(page).to have_selector('body')
# die zuteilung
order.finish!(admin)
oa.reload
# Endstand: insg. Bestellt wurden 6(1)
expect(oa.quantity).to eq(6)
expect(oa.tolerance).to eq(1)
# Gruppe a bekommt 3 einheiten.
goa_a = oa.group_order_articles.joins(:group_order).where(:group_orders => {:ordergroup_id => user_a.ordergroup.id}).first
expect(goa_a.result).to eq(3)
# gruppe b bekommt 2 einheiten.
goa_b = oa.group_order_articles.joins(:group_order).where(:group_orders => {:ordergroup_id => user_b.ordergroup.id}).first
expect(goa_b.result).to eq(2)
end
end
end

View File

@ -0,0 +1,21 @@
require 'spec_helper'
describe 'the session', :type => :feature do
let(:user) { FactoryGirl.create :user }
describe 'login page', :type => :feature do
it 'is accesible' do
get login_path
expect(response).to be_success
end
it 'logs me in' do
login user
expect(page).to_not have_selector('.alert-error')
end
it 'does not log me in with wrong password' do
login user.nick, 'XX'+user.password
expect(page).to have_selector('.alert-error')
end
end
end

View File

@ -0,0 +1,63 @@
require 'spec_helper'
describe 'supplier', :type => :feature do
let(:supplier) { FactoryGirl.create :supplier }
describe :type => :feature, :js => true do
let(:user) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_suppliers: true)] }
before { login user }
it 'can be created' do
visit suppliers_path
click_on I18n.t('suppliers.index.action_new')
supplier = FactoryGirl.build :supplier
within('#new_supplier') do
fill_in 'supplier_name', :with => supplier.name
fill_in 'supplier_address', :with => supplier.address
fill_in 'supplier_phone', :with => supplier.phone
find('input[type="submit"]').click
end
expect(page).to have_content(supplier.name)
end
it 'is included in supplier list' do
supplier
visit suppliers_path
expect(page).to have_content(supplier.name)
end
end
describe :type => :feature, :js => true do
let(:article_category) { FactoryGirl.create :article_category }
let(:user) { FactoryGirl.create :user, groups:[FactoryGirl.create(:workgroup, role_article_meta: true)] }
before { login user }
it 'can visit supplier articles path' do
visit supplier_articles_path(supplier)
expect(page).to have_content(supplier.name)
expect(page).to have_content(I18n.t('articles.index.edit_all'))
end
it 'can create a new article' do
article_category.save!
visit supplier_articles_path(supplier)
click_on I18n.t('articles.index.new')
expect(page).to have_selector('form#new_article')
article = FactoryGirl.build :article, supplier: supplier, article_category: article_category
within('#new_article') do
fill_in 'article_name', :with => article.name
fill_in 'article_unit', :with => article.unit
select article.article_category.name, :from => 'article_article_category_id'
fill_in 'article_price', :with => article.price
fill_in 'article_unit_quantity', :with => article.unit_quantity
fill_in 'article_tax', :with => article.tax
fill_in 'article_deposit', :with => article.deposit
# "Element cannot be scrolled into view" error, js as workaround
#find('input[type="submit"]').click
page.execute_script('$("form#new_article").submit();')
end
expect(page).to have_content(article.name)
end
end
end

View File

@ -0,0 +1,47 @@
require 'spec_helper'
describe Article do
let(:supplier) { FactoryGirl.create :supplier }
let(:article) { FactoryGirl.create :article, supplier: supplier }
it 'has a unique name' do
article2 = FactoryGirl.build :article, supplier: supplier, name: article.name
expect(article2).to be_invalid
end
it 'computes the gross price correctly' do
article.deposit = 0
article.tax = 12
expect(article.gross_price).to eq((article.price * 1.12).round(2))
article.deposit = 1.20
expect(article.gross_price).to eq(((article.price + 1.20) * 1.12).round(2))
end
it 'gross price >= net price' do
expect(article.gross_price).to be >= article.price
end
it 'fc-price >= gross price' do
if article.gross_price > 0
expect(article.fc_price).to be > article.gross_price
else
expect(article.fc_price).to be >= article.gross_price
end
end
it 'knows when it is deleted' do
expect(supplier.deleted?).to be_false
supplier.mark_as_deleted
expect(supplier.deleted?).to be_true
end
it 'keeps a price history' do
expect(article.article_prices.all.map(&:price)).to eq([article.price])
oldprice = article.price
sleep 1 # so that the new price really has a later creation time
article.price += 1
article.save!
expect(article.article_prices.all.map(&:price)).to eq([article.price, oldprice])
end
end

View File

@ -0,0 +1,48 @@
require 'spec_helper'
describe GroupOrderArticle do
let(:user) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] }
let(:order) { FactoryGirl.create(:order).reload }
let(:go) { FactoryGirl.create :group_order, order: order, ordergroup: user.ordergroup }
let(:goa) { FactoryGirl.create :group_order_article, group_order: go, order_article: order.order_articles.first }
it 'has zero quantity by default' do expect(goa.quantity).to eq(0) end
it 'has zero tolerance by default' do expect(goa.tolerance).to eq(0) end
it 'has zero result by default' do expect(goa.result).to eq(0) end
it 'is not ordered by default' do expect(GroupOrderArticle.ordered.where(:id => goa.id).exists?).to be_false end
it 'has zero total price by default' do expect(goa.total_price).to eq(0) end
describe do
let(:article) { FactoryGirl.create :article, supplier: order.supplier, unit_quantity: 1 }
let(:oa) { order.order_articles.create(:article => article) }
let(:goa) { FactoryGirl.create :group_order_article, group_order: go, order_article: oa }
it 'can be ordered by piece' do
goa.update_quantities(1, 0)
expect(goa.quantity).to eq(1)
expect(goa.tolerance).to eq(0)
end
it 'can be ordered in larger amounts' do
quantity, tolerance = rand(13..99), rand(0..99)
goa.update_quantities(quantity, tolerance)
expect(goa.quantity).to eq(quantity)
expect(goa.tolerance).to eq(tolerance)
end
it 'has a proper total price' do
quantity = rand(1..99)
goa.update_quantities(quantity, 0)
expect(goa.total_price).to eq(quantity * goa.order_article.price.fc_price)
end
it 'can unorder a product' do
goa.update_quantities(rand(1..99), rand(0..99))
goa.update_quantities(0, 0)
expect(goa.quantity).to eq(0)
expect(goa.tolerance).to eq(0)
end
end
end

View File

@ -0,0 +1,25 @@
require 'spec_helper'
describe GroupOrder do
let(:user) { FactoryGirl.create :user, groups: [FactoryGirl.create(:ordergroup)] }
let(:order) { FactoryGirl.create :order }
# the following two tests are currently disabled - https://github.com/foodcoops/foodsoft/issues/158
#it 'needs an order' do
# expect(FactoryGirl.build(:group_order, ordergroup: user.ordergroup)).to be_invalid
#end
#it 'needs an ordergroup' do
# expect(FactoryGirl.build(:group_order, order: order)).to be_invalid
#end
describe do
let(:go) { FactoryGirl.create :group_order, order: order, ordergroup: user.ordergroup }
it 'has zero price initially' do
expect(go.price).to eq(0)
end
end
end

47
spec/models/order_spec.rb Normal file
View File

@ -0,0 +1,47 @@
require 'spec_helper'
describe Order do
it 'needs a supplier' do
expect(FactoryGirl.build(:order, supplier: nil)).to be_invalid
end
it 'needs order articles' do
supplier = FactoryGirl.create :supplier, article_count: 0
expect(FactoryGirl.build(:order, supplier: supplier)).to be_invalid
end
it 'can be created' do
expect(FactoryGirl.build(:order, article_count: 1)).to be_valid
end
describe 'with articles' do
let(:order) { FactoryGirl.create :order }
it 'is open by default' do expect(order).to be_open end
it 'is not finished by default' do expect(order).to_not be_finished end
it 'is not closed by default' do expect(order).to_not be_closed end
it 'has valid order articles' do
order.order_articles.all.each {|oa| expect(oa).to be_valid }
end
it 'can be finished' do
# TODO randomise user
order.finish!(User.first)
expect(order).to_not be_open
expect(order).to be_finished
expect(order).to_not be_closed
end
it 'can be closed' do
# TODO randomise user
order.finish!(User.first)
order.close!(User.first)
expect(order).to_not be_open
expect(order).to be_closed
end
end
end

View File

@ -0,0 +1,16 @@
require 'spec_helper'
describe Supplier do
let(:supplier) { FactoryGirl.create :supplier }
it 'has a unique name' do
supplier2 = FactoryGirl.build :supplier, name: supplier.name
expect(supplier2).to be_invalid
end
it 'has valid articles' do
supplier = FactoryGirl.create :supplier, article_count: true
supplier.articles.all.each {|a| expect(a).to be_valid }
end
end

59
spec/models/user_spec.rb Normal file
View File

@ -0,0 +1,59 @@
require 'spec_helper'
describe User do
it 'is correctly created' do
user = FactoryGirl.create :user,
nick: 'johnnydoe', first_name: 'Johnny', last_name: 'DoeBar',
email: 'johnnydoe@foodcoop.test', phone: '+1234567890'
expect(user.nick).to eq('johnnydoe')
expect(user.first_name).to eq('Johnny')
expect(user.last_name).to eq('DoeBar')
expect(user.name).to eq('Johnny DoeBar')
expect(user.email).to eq('johnnydoe@foodcoop.test')
expect(user.phone).to eq('+1234567890')
end
describe 'does not have the role' do
let(:user) { FactoryGirl.create :user }
it 'admin' do expect(user.role_admin?).to be_false end
it 'finance' do expect(user.role_finance?).to be_false end
it 'article_meta' do expect(user.role_article_meta?).to be_false end
it 'suppliers' do expect(user.role_suppliers?).to be_false end
it 'orders' do expect(user.role_orders?).to be_false end
end
describe do
let(:user) { FactoryGirl.create :user, password: 'blahblah' }
it 'can authenticate with correct password' do
expect(User.authenticate(user.nick, 'blahblah')).to be_true
end
it 'can not authenticate with incorrect password' do
expect(User.authenticate(user.nick, 'foobar')).to be_nil
end
it 'can not set a password without matching confirmation' do
user.password = 'abcdefghij'
user.password_confirmation = 'foobarxyz'
expect(user).to be_invalid
end
it 'can set a password with matching confirmation' do
user.password = 'abcdefghij'
user.password_confirmation = 'abcdefghij'
expect(user).to be_valid
end
it 'has a unique nick' do
expect(FactoryGirl.build(:user, nick: user.nick, email: "x-#{user.email}")).to be_invalid
end
it 'has a unique email' do
expect(FactoryGirl.build(:user, email: "#{user.email}")).to be_invalid
end
end
describe 'admin' do
let(:user) { FactoryGirl.create :admin }
it 'default admin role' do expect(user.role_admin?).to be_true end
end
end

67
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,67 @@
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'support/coverage' # needs to be first
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rails'
require 'capybara/rspec'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
#config.use_transactional_fixtures = true
# We use capybara with selenium, and need database_cleaner
config.before(:each) do
DatabaseCleaner.strategy = (example.metadata[:js] ? :truncation : :transaction)
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
config.include SessionHelper
end
module Faker
class Unit
class << self
def unit
['kg', '1L', '100ml', 'piece', 'bunch', '500g'].sample
end
end
end
end
# include default foodsoft scope in urls, so that *_path works
ActionDispatch::Integration::Runner.class_eval do
undef default_url_options
def default_url_options(options={})
{foodcoop: FoodsoftConfig.scope}.merge(options)
end
end

14
spec/support/coverage.rb Normal file
View File

@ -0,0 +1,14 @@
# optional test coverage
# needs to be loaded first, e.g. add a require at top of spec_helper
if ENV['COVERAGE']
require 'simplecov'
SimpleCov.start do
add_filter '/spec/'
add_filter '/test/'
add_group 'Models', '/app/models/'
add_group 'Controllers', '/app/controllers/'
add_group 'Helpers', '/app/helpers/'
add_group 'Documents', '/app/documents/'
add_group 'Libraries', '/lib/'
end
end

View File

@ -0,0 +1,17 @@
module SessionHelper
def login(user=nil, password=nil)
visit login_path
user = FactoryGirl.create :user if user.nil?
if user.instance_of? ::User
nick, password = user.nick, user.password
else
nick = user
end
fill_in 'nick', :with => nick
fill_in 'password', :with => password
find('input[type=submit]').click
end
end