Merge branch 'master' of github.com:foodcoops/foodsoft

This commit is contained in:
Benjamin Meichsner 2013-09-18 15:19:04 +02:00
commit c597d70596
42 changed files with 2825 additions and 73 deletions

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

26
Gemfile
View file

@ -54,10 +54,6 @@ group :development do
gem 'binding_of_caller'
# gem "rails-i18n-debug"
# Re-enable rails benchmarker/profiler
gem 'ruby-prof'
gem 'test-unit'
# Get infos when not using proper eager loading
gem 'bullet'
@ -70,4 +66,24 @@ group :development do
#gem 'common_deploy', require: false, path: '../../common_deploy' # pending foodcoops/foodsoft#34, git: 'git://github.com/fsmanuel/common_deploy.git'
# Avoid having content-length warnings
gem 'thin'
end
end
group :development, :test do
gem 'ruby-prof'
end
group :test do
gem 'rspec-rails'
gem 'factory_girl_rails', '~> 4.0'
gem 'faker'
# version requirements to avoid problem http://stackoverflow.com/questions/18114544
gem 'capybara', '~> 2.1.0'
# webkit and poltergeist don't seem to work yet
gem 'selenium-webdriver', '~> 2.35.1'
gem 'database_cleaner'
gem 'simplecov', require: false
# need to include rspec components before i18n-spec or rake fails in test environment
gem 'rspec-core'
gem 'rspec-expectations'
gem 'i18n-spec'
end

View file

@ -62,6 +62,14 @@ GEM
net-ssh-gateway (>= 1.1.0)
capistrano-ext (1.2.1)
capistrano (>= 1.0.0)
capybara (2.1.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
chronic (0.9.0)
client_side_validations (3.1.4)
coderay (1.0.8)
@ -74,6 +82,8 @@ GEM
coffee-script-source (1.3.3)
commonjs (0.2.6)
daemons (1.1.9)
database_cleaner (0.7.1)
diff-lcs (1.2.4)
erubis (2.7.0)
eventmachine (1.0.3)
exception_notification (2.6.1)
@ -81,6 +91,14 @@ GEM
execjs (1.4.0)
multi_json (~> 1.0)
expression_parser (0.9.0)
factory_girl (4.2.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.2.1)
factory_girl (~> 4.2.0)
railties (>= 3.0.0)
faker (1.1.2)
i18n (~> 0.5)
ffi (1.9.0)
haml (3.1.7)
haml-rails (0.3.5)
actionpack (>= 3.1, < 4.1)
@ -92,9 +110,13 @@ GEM
highline (1.6.19)
hike (1.2.3)
i18n (0.6.1)
i18n-spec (0.4.0)
iso
inherited_resources (1.3.1)
has_scope (~> 0.5.0)
responders (~> 0.6)
iso (0.2.0)
i18n
journey (1.0.4)
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
@ -128,8 +150,9 @@ GEM
activesupport (~> 3.1)
polyamorous (~> 0.5.0)
mime-types (1.21)
mini_portile (0.5.1)
mono_logger (1.1.0)
multi_json (1.7.6)
multi_json (1.7.9)
mysql2 (0.3.11)
net-scp (1.1.1)
net-ssh (>= 2.6.5)
@ -138,6 +161,8 @@ GEM
net-ssh (2.6.7)
net-ssh-gateway (1.2.0)
net-ssh (>= 2.6.5)
nokogiri (1.6.0)
mini_portile (~> 0.5.0)
pdf-reader (1.2.0)
Ascii85 (~> 1.0.0)
hashery (~> 2.0)
@ -180,7 +205,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redis (3.0.4)
redis-namespace (1.3.0)
redis-namespace (1.3.1)
redis (~> 3.0.0)
responders (0.9.3)
railties (~> 3.1)
@ -190,8 +215,20 @@ GEM
redis-namespace (~> 1.2)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
ruby-prof (0.11.2)
rspec-core (2.14.2)
rspec-expectations (2.14.0)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.1)
rspec-rails (2.14.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-prof (0.13.0)
ruby-rc4 (0.1.5)
rubyzip (0.9.9)
sass (3.2.1)
sass-rails (3.2.5)
railties (~> 3.2.0)
@ -200,6 +237,11 @@ GEM
select2-rails (3.4.2)
sass-rails
thor (~> 0.14)
selenium-webdriver (2.35.1)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip (< 1.0.0)
websocket (~> 1.0.4)
simple-navigation (3.9.0)
activesupport (>= 2.3.2)
simple-navigation-bootstrap (0.0.4)
@ -207,6 +249,10 @@ GEM
simple_form (2.1.0)
actionpack (~> 3.0)
activemodel (~> 3.0)
simplecov (0.7.1)
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
sinatra (1.3.6)
rack (~> 1.4)
rack-protection (~> 1.3)
@ -220,7 +266,6 @@ GEM
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.6)
test-unit (2.5.3)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thin (1.5.1)
@ -245,12 +290,15 @@ GEM
uniform_notifier (1.1.1)
vegas (0.1.11)
rack (>= 1.0.0)
websocket (1.0.7)
whenever (0.8.1)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
wikicloth (0.8.0)
builder
expression_parser
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
@ -264,11 +312,16 @@ DEPENDENCIES
bullet
capistrano (= 2.13.5)
capistrano-ext
capybara (~> 2.1.0)
client_side_validations
coffee-rails (~> 3.2.1)
daemons
database_cleaner
exception_notification
factory_girl_rails (~> 4.0)
faker
haml-rails
i18n-spec
inherited_resources
jquery-rails
kaminari
@ -281,14 +334,18 @@ DEPENDENCIES
rails (~> 3.2.9)
rails-settings-cached (= 0.2.4)
resque
rspec-core
rspec-expectations
rspec-rails
ruby-prof
sass-rails (~> 3.2.3)
select2-rails
selenium-webdriver (~> 2.35.1)
simple-navigation
simple-navigation-bootstrap
simple_form
simplecov
sqlite3
test-unit
therubyracer
thin
twitter-bootstrap-rails

View file

@ -1,5 +1,6 @@
FoodSoft
=========
[![Build Status](https://travis-ci.org/foodcoops/foodsoft.png?branch=tests-rspec)](https://travis-ci.org/foodcoops/foodsoft)
[![Code Climate](https://codeclimate.com/github/foodcoops/foodsoft.png)](https://codeclimate.com/github/foodcoops/foodsoft)
[![Dependency Status](https://gemnasium.com/foodcoops/foodsoft.png)](https://gemnasium.com/foodcoops/foodsoft)

View file

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

View file

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

View file

@ -3,7 +3,7 @@ class Workgroup < Group
has_many :tasks
# returns all non-finished tasks
has_many :open_tasks, :class_name => 'Task', :conditions => ['done = ?', false], :order => 'due_date ASC'
has_many :open_tasks, :class_name => 'Task', :conditions => ['done = ?', false], order: 'due_date ASC, name ASC'
validates_uniqueness_of :name
validate :last_admin_on_earth, :on => :update

View file

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

View file

@ -5,7 +5,7 @@
%thead
%tr
%th= sort_link_helper t('.name'), "name", :per_page => @per_page
%th Kontakt
%th= t '.contact'
%th.numeric= sort_link_helper t('.account_balance'), "account_balance", :per_page => @per_page
%th
%tbody
@ -17,4 +17,4 @@
%td
= 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'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -493,7 +493,7 @@ en:
- Article
- Amount
- Price
- Unit Quantity
- Unit quantity
- Unit
- Sum
sum: Sum
@ -504,20 +504,24 @@ en:
- Order Number
- Amount
- Name
- Barrel
- Unit quantity
- Unit
- Price/Unit
- Subtotal
total: Total
order_matrix:
filename: Order %{name}-%{date} - sorting matrix
heading: Article overview
rows:
- Article
- Unit
- Barrel
- Unit quantity
- FC-Price
- Amount
title: ! 'Order sorting matrix: %{name}, closed at %{date}'
total: ! '%{count} articles in total'
total:
one: One article in total
other: ! '%{count} articles in total'
errors:
format: ! '%{attribute} %{message}'
general: A problem has occured.
@ -721,6 +725,7 @@ en:
ordergroups:
account_balance: Account balance
account_statement: Account statement
contact: Contact
name: Name
new_transaction: New transaction
update:
@ -1604,6 +1609,7 @@ en:
contact_person: Contact person
contact_phone: Phone
ignore_apple_restriction: Ignore order stop by apple points restriction
name: Name
page:
body: Body
parent_id: Parent page
@ -1671,6 +1677,7 @@ en:
language:
de: German
en: English
fr: French
nl: Dutch
required:
mark: ! '*'
@ -1744,6 +1751,7 @@ en:
show_stock_takings: Inventory overview
stock_count: ! 'Number of articles:'
stock_worth: ! 'Current stock value:'
title: Stock (%{article_count})
toggle_unavailable: Show/hide unavailable articles
view_options: View options
new:
@ -1835,6 +1843,7 @@ en:
group_tasks: Group tasks
my_tasks: My tasks
new_task: Create new task
pages: Pages
new:
title: Create new tasks
repeated: Task is repeated weekly

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

View file

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

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*sqlite3\\b\)|#\1|" Gemfile.lock
# make sure postgresql db is present, as it is the default heroku db
echo $'\ngem "pg"' >>Gemfile
echo $'\ngem "localeapp"' >>Gemfile
echo "
gem 'pg'" >>Gemfile
# always use unicorn
echo $'\ngem "unicorn"' >>Gemfile
echo "
gem 'unicorn'" >>Gemfile
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
sed -i 's|^\(config/\*.yml\)|#\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']
end
EOF
echo "
gem 'localeapp'" >>Gemfile
# 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
# update Gemfile.lock after Gemfile updates (required by heroku)
bundle install --quiet
# TODO add more extensive database seed
# 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