Merge pull request #72 from ZeusWPI/barcode

Barcode
This commit is contained in:
benji 2015-10-07 14:55:11 +02:00
commit 88096fc46a
73 changed files with 752 additions and 431 deletions

BIN
app/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -15,3 +15,12 @@
//= require bootstrap //= require bootstrap
//= require turbolinks //= require turbolinks
//= require_tree . //= require_tree .
parseIntNaN = function(value) {
parsed_value = parseInt(value, 10);
if (isNaN(parsed_value)) {
return 0;
} else {
return parsed_value;
}
}

View file

@ -0,0 +1,12 @@
ready = function() {
increment_function = function() {
target = $(this).data("target");
$(target).val(parseIntNaN($(target).data("default")) + parseIntNaN($(this).val()));
}
$('[data-increment]').change(increment_function);
$('[data-increment]').keyup(increment_function);
}
$(document).ready(ready);
$(document).on('page:load', ready);

View file

@ -2,64 +2,75 @@
// All this logic will automatically be available in application.js. // All this logic will automatically be available in application.js.
// You can use CoffeeScript in this file: http://coffeescript.org/ // You can use CoffeeScript in this file: http://coffeescript.org/
ready = function() { ready = function() {
$('.btn-inc').on('click', function() { /* INITIALIZE */
increment($(this), 1); $('tr.order_item_wrapper').hide();
$('tr.order_item_wrapper').filter(function() {
return parseIntNaN($(this).find('[type=number]').val()) > 0;
}).show();
/* HELPERS */
increment_product = function(product_id) {
input = $("#current_order").find(".order_item_wrapper[data-product=" + product_id + "]").find("input[type=number]");
$(input).val(parseIntNaN($(input).val()) + 1).change();
}
recalculate = function() {
/* Total Price */
array = $('tr.order_item_wrapper').map(function() {
return parseIntNaN($(this).data("price")) * parseIntNaN($(this).find("input[type=number]").val());
})
sum = 0;
array.each(function(i, el) { sum += el; });
$("#current_order .total_price").html((sum / 100.0).toFixed(2));
/* Message when no product has been choosen */
$("#current_order #empty").toggle(!($('tr.order_item_wrapper input[type=number]').filter(function() {
return parseIntNaN($(this).val()) > 0;
}).length));
}
/* PRODUCT MODAL */
products_ordered = $('#product_search').keyup(function () {
var rex = new RegExp($(this).val(), 'i');
$('[data-name]').hide();
$('[data-name]').filter(function () {
return rex.test($(this).data("name"));
}).show();
})
$('#products_modal').on('hidden.bs.modal', function () {
$('#product_search').val('');
}); });
$('.btn-dec').on('click', function() {
increment($(this), -1); $("#products_modal button").click(function() {
}); increment_product($(this).data("product"))
$('.form_row').each(function(index, row) { })
updateInput(row, false);
$(row).on('input', function() { /* BARCODE SCAN */
updateInput(row); $("#from_barcode_form").submit(function(event) {
event.preventDefault();
barcode = $(this).find("input[type=number]").val();
$.ajax({
url: "/barcodes/" + barcode,
success: function(data) {
increment_product(data["id"]);
$("#from_barcode_form")[0].reset();
},
dataMethod: "json"
}).fail(function() {
alert("Barcode '" + barcode + "' was not found in the database system.");
}); });
}); });
/* CURRENT ORDER CHANGE */
$('tr.order_item_wrapper input[type=number]').change(function() {
tr = $(this).closest('tr.order_item_wrapper')
$(tr).toggle(parseIntNaN($(this).val()) > 0);
$(tr).find("td").last().html(((parseIntNaN($(tr).data("price")) * parseIntNaN($(this).val())) / 100.0).toFixed(2))
recalculate();
})
recalculate(); recalculate();
};
// Validate input, and then update
updateInput = function(row, useRecalculate) {
if (useRecalculate == null) {
useRecalculate = true;
}
cell = row.querySelector("input");
if (!cell.validity.valid) {
if (parseInt(cell.value) > parseInt(cell.max)) {
cell.value = parseInt(cell.max);
} else {
cell.value = 0;
}
}
disIfNec(row)
if (useRecalculate) {
recalculate()
}
};
disIfNec = function(row) {
counter = parseInt($(row).find('.row_counter').val())
$(row).find('.btn-dec').prop('disabled', counter === 0)
$(row).find('.btn-inc').prop('disabled', counter === parseInt($(row).find('.row_counter').attr('max')))
};
recalculate = function() {
sum = 0
$('.row_counter').each(function(i, value) { sum += parseInt($(value).val()) * parseInt($(value).data('price')) })
return $('#order_price').html((sum / 100.0).toFixed(2))
};
increment = function(button, n) {
row = $(button).closest('.form_row')
// Fix the counter
counter = $(row).find('.row_counter')
value = parseInt(counter.val())
if (isNaN(value)) {
value = 0
}
counter.val(value + n)
updateInput(row[0])
} }
$(document).ready(ready); $(document).ready(ready);

View file

@ -1,3 +0,0 @@
// Place all the styles related to the admins controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -1,3 +0,0 @@
// Place all the styles related to the callbacks controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -2,60 +2,81 @@
// They will automatically be included in application.css. // They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/ // You can use Sass (SCSS) here: http://sass-lang.com/
.big-form-button { .barcode-wrapper {
height: 65px; font-size: 300%;
width: 65px; input {
} width: 100%;
.form-control.big-form-field {
height: 65px;
text-align: center;
}
.form_row_image {
height: 100px;
width: 100px;
margin-left: auto;
margin-right: auto;
position: relative;
img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
top: 0;
margin: auto;
} }
} }
.form_row input::-webkit-outer-spin-button, #products_modal {
.form_row input::-webkit-inner-spin-button { .modal-header {
-webkit-appearance: none; h4 {
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ display: inline-block;
}
input {
margin-right: 20px;
}
}
} }
.form_row .btn-lg { #product_buttons {
padding: 10px 10px; .col-md-2, .col-md-3 {
} margin-bottom: 20px;
}
.form_row .caption { button.product {
h4 { width: 100%;
position: relative; p {
span {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 100%;
display: inline-block;
padding-right: 50px;
} }
small { img {
margin-left: -45px; max-width: 100%;
position: absolute; margin-left: auto;
top: 5px; margin-right: auto;
display: block;
} }
} }
} }
#order_price {
width: 50px; #current_order {
text-align: left;
border: 1px solid;
padding: 10px;
div.center {
margin-bottom: 20px;
}
.order_item_wrapper {
input {
border: none;
width: auto;
}
}
table {
border-top: 1px dashed;
border-collapse: separate;
margin-bottom: 15px;
td:first-child {
width: 40px;
input {
text-align: right;
width: 40px;
}
}
tr:last-child td {
border-top: 1px dashed;
padding-top: 10px;
margin-top: 10px;
}
tr.margin {
height: 10px;
}
td.euro {
text-align: right;
&::before {
content: "";
}
}
}
} }

View file

@ -1,3 +0,0 @@
// Place all the styles related to the sessions controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -1,3 +0,0 @@
// Place all the styles related to the stock controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -0,0 +1,22 @@
#stock_entry {
border: 1px solid #ccc;
background-color: #F5F5F5;
padding: 20px;
border-radius: 8px;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
table {
margin-bottom: 20px;
border-spacing: 10px;
border-collapse: separate;
tr:last-child td {
border-top: 1px dashed;
padding-top: 10px;
}
td {
/* padding: 10px; */
}
}
}

View file

@ -4,12 +4,12 @@
//signin //signin
.sign-in{ .sign-in {
.checkbox label{ .checkbox label {
padding-left: 0px; padding-left: 0px;
} }
#user_remember_me{ #user_remember_me {
margin-left: 10px; margin-left: 10px;
} }
} }
@ -17,7 +17,7 @@
padding: 0px; padding: 0px;
min-height: 280px; min-height: 280px;
border: 3px solid #333; border: 3px solid #333;
.header{ .header {
border-bottom: 3px solid #333; border-bottom: 3px solid #333;
text-align: center; text-align: center;
color: #fff; color: #fff;
@ -26,19 +26,19 @@
margin: 0px; margin: 0px;
} }
.caption{ .caption {
.avatar{ .avatar {
float: right; float: right;
height: 70px; height: 70px;
width: 70px; width: 70px;
} }
} }
.footer{ .footer {
width:80%; width:80%;
border-top: 1px dashed #333; border-top: 1px dashed #333;
margin:10%; margin:10%;
margin-bottom: 5px; margin-bottom: 5px;
.btn{ .btn {
width:100%; width:100%;
margin-top:10px; margin-top:10px;
margin-bottom:0px; margin-bottom:0px;

View file

@ -1,3 +0,0 @@
// Place all the styles related to the welcome controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -1,6 +1,5 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
check_authorization
rescue_from CanCan::AccessDenied do |exception| rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, flash: { error: exception.message } redirect_to root_path, flash: { error: exception.message }

View file

@ -0,0 +1,18 @@
class BarcodesController < ApplicationController
load_and_authorize_resource :barcode, shallow: true
def create
@barcode.save
redirect_to barcode_products_path, notice: "Barcode successfully linked!"
end
def show
render json: @barcode.product
end
private
def barcode_params
params.require(:barcode).permit(:code)
end
end

View file

@ -1,6 +1,4 @@
class CallbacksController < Devise::OmniauthCallbacksController class CallbacksController < Devise::OmniauthCallbacksController
skip_authorization_check
def zeuswpi def zeuswpi
@user = User.from_omniauth(request.env["omniauth.auth"]) @user = User.from_omniauth(request.env["omniauth.auth"])
sign_in_and_redirect @user sign_in_and_redirect @user

View file

@ -3,10 +3,8 @@ class OrdersController < ApplicationController
load_and_authorize_resource :order, through: :user, shallow: true load_and_authorize_resource :order, through: :user, shallow: true
def new def new
products = (@user.products.for_sale.select("products.*", "sum(order_items.count) as count").group(:product_id).order("count desc") | Product.for_sale) @products = Product.all.for_sale.order(:name)
products.each do |p| @order.products << @products
@order.order_items.build(product: p)
end
end end
def create def create
@ -14,6 +12,7 @@ class OrdersController < ApplicationController
flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!" flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!"
redirect_to root_path redirect_to root_path
else else
@products = Product.all.for_sale.order(:name)
render 'new' render 'new'
end end
end end

View file

@ -3,15 +3,26 @@ class ProductsController < ApplicationController
respond_to :html, :js respond_to :html, :js
def new
end
def create def create
if @product.save if @product.save
flash[:success] = "Product created!" flash[:success] = "Product created!"
redirect_to products_path redirect_to barcode_products_path
else else
render 'new' render 'link'
end
end
def barcode
end
def load_barcode
@product = Barcode.find_by(code: params[:barcode]).try(:product)
if @product
render 'products/stock_entry'
else
@product = Product.new
@product.barcodes.build(code: params[:barcode])
render 'products/link'
end end
end end
@ -28,12 +39,15 @@ class ProductsController < ApplicationController
def update def update
@product.update_attributes product_params @product.update_attributes product_params
respond_with @product respond_to do |format|
format.js { respond_with @product }
format.html { redirect_to barcode_products_path, notice: "Stock has been updated!" }
end
end end
private private
def product_params def product_params
params.require(:product).permit(:name, :price, :avatar, :category, :stock, :calories, :deleted) params.require(:product).permit(:name, :price, :avatar, :category, :stock, :calories, :deleted, :barcode, barcodes_attributes: [:code])
end end
end end

View file

@ -1,3 +0,0 @@
class SessionsController < Devise::SessionsController
skip_authorization_check
end

View file

@ -1,19 +0,0 @@
class StocksController < ApplicationController
load_and_authorize_resource
def new
Product.all.each do |p|
@stock.stock_entries << Stock::StockEntry.new(product: p)
end
end
def create
@stock = Stock.new(params[:stock])
if @stock.update
flash[:success] = "Stock updated!"
redirect_to products_path
else
render 'new'
end
end
end

View file

@ -18,16 +18,6 @@ class UsersController < ApplicationController
end end
end end
def index
@users = User.members
end
def destroy
@user.destroy
flash[:success] = "Succesfully removed user"
redirect_to users_path
end
def edit_dagschotel def edit_dagschotel
@dagschotel = @user.dagschotel @dagschotel = @user.dagschotel
@ -35,14 +25,6 @@ class UsersController < ApplicationController
@categories = Product.categories @categories = Product.categories
end end
def update_dagschotel
@user.dagschotel = Product.find(params[:product_id])
@user.save
flash[:success] = "Succesfully updated dagschotel"
redirect_to @user
end
def quickpay def quickpay
order = @user.orders.build order = @user.orders.build
order.order_items.build(count: 1, product: @user.dagschotel) order.order_items.build(count: 1, product: @user.dagschotel)
@ -57,7 +39,7 @@ class UsersController < ApplicationController
private private
def user_params def user_params
params.require(:user).permit(:avatar, :private) params.require(:user).permit(:avatar, :private, :dagschotel_id)
end end
def init def init

View file

@ -1,6 +1,12 @@
class WelcomeController < ApplicationController class WelcomeController < ApplicationController
skip_authorization_check skip_before_filter :verify_authenticity_token, only: :token_sign_in
def index def index
end end
def token_sign_in
return head(:unauthorized) unless params[:token] == Rails.application.secrets.koelkast_token
koelkast = User.find_by(name: "koelkast")
sign_in_and_redirect koelkast
end
end end

View file

@ -37,7 +37,12 @@ class FormattedFormBuilder < ActionView::Helpers::FormBuilder
options[:value] = number_with_precision(options[:value], precision: 2) options[:value] = number_with_precision(options[:value], precision: 2)
form_group_builder(name, options) do form_group_builder(name, options) do
number_field_without_format(name, options) content_tag :div, class: "input-group" do
content_tag(:span, class: "input-group-addon") do
content_tag :span, nil, class: "glyphicon glyphicon-euro"
end +
number_field_without_format(name, options)
end
end end
end end

View file

@ -4,10 +4,14 @@ class Ability
def initialize(user) def initialize(user)
return unless user return unless user
can :from_barcode, Product
if user.admin? if user.admin?
can :manage, :all can :manage, :all
elsif user.koelkast? elsif user.koelkast?
can :manage, Order can :manage, Order do |order|
!order.try(:user).try(:private)
end
can :quickpay, User can :quickpay, User
else else
can :read, :all can :read, :all

20
app/models/barcode.rb Normal file
View file

@ -0,0 +1,20 @@
# == Schema Information
#
# Table name: barcodes
#
# id :integer not null, primary key
# product_id :integer
# code :string default(""), not null
# created_at :datetime
# updated_at :datetime
#
class Barcode < ActiveRecord::Base
include FriendlyId
friendly_id :code, use: :finders
belongs_to :product
# validates :product, presence: true
validates :code, presence: true, uniqueness: true
end

View file

@ -36,7 +36,7 @@ class Order < ActiveRecord::Base
private private
def calculate_price def calculate_price
self.price_cents = self.order_items.map{ |oi| oi.count * oi.product.price_cents }.sum self.price_cents = self.order_items.map{ |oi| oi.count * (oi.product.try(:price_cents) || 0) }.sum
end end
def create_api_job def create_api_job

View file

@ -21,10 +21,12 @@ class Product < ActiveRecord::Base
include Avatarable include Avatarable
has_many :order_items has_many :order_items
has_many :barcodes
accepts_nested_attributes_for :barcodes
enum category: %w(food beverages other) enum category: %w(food beverages other)
validates :name, presence: true validates :name, presence: true, uniqueness: true
validates :price_cents, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :price_cents, presence: true, numericality: { only_integer: true, greater_than: 0 }
validates :stock, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :stock, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :calories, numericality: { only_integer: true, allow_nil: true, greater_than_or_equal_to: 0 } validates :calories, numericality: { only_integer: true, allow_nil: true, greater_than_or_equal_to: 0 }
@ -39,8 +41,4 @@ class Product < ActiveRecord::Base
if value.is_a? String then value.sub!(',', '.') end if value.is_a? String then value.sub!(',', '.') end
self.price_cents = (value.to_f * 100).to_int self.price_cents = (value.to_f * 100).to_int
end end
def take_out_of_sale!
update_attribute :deleted, true
end
end end

View file

@ -6,11 +6,6 @@
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# remember_created_at :datetime # remember_created_at :datetime
# sign_in_count :integer default("0"), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string
# last_sign_in_ip :string
# admin :boolean # admin :boolean
# dagschotel_id :integer # dagschotel_id :integer
# avatar_file_name :string # avatar_file_name :string
@ -28,12 +23,14 @@ class User < ActiveRecord::Base
include Statistics, Avatarable, FriendlyId include Statistics, Avatarable, FriendlyId
friendly_id :name, use: :finders friendly_id :name, use: :finders
devise :database_authenticatable, :omniauthable, :omniauth_providers => [:zeuswpi] devise :omniauthable, :omniauth_providers => [:zeuswpi]
has_many :orders, -> { includes :products } has_many :orders, -> { includes :products }
has_many :products, through: :orders has_many :products, through: :orders
belongs_to :dagschotel, class_name: 'Product' belongs_to :dagschotel, class_name: 'Product'
validates :dagschotel, presence: true, if: -> { dagschotel_id }
scope :members, -> { where koelkast: false } scope :members, -> { where koelkast: false }
scope :publik, -> { where private: false } scope :publik, -> { where private: false }

View file

@ -1,26 +1,26 @@
#flash #flash
- if flash[:error] - if flash[:error]
.alert.alert-danger.alert-dismissable .alert.alert-danger.alert-dismissable
%button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} × %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", type: "button"} ×
%strong Error! %strong Error!
= flash[:error] = flash[:error]
- if flash[:success] - if flash[:success]
.alert.alert-success.alert-dismissable .alert.alert-success.alert-dismissable
%button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} × %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", type: "button"} ×
%strong Success! %strong Success!
= raw flash[:success] = raw flash[:success]
- if flash[:notice] - if flash[:notice]
.alert.alert-info.alert-dismissable .alert.alert-info.alert-dismissable
%button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} × %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", type: "button"} ×
%strong Notice! %strong Notice!
= flash[:notice] = flash[:notice]
- if flash[:warning] - if flash[:warning]
.alert.alert-warning.alert-dismissable .alert.alert-warning.alert-dismissable
%button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} × %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", type: "button"} ×
%strong Warning! %strong Warning!
= flash[:warning] = flash[:warning]
- if flash[:alert] - if flash[:alert]
.alert.alert-danger.alert-dismissable .alert.alert-danger.alert-dismissable
%button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} × %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", type: "button"} ×
%strong Error! %strong Error!
= flash[:alert] = flash[:alert]

View file

@ -1,10 +1,8 @@
%h2 Sign in = content_for :title, "Sign in"
= render partial: 'flash'
.sign-in .sign-in
= f_form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| = f_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
= f.text_field :name = f.text_field :name
= f.password_field :password = f.password_field :password
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
= f.check_box :remember_me = f.check_box :remember_me
= f.submit "Sign in" = f.submit "Sign in"
= render "devise/shared/links"

View file

@ -1,16 +0,0 @@
- unless controller_name == 'sessions'
= link_to "Log in", new_session_path(resource_name)
%br/
- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
= link_to "Forgot your password?", new_password_path(resource_name)
%br/
- if devise_mapping.confirmable? && controller_name != 'confirmations'
= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
%br/
- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name)
%br/
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), class: "btn btn-large btn-primary"
%br/

View file

@ -2,7 +2,7 @@
.container-fluid .container-fluid
/ Brand and toggle get grouped for better mobile display / Brand and toggle get grouped for better mobile display
.navbar-header .navbar-header
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} %button.navbar-toggle{data: { target: ".navbar-collapse", toggle: "collapse" } }
%span.icon-bar %span.icon-bar
%span.icon-bar %span.icon-bar
%span.icon-bar %span.icon-bar
@ -11,11 +11,7 @@
- unless current_user && current_user.koelkast? - unless current_user && current_user.koelkast?
.collapse.navbar-collapse .collapse.navbar-collapse
.hidden-xs.navbar-form.navbar-right .hidden-xs.navbar-form.navbar-right
.form-group = render 'layouts/session_button'
- if user_signed_in?
= link_to "Logout", destroy_user_session_path, class: "btn btn-default form-control"
- else
= link_to "Login", omniauth_authorize_path("user", "zeuswpi"), class: "btn btn-success form-control"
%ul.nav.navbar-nav.navbar-right %ul.nav.navbar-nav.navbar-right
%li= mail_to "tab@zeus.ugent.be", "Send feedback" %li= mail_to "tab@zeus.ugent.be", "Send feedback"
- if user_signed_in? - if user_signed_in?
@ -26,26 +22,15 @@
%span.caret %span.caret
%ul.dropdown-menu{role: "menu"} %ul.dropdown-menu{role: "menu"}
%li= link_to "List", products_path %li= link_to "List", products_path
%li= link_to "Add product" , new_product_path %li= link_to "Add product" , barcode_products_path
%li= link_to "Add stock", new_stock_path
%li.dropdown
%a.dropdown-toggle{"aria-expanded" => "false", "data-toggle" => "dropdown", href: "#", role: "button"}
Users
%span.caret
%ul.dropdown-menu{role: "menu"}
%li= link_to "List" , users_path
%li.dropdown %li.dropdown
%a.dropdown-toggle{"data-toggle" => "dropdown", href: "#"} %a.dropdown-toggle{"data-toggle" => "dropdown", href: "#"}
Logged in as #{current_user.name} Logged in as #{current_user.name}
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu
%li= link_to "Edit avatar", edit_user_path(current_user) %li= link_to "Edit profile", edit_user_path(current_user)
%li %li
%p.navbar-text %p.navbar-text
Balance: #{euro_from_cents(current_user.balance)} Balance: #{euro_from_cents(current_user.balance)}
.visible-xs.navbar-form .visible-xs.navbar-form
.form-group = render 'layouts/session_button'
- if user_signed_in?
= button_to "Logout", destroy_user_session_path, class: "btn btn-default form-control", method: :delete
- else
= link_to "Login", omniauth_authorize_path("user", "zeuswpi"), class: "btn btn-success form-control"

View file

@ -0,0 +1,5 @@
.form-group
- if user_signed_in?
= link_to "Logout", destroy_user_session_path, class: "btn btn-default form-control"
- else
= link_to "Login", omniauth_authorize_path("user", "zeuswpi"), class: "btn btn-success form-control"

View file

@ -9,6 +9,8 @@
%body %body
= render 'layouts/header' = render 'layouts/header'
.container .container
%h2= yield :title
= render partial: 'flash'
= yield = yield
= render 'layouts/footer' = render 'layouts/footer'
= debug(params) if Rails.env.development? = debug(params) if Rails.env.development?

View file

@ -1,5 +1,5 @@
.col-md-3.form_products .col-md-3.form_products
%div{class: "thumbnail#{' out-of-stock' if product.stock.zero?}"} %div.thumbnail{ class: ('out-of-stock' if product.stock.zero?) }
.form_row.center .form_row.center
.form_row_image .form_row_image
= image_tag product.avatar = image_tag product.avatar

View file

@ -1,7 +0,0 @@
.col-md-3.form_total
%strong Total price
.input-group
%span.input-group-addon €
= content_tag :span, "", id: "order_price", class: "input-group-addon"
%span.input-group-btn
= f.submit "Order!", class: "btn btn-primary big-form-button", skip_wrapper: true

View file

@ -0,0 +1,19 @@
#products_modal.modal{ tabindex: -1 }
.modal-dialog.modal-lg
.modal-content
.modal-header
%button.close{ data: { dismiss: :modal } }
%span &times;
%h4.modal-title Kies een product
.col-xs-3.pull-right
%input#product_search.form-control{ placeholder: "Search" }
.modal-body
#product_buttons.container-fluid
- @products.each do |product|
.col-md-2{ data: { name: product.name } }
%button.btn.btn-default.product{ data: { product: product.id, dismiss: :modal } }
%p= product.name
= image_tag product.avatar(:dagschotel), class: "center"
.modal-footer
%button.btn.btn-default{ data: { dismiss: :modal } }
Close

View file

@ -1,9 +1,42 @@
%h3
Order for #{@user.name} (Huidige schuld: #{euro_from_cents(@user.balance)})
.row .row
= f_form_for [@user, @order] do |f| .col-md-6.col-md-offset-1.barcode-wrapper
= f.error_messages .center
.col-md-12 %h1 Order for #{@user.name}
= f.fields_for :order_items do |op_field| = form_tag nil, id: "from_barcode_form" do
= render op_field.object, f: op_field, product: op_field.object.product %input.center-block{ type: :number, name: :id, autofocus: true }
= render 'orders/price', f: f = "- OR -"
%button.btn.btn-default.center-block{ data: { toggle: :modal, target: "#products_modal" } }
Select Product Without Barcode
.col-md-4.col-md-offset-1
= form_for [@user, @order] do |f|
= render 'errors', object: @order
#current_order
.div.center
= image_tag "logo.png"
%table
%tr.margin
= f.fields_for :order_items do |ff|
%tr.order_item_wrapper{ data: { product: ff.object.product.id, price: ff.object.product.price_cents } }
%td
= ff.number_field :count
= ff.fields_for :product do |fff|
/ Needed for haml
%td
x
%td
%span= ff.object.product.name
%td.euro
= euro_from_cents(ff.object.product.price_cents * ff.object.count)
%tr#empty
%td
%td
%em Empty Order.
%tr.margin
%tr
%td
%td
%td.text-right
Total:
%td.total_price.euro
= f.submit "Order!", class: "btn btn-primary form-control"
= render 'products_modal'

View file

@ -1,6 +1,4 @@
= render partial: 'flash'
.warning.center .warning.center
%h1 TESTFASE | GELIEVE STREEPJES TE BLIJVEN ZETTEN | TESTFASE %h1 TESTFASE | GELIEVE STREEPJES TE BLIJVEN ZETTEN | TESTFASE
.row .row
- @users.each do |user| = render @users
= render 'users/new_order', user: user

View file

@ -1,11 +1,11 @@
.row = f_form_for @product, html: { multipart: true } do |f|
.col-md-6.col-md-offset-3.sign-in = f.error_messages
= f_form_for @product, html: { multipart: true } do |f| = f.text_field :name
= f.error_messages = f.price_field :price
= f.text_field :name = f.collection_select :category, Product.categories.keys
= f.price_field :price = f.number_field :stock
= f.collection_select :category, Product.categories.keys = f.number_field :calories
= f.number_field :stock = f.file_field :avatar
= f.number_field :calories = f.fields_for :barcodes do |ff|
= f.file_field :avatar = ff.number_field :code, readonly: true
= f.submit = f.submit

View file

@ -1,9 +1,9 @@
- if controller_name == 'products' && current_user && current_user.admin? - if controller_name == 'products' && can?(:manage, Product)
= link_to "Edit", edit_product_path(product), class: "btn btn-default" = link_to "Edit", edit_product_path(product), class: "btn btn-default"
= link_to "Delete", product_path(product), method: :delete, class: "btn btn-danger", data: {confirm: 'Are you sure?'} = link_to "Delete", product_path(product), method: :delete, class: "btn btn-danger", data: {confirm: 'Are you sure?'}
- if controller_name == 'users' - if controller_name == 'users'
.product_dagschotel .product_dagschotel
- if current_user.dagschotel != product - if current_user.dagschotel != product
= link_to "Make dagschotel", dagschotel_user_path(current_user, product), class: "btn btn-default" = button_to "Make dagschotel", { controller: 'users', action: 'update', "user[dagschotel_id]" => product.id }, method: :put, class: "btn btn-default"
- else - else
= link_to "Huidige dagschotel", dagschotel_user_path(current_user, product), class: "btn btn-success", disabled: true %span.btn.btn-success= "Current dagschotel"

View file

@ -1,6 +1,6 @@
.col-md-3 .col-md-3
.thumbnail.pic .thumbnail.pic
.form_row_image .center
= image_tag product.avatar = image_tag product.avatar
.caption .caption
= kcal_tag product.calories = kcal_tag product.calories

View file

@ -0,0 +1,6 @@
.row
.col-md-6.col-md-offset-3.center
%h2 Scan barcode
= form_tag load_barcode_products_path do
.barcode-wrapper
%input.center-block{ type: :number, name: :barcode, autofocus: true }

View file

@ -1,2 +1,2 @@
%h1 Update product = content_for :title, "Update product"
= render "form" = render "form"

View file

@ -1,3 +1,2 @@
%h1 All products = content_for :title, "All products"
= render partial: 'flash'
= render 'products/index' = render 'products/index'

View file

@ -0,0 +1,12 @@
.row
.col-md-7
%h4.pull-right Select a product to link the barcode to an existing product ...
#product_buttons.row
- Product.all.each do |product|
.col-md-3
= button_to product_barcodes_path(product), class: "btn btn-default product", data: { product: product.id, dismiss: :modal }, params: { "barcode[code]" => params[:barcode] } do
%p= product.name
= image_tag product.avatar(:dagschotel), class: "center"
.col-md-5
%h4 or create a new one
= render 'products/form'

View file

@ -1,2 +1,2 @@
%h1 New product = content_for :title, "New product"
= render "form" = render "form"

View file

@ -0,0 +1,15 @@
.row
#stock_entry.col-md-6.col-md-offset-3
= form_for @product do |f|
%table
%tr
%td Current stock
%td= @product.stock
%tr
%td Purchase
%td
%input.form-control.new_stock{ type: :number, autofocus: true, data: { increment: true, target: "#product_stock" }}
%tr
%td New stock
%td= f.number_field :stock, data:{ default: f.object.stock }, class: "form-control"
= f.submit "Update stock", class: "btn btn-primary form-control"

View file

@ -1,9 +1,9 @@
%tr{:id => "products_row_#{dom_id(product)}"} %tr{id: "products_row_#{dom_id(product)}"}
%td= image_tag product.avatar(:small) %td= link_to image_tag(product.avatar(:small)), edit_product_path(product)
%td= product.name %td= product.name
%td= euro(product.price) %td= euro(product.price)
%td= product.stock %td= product.stock
%td %td
%span{:class => "glyphicon #{product.deleted ? "glyphicon-check" : "glyphicon-unchecked"}"} %span{class: "glyphicon #{product.deleted ? "glyphicon-check" : "glyphicon-unchecked"}"}
%td= product.calories %td= product.calories
%td= button_to "Edit", edit_product_path(product), method: :get, class: "btn btn-default", remote: true %td= link_to "Edit", edit_product_path(product), class: "btn btn-default", remote: true

View file

@ -1,9 +1,8 @@
= content_for :title, "Products"
#products-errors #products-errors
.row.products .row.products
.col-md-8.col-md-offset-2 .col-md-8.col-md-offset-2
%h1 Products = link_to "Add products", barcode_products_path, class: "btn btn-default"
= render partial: 'flash'
= link_to "Add Stock", new_stock_path, class: "btn btn-default"
%table#products-table.table.table-striped %table#products-table.table.table-striped
%tr %tr
%th %th

View file

@ -1,8 +0,0 @@
- unless @stock.valid?
.panel.panel-danger.form-errors
.panel-heading
= "#{pluralize(@stock.errors.count + @stock.stock_entries.map(&:errors).map(&:count).sum, "error")} prohibited this stock from being saved:"
.panel-body
%ul
= @stock.errors.full_messages.map{ |m| content_tag(:li, m) }.join.html_safe
= @stock.stock_entries.map{ |se| se.errors.full_messages.map{ |e| "#{se.product.name}: #{e}" } }.flatten.map{ |m| content_tag(:li, m) }.join.html_safe

View file

@ -1,13 +0,0 @@
.row
.col-md-6.col-md-offset-3
%h2 Add stock
= f_form_for @stock do |f|
= render 'stocks/errors'
= f.fields_for :stock_entries do |se_field|
.row
.col-sm-3
= image_tag se_field.object.product.avatar
.col-sm-9
= se_field.hidden_field :product_id
= se_field.number_field :count, skip_label: true
= f.submit "Insert stock", class: 'btn btn-primary'

View file

@ -1,6 +0,0 @@
.col-md-2.overviewthumbnail
- unless user.dagschotel.nil?
= link_to quickpay_user_path(user) do
= image_tag user.dagschotel.avatar(:dagschotel), class: "img-circle dagschotel"
= link_to image_tag(user.avatar(:large) , class: "img-circle avatar"), new_user_order_path(user)
= link_to user.name , new_user_order_path(user), class: "btn btn-info", style: get_color_style(user)

View file

@ -1,8 +1,6 @@
%tr .col-md-2.overviewthumbnail
%td= user.id - unless user.dagschotel.nil?
%td= image_tag user.avatar(:small) = link_to quickpay_user_path(user) do
%td= user.name = image_tag user.dagschotel.avatar(:dagschotel), class: "img-circle dagschotel"
%td= euro(user.debt) = link_to image_tag(user.avatar(:large) , class: "img-circle avatar"), new_user_order_path(user)
- if current_user.admin? = link_to user.name , new_user_order_path(user), class: "btn btn-info", style: get_color_style(user)
%td
= link_to "Delete", user_path(user), method: :delete, class: "btn btn-danger", data: { confirm: "Are you sure?" }

View file

@ -1,4 +1,3 @@
= render 'flash'
.row .row
= render 'sidebar' = render 'sidebar'
.col-sm-9 .col-sm-9

View file

@ -1,3 +1,2 @@
%h3 = content_for :title, "Choose new Dagschotel"
Choose new Dagschotel = render 'products/index'
= render 'products/index'

View file

@ -1,12 +0,0 @@
.row.users
.col-md-8.col-md-offset-2
%h1 All users
= render partial: 'flash'
%table#users-table.table.table-striped
%tr
%th ID
%th
%th Nickname
%th Debt
%th
= render @users

View file

@ -1,4 +1,3 @@
= render partial: 'flash'
.row .row
= render 'sidebar' = render 'sidebar'
#user_info.col-sm-9 #user_info.col-sm-9

View file

@ -1,7 +1,4 @@
%h2 Login = content_for :title, "Login"
= render 'flash'
If this is the first time you log in, an account will be created for you. If this is the first time you log in, an account will be created for you.
%div %div
%br/
= link_to "Sign in with Zeus WPI account.", omniauth_authorize_path("user", "zeuswpi"), class: "btn btn-large btn-primary" = link_to "Sign in with Zeus WPI account.", omniauth_authorize_path("user", "zeuswpi"), class: "btn btn-large btn-primary"
%br/

View file

@ -1,10 +1,9 @@
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :users, controllers: { devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
omniauth_callbacks: "callbacks",
sessions: "sessions"
}
devise_scope :user do devise_scope :user do
get 'sign_out', to: 'devise/sessions#destroy', as: :destroy_user_session
post 'sign_in', to: 'welcome#token_sign_in'
unauthenticated :user do unauthenticated :user do
root to: 'welcome#index' root to: 'welcome#index'
end end
@ -18,17 +17,21 @@ Rails.application.routes.draw do
end end
end end
resources :users, only: [:show, :edit, :update, :index, :destroy] do resources :users, only: [:show, :edit, :update] do
resources :orders, only: [:new, :create, :destroy] resources :orders, only: [:new, :create, :destroy]
member do member do
get 'quickpay' => 'users#quickpay' get 'quickpay' => 'users#quickpay'
get 'dagschotel/edit' => 'users#edit_dagschotel', as: 'edit_dagschotel' get 'dagschotel/edit' => 'users#edit_dagschotel', as: 'edit_dagschotel'
get 'dagschotel/:product_id' => 'users#update_dagschotel', as: 'dagschotel'
end end
end end
resources :products, only: [:new, :create, :index, :edit, :update] resources :products, only: [:create, :index, :edit, :update] do
resources :stocks, only: [:new, :create] resources :barcodes, only: [:create, :show], shallow: true
collection do
get 'barcode' => 'products#barcode', as: :barcode
post 'barcode' => 'products#load_barcode', as: :load_barcode
end
end
get 'overview' => 'orders#overview', as: "orders" get 'overview' => 'orders#overview', as: "orders"
end end

View file

@ -16,6 +16,7 @@ development:
omniauth_client_secret: blargh omniauth_client_secret: blargh
access_token: "token" access_token: "token"
tab_api_key: "HriaktSIhRaB5CJzD71uLQ==" tab_api_key: "HriaktSIhRaB5CJzD71uLQ=="
koelkast_token: ""
test: test:
secret_key_base: 961437e28e7d6055ffaad9cf1f8d614354f57f10cb2d7601c9d6ede72a03b9c9535ad9e63507e3eb31252c4895970a63117493408f2e9a46c7a0c4a5a7836b81 secret_key_base: 961437e28e7d6055ffaad9cf1f8d614354f57f10cb2d7601c9d6ede72a03b9c9535ad9e63507e3eb31252c4895970a63117493408f2e9a46c7a0c4a5a7836b81
@ -29,3 +30,4 @@ production:
omniauth_client_secret: "" omniauth_client_secret: ""
access_token: "" access_token: ""
tab_api_key: "" tab_api_key: ""
koelkast_token: ""

View file

@ -0,0 +1,9 @@
class RemoveDeviseFieldsFromUsers < ActiveRecord::Migration
def change
remove_column :users, :sign_in_count, :integer
remove_column :users, :current_sign_in_at, :datetime
remove_column :users, :last_sign_in_at, :datetime
remove_column :users, :current_sign_in_ip, :string
remove_column :users, :last_sign_in_ip, :string
end
end

View file

@ -0,0 +1,10 @@
class AddBarcodeToProducts < ActiveRecord::Migration
def change
create_table :barcodes do |t|
t.references :product
t.string :code, index: true, null: false, default: ""
t.timestamps
end
end
end

View file

@ -11,7 +11,16 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150917165758) do ActiveRecord::Schema.define(version: 20150919091214) do
create_table "barcodes", force: :cascade do |t|
t.integer "product_id"
t.string "code", default: "", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "barcodes", ["code"], name: "index_barcodes_on_code"
create_table "delayed_jobs", force: :cascade do |t| create_table "delayed_jobs", force: :cascade do |t|
t.integer "priority", default: 0, null: false t.integer "priority", default: 0, null: false
@ -66,11 +75,6 @@ ActiveRecord::Schema.define(version: 20150917165758) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.datetime "remember_created_at" t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.boolean "admin" t.boolean "admin"
t.integer "dagschotel_id" t.integer "dagschotel_id"
t.string "avatar_file_name" t.string "avatar_file_name"

View file

@ -1,17 +1,40 @@
# from_barcode_products POST /products/barcode(.:format) products#from_barcode
# products GET /products(.:format) products#index
# POST /products(.:format) products#create
# new_product GET /products/new(.:format) products#new
# edit_product GET /products/:id/edit(.:format) products#edit
# product PATCH /products/:id(.:format) products#update
# PUT /products/:id(.:format) products#update
#
describe ProductsController, type: :controller do describe ProductsController, type: :controller do
before :each do before :each do
@admin = create :admin @admin = create :admin
sign_in @admin sign_in @admin
end end
#########
# NEW #
#########
describe 'GET new' do describe 'GET new' do
it 'should render the form' do it 'should render the form' do
get :new get :new
expect(response).to render_template(:new) expect(response).to render_template(:new)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'should initialize a new product' do
get :new
expect(assigns(:product).class).to eq(Product)
expect(assigns(:product)).to_not be_persisted
end
end end
##########
# POST #
##########
describe 'POST create' do describe 'POST create' do
context 'successfull' do context 'successfull' do
it 'should create a product' do it 'should create a product' do
@ -22,7 +45,7 @@ describe ProductsController, type: :controller do
it 'should redirect to index page' do it 'should redirect to index page' do
post :create, product: attributes_for(:product) post :create, product: attributes_for(:product)
expect(response).to redirect_to action: :index expect(response).to redirect_to action: :barcode
end end
end end
@ -35,11 +58,15 @@ describe ProductsController, type: :controller do
it 'should render form' do it 'should render form' do
post :create, product: attributes_for(:invalid_product) post :create, product: attributes_for(:invalid_product)
expect(response).to render_template(:new) expect(response).to render_template(:link)
end end
end end
end end
###########
# INDEX #
###########
describe 'GET index' do describe 'GET index' do
it 'should load all the products' do it 'should load all the products' do
product = create :product product = create :product
@ -48,6 +75,10 @@ describe ProductsController, type: :controller do
end end
end end
##########
# EDIT #
##########
describe 'GET edit' do describe 'GET edit' do
before :each do before :each do
@product = create :product @product = create :product
@ -65,6 +96,10 @@ describe ProductsController, type: :controller do
end end
end end
############
# UPDATE #
############
describe 'PUT update' do describe 'PUT update' do
before :each do before :each do
@product = create :product @product = create :product
@ -75,12 +110,32 @@ describe ProductsController, type: :controller do
expect(assigns :product).to eq(@product) expect(assigns :product).to eq(@product)
end end
context 'successful' do
it 'should update attributes' do
put :update, id: @product, product: { name: "new_product_name" }
expect(@product.reload.name).to eq("new_product_name")
end
end
context 'failed' do context 'failed' do
it 'should not update attributes' do it 'should not update attributes' do
old_attributes = @product.reload.attributes old_attributes = @product.attributes
put :update, id: @product, product: attributes_for(:invalid_product) put :update, id: @product, product: attributes_for(:invalid_product)
expect(@product.reload.attributes).to eq(old_attributes) expect(@product.reload.attributes).to eq(old_attributes)
end end
end end
end end
##################
# FROM_BARCODE #
##################
describe 'POST from_barcode' do
it 'should return a product when barcode in database' do
product = create :product
bar = create :barcode, product: product
post :from_barcode, barcode: bar.code
expect(JSON.parse(response.body)["id"]).to eq(product.id)
end
end
end end

View file

@ -1,5 +1,10 @@
require 'identicon' # quickpay_user GET /users/:id/quickpay(.:format) users#quickpay
require 'faker' # edit_dagschotel_user GET /users/:id/dagschotel/edit(.:format) users#edit_dagschotel
# edit_user GET /users/:id/edit(.:format) users#edit
# user GET /users/:id(.:format) users#show
# PATCH /users/:id(.:format) users#update
# PUT /users/:id(.:format) users#update
#
describe UsersController, type: :controller do describe UsersController, type: :controller do
before :each do before :each do
@ -7,6 +12,10 @@ describe UsersController, type: :controller do
sign_in @user sign_in @user
end end
##########
# SHOW #
##########
describe 'GET show' do describe 'GET show' do
before :each do before :each do
get :show, id: @user get :show, id: @user
@ -22,6 +31,10 @@ describe UsersController, type: :controller do
end end
end end
##########
# EDIT #
##########
describe 'GET edit' do describe 'GET edit' do
before :each do before :each do
get :edit, id: @user get :edit, id: @user
@ -36,6 +49,10 @@ describe UsersController, type: :controller do
end end
end end
############
# UPDATE #
############
describe 'PUT update' do describe 'PUT update' do
it 'should load the correct user' do it 'should load the correct user' do
put :update, id: @user, user: attributes_for(:user) put :update, id: @user, user: attributes_for(:user)
@ -48,23 +65,18 @@ describe UsersController, type: :controller do
put :update, id: @user, user: { private: new_private } put :update, id: @user, user: { private: new_private }
expect(@user.reload.private).to be new_private expect(@user.reload.private).to be new_private
end end
it 'should update dagschotel' do
product = create :product
put :update, id: @user, user: { dagschotel_id: product.id }
expect(@user.reload.dagschotel).to eq(product)
end
end end
end end
describe 'GET index' do #####################
before :each do # EDIT_DAGSCHOTEL #
get :index #####################
end
it 'should load an array of all users' do
expect(assigns(:users)).to eq([@user])
end
it 'should render the correct template' do
expect(response).to render_template(:index)
expect(response).to have_http_status(200)
end
end
describe 'GET edit_dagschotel' do describe 'GET edit_dagschotel' do
it 'should render the page' do it 'should render the page' do
@ -73,12 +85,4 @@ describe UsersController, type: :controller do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
end end
describe 'GET update_dagschotel' do
it 'should update the dagschotel' do
product = create :product
get :update_dagschotel, id: @user, product_id: product
expect(@user.reload.dagschotel).to eq(product)
end
end
end end

View file

@ -0,0 +1,17 @@
# == Schema Information
#
# Table name: barcodes
#
# id :integer not null, primary key
# product_id :integer
# code :string default(""), not null
# created_at :datetime
# updated_at :datetime
#
FactoryGirl.define do
factory :barcode do
product
sequence :code
end
end

View file

@ -6,11 +6,6 @@
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# remember_created_at :datetime # remember_created_at :datetime
# sign_in_count :integer default("0"), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string
# last_sign_in_ip :string
# admin :boolean # admin :boolean
# dagschotel_id :integer # dagschotel_id :integer
# avatar_file_name :string # avatar_file_name :string

View file

@ -5,37 +5,44 @@ describe User do
subject(:ability){ Ability.new(user) } subject(:ability){ Ability.new(user) }
let(:user) { nil} let(:user) { nil}
# Admin
describe 'as admin' do describe 'as admin' do
let(:user) { create :admin } let(:user) { create :admin }
it{ should be_able_to(:manage, Product.new) }
it{ should be_able_to(:manage, Order.new) } it{ should be_able_to(:manage, Order.new) }
it{ should be_able_to(:manage, OrderItem.new) }
it{ should be_able_to(:manage, Product.new) }
it{ should be_able_to(:manage, Stock.new) } it{ should be_able_to(:manage, Stock.new) }
it{ should be_able_to(:manage, User.new) } it{ should be_able_to(:manage, User.new) }
end end
# Normal User
describe 'as normal user' do describe 'as normal user' do
let(:user) { create :user } let(:user) { create :user }
it{ should be_able_to(:read, Product.new) }
it{ should_not be_able_to(:manage, Product.new) }
it{ should be_able_to(:create, Order.new(user: user)) } it{ should be_able_to(:create, Order.new(user: user)) }
it{ should be_able_to(:delete, Order.new(user: user, created_at: (Rails.application.config.call_api_after - 1.minutes).ago)) } it{ should be_able_to(:delete, Order.new(user: user, created_at: (Rails.application.config.call_api_after - 1.minutes).ago)) }
it{ should_not be_able_to(:delete, Order.new(user: user, created_at: 10.minutes.ago)) } it{ should_not be_able_to(:delete, Order.new(user: user, created_at: 10.minutes.ago)) }
it{ should_not be_able_to(:manage, Order.new) } it{ should_not be_able_to(:create, Order.new) }
it{ should_not be_able_to(:update, Order.new) }
it{ should_not be_able_to(:manage, Stock.new) } it{ should be_able_to(:read, Product.new) }
it{ should_not be_able_to(:delete, Product.new) }
it{ should_not be_able_to(:update, Product.new) }
it{ should_not be_able_to(:create, Stock.new) }
it{ should be_able_to(:manage, user) } it{ should be_able_to(:manage, user) }
it{ should_not be_able_to(:manage, User.new) } it{ should_not be_able_to(:create, User.new) }
it{ should_not be_able_to(:update, User.new) }
end end
describe 'as koelkast' do describe 'as koelkast' do
let(:user) { create :koelkast } let(:user) { create :koelkast }
it{ should_not be_able_to(:manage, Product.new) } it{ should_not be_able_to(:manage, Product.new) }
it{ should be_able_to(:manage, Order.new) } it{ should be_able_to(:manage, Order.new, user: create(:user)) }
it{ should_not be_able_to(:create, build(:order, user: create(:user, private: true))) }
it{ should_not be_able_to(:manage, Stock.new) } it{ should_not be_able_to(:manage, Stock.new) }
it{ should_not be_able_to(:manage, User.new) } it{ should_not be_able_to(:manage, User.new) }
end end

View file

@ -0,0 +1,27 @@
describe Barcode do
before :each do
@barcode = create :barcode
end
it 'has a valid factory' do
expect(@barcode).to be_valid
end
############
# FIELDS #
############
describe 'fields' do
describe 'code' do
it 'should be present' do
@barcode.code = nil
expect(@barcode).to_not be_valid
end
it 'should be unique' do
barcode = build :barcode, code: @barcode.code
expect(barcode).to_not be_valid
end
end
end
end

View file

@ -14,14 +14,20 @@ describe OrderItem do
expect(order_item).to be_valid expect(order_item).to be_valid
end end
describe 'validations' do ############
# FIELDS #
############
describe 'fields' do
before :each do before :each do
@order_item = create :order_item @order_item = create :order_item
end end
it 'product should be present' do describe 'product' do
@order_item.product = nil it 'should be present' do
expect(@order_item).to_not be_valid @order_item.product = nil
expect(@order_item).to_not be_valid
end
end end
describe 'count' do describe 'count' do
@ -34,10 +40,21 @@ describe OrderItem do
@order_item.count = -5 @order_item.count = -5
expect(@order_item).to_not be_valid expect(@order_item).to_not be_valid
end end
it 'should be less or equal to product stock' do
@order_item.count = @order_item.product.stock + 1
expect(@order_item).to_not be_valid
@order_item.count = @order_item.product.stock
expect(@order_item).to be_valid
end
end end
end end
describe 'product stock' do ###############
# CALLBACKS #
###############
describe 'stock change' do
before :each do before :each do
@product = create :product @product = create :product
@count = rand 10 @count = rand 10
@ -48,7 +65,7 @@ describe OrderItem do
expect{ @order_item.save }.to change{ @product.stock }.by(-@count) expect{ @order_item.save }.to change{ @product.stock }.by(-@count)
end end
it 'should increment on cancel' do it 'should increment on destroy' do
@order_item.save @order_item.save
expect{ @order_item.destroy }.to change{ @product.stock }.by(@count) expect{ @order_item.destroy }.to change{ @product.stock }.by(@count)
end end

View file

@ -20,16 +20,58 @@ describe Order do
expect(@order).to be_valid expect(@order).to be_valid
end end
describe 'price' do ############
it 'should be calculated from order_items' do # FIELDS #
@order = build :order, products_count: 0 ############
sum = (create_list :product, 1 + rand(10)).map do |p|
create(:order_item, order: @order, product: p, count: 1 + rand(5)) do |oi| describe 'fields' do
@order.order_items << oi describe 'user' do
end it { Order.reflect_on_association(:user).macro.should eq(:belongs_to) }
end.map{ |oi| oi.count * oi.product.price_cents }.sum it 'should be present' do
@order.valid? @order.user = nil
expect(@order.price_cents).to eq(sum) expect(@order).to_not be_valid
end
end
describe 'price_cents' do
it 'should be calculated from order_items' do
@order = build :order, products_count: 0
sum = (create_list :product, 1 + rand(10)).map do |p|
create(:order_item, order: @order, product: p, count: 1 + rand(5)) do |oi|
@order.order_items << oi
end
end.map{ |oi| oi.count * oi.product.price_cents }.sum
@order.save
expect(@order.price_cents).to eq(sum)
end
end
describe 'order_items' do
it 'should be validated' do
@order.order_items.build(count: -5)
expect(@order).to_not be_valid
end
end
describe 'products' do
it 'should be present' do
@order.products.clear
expect(@order).to_not be_valid
end
end end
end end
###############
# CALLBACKS #
###############
describe 'empty order_items' do
it 'should be removed' do
product = create :product
@order.order_items << create(:order_item, order: @order, product: product, count: 0)
@order.save
expect(@order.order_items.where(product: product)).to be_empty
end
end
end end

View file

@ -26,23 +26,38 @@ describe Product do
expect(@product).to be_valid expect(@product).to be_valid
end end
describe 'validations' do ############
it 'name should be present' do # FIELDS #
@product.name = '' ############
expect(@product).to_not be_valid
end
describe 'price' do describe 'fields' do
it 'should be positive' do describe 'name' do
@product.price = -5 it 'should be present' do
@product.name = nil
expect(@product).to_not be_valid expect(@product).to_not be_valid
end end
it 'should be saved correctly' do it 'shold be unique' do
@product.price = 1.20 expect(build :product, name: @product.name).to_not be_valid
@product.save end
expect(@product.reload.price).to eq(1.20) end
expect(@product.reload.price_cents).to eq(120)
describe 'price_cents' do
it 'should be present' do
@product.price_cents = nil
expect(@product).to_not be_valid
end
it 'should be a number' do
@product.price_cents = "123abc"
expect(@product).to_not be_valid
end
it 'should be strict positive' do
@product.price = -5
expect(@product).to_not be_valid
@product.price = 0
expect(@product).to_not be_valid
end end
end end
@ -55,11 +70,13 @@ describe Product do
it 'should be positive' do it 'should be positive' do
@product.stock = -5 @product.stock = -5
expect(@product).to_not be_valid expect(@product).to_not be_valid
@product.stock = 0
expect(@product).to be_valid
end end
end end
describe 'calories' do describe 'calories' do
it 'should not be present' do it 'does not have to be present' do
@product.calories = nil @product.calories = nil
expect(@product).to be_valid expect(@product).to be_valid
end end
@ -70,9 +87,28 @@ describe Product do
end end
end end
it 'avatar should be present' do describe 'avatar' do
@product.avatar = nil it 'should be present' do
expect(@product).to_not be_valid @product.avatar = nil
expect(@product).to_not be_valid
end
end end
end end
#############
# METHODS #
#############
describe 'price' do
it 'should read the correct value' do
expect(@product.price).to eq(@product.price_cents / 100.0)
end
it 'should write the correct value' do
@product.price = 1.5
@product.save
expect(@product.reload.price_cents).to eq(150)
end
end
end end

View file

@ -6,11 +6,6 @@
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# remember_created_at :datetime # remember_created_at :datetime
# sign_in_count :integer default("0"), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string
# last_sign_in_ip :string
# admin :boolean # admin :boolean
# dagschotel_id :integer # dagschotel_id :integer
# avatar_file_name :string # avatar_file_name :string
@ -32,4 +27,23 @@ describe User do
it 'has a valid factory' do it 'has a valid factory' do
expect(@user).to be_valid expect(@user).to be_valid
end end
############
# FIELDS #
############
describe 'fields' do
describe 'avatar' do
it 'should be present' do
@user.avatar = nil
expect(@user).to_not be_valid
end
end
describe 'orders_count' do
it 'should automatically cache the number of orders' do
expect{ create :order, user: @user }.to change{ @user.reload.orders_count }.by(1)
end
end
end
end end