Merge branch 'master' of github.com:ZeusWPI/Tab

This commit is contained in:
Tom Naessens 2015-09-01 15:02:21 +02:00
commit d8c6ec8f0b
34 changed files with 179 additions and 141 deletions

View file

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

View file

@ -32,3 +32,7 @@
vertical-align: middle; vertical-align: middle;
} }
} }
div.out-of-stock {
border-color:red;
}

View file

@ -37,16 +37,25 @@
cursor: pointer; cursor: pointer;
} }
.debt { .user_info .actions {
padding: 12px 0; a, span {
text-align: center; display: inline-block;
font-family: monospace; padding: 12px 0;
font-size: 16px; text-align: center;
width: 60%; font-family: monospace;
margin: auto; font-size: 16px;
margin-top: 10px; width: 60%;
background-color: #FF7F00; margin: auto;
color: white; margin-top: 10px;
background-color: #FF7F00;
color: white;
}
a {
background: #64a724;
background: -moz-linear-gradient(top, #64a724 0%, #579727 50%, #58982a 51%, #498c25 100%);
background: -webkit-gradient(linear, left top, left bottom, from(#64a724), to(#498c25), color-stop(0.4, #579727), color-stop(0.5, #58982a), color-stop(.9, #498c25), color-stop(0.9, #498c25));
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#64a724', endColorstr='#498c25', GradientType=0 );
}
} }
.stats{ .stats{

View file

@ -0,0 +1,3 @@
// 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,7 +1,6 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
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

@ -1,4 +1,6 @@
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

@ -2,27 +2,20 @@ class OrdersController < ApplicationController
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
include ApplicationHelper include ApplicationHelper
load_and_authorize_resource load_resource :user
load_and_authorize_resource :order, through: :user, shallow: true
def new def new
init products = (@user.products.for_sale.select("products.*", "sum(order_items.count) as count").group(:product_id).order("count desc") | Product.for_sale)
@order = @user.orders.build
products = (@user.products.select("products.*", "sum(order_items.count) as count").group(:product_id).order("count desc") | Product.all)
@order.g_order_items products @order.g_order_items products
end end
def create def create
init
@order = @user.orders.build order_params
if @order.save if @order.save
message = "#{@order.to_sentence} ordered. Enjoy it!" flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!"
flash[:success] = message
slack_notification(@user, message)
redirect_to root_path redirect_to root_path
else else
@order.g_order_items Product.all @order.g_order_items Product.for_sale
render 'new' render 'new'
end end
end end
@ -56,25 +49,6 @@ class OrdersController < ApplicationController
private private
def init
@user = User.find(params[:user_id])
if @user.koelkast?
flash[:error] = "Koelkast can't order things."
redirect_to root_path
end
if @user.private && current_user != @user
flash[:error] = "You can't order stuff for this person."
redirect_to root_path
end
unless current_user.koelkast? || current_user.admin? || current_user == @user
flash[:error] = "Please don't order stuff for other people"
redirect_to root_path
end
end
def order_params def order_params
params.require(:order).permit(order_items_attributes: [:count, :price, product_attributes: [:id]]) params.require(:order).permit(order_items_attributes: [:count, :price, product_attributes: [:id]])
end end

View file

@ -10,6 +10,7 @@ class ProductsController < ApplicationController
def create def create
@product = Product.new(product_params) @product = Product.new(product_params)
if @product.save if @product.save
flash[:success] = "Product created!"
redirect_to products_path redirect_to products_path
else else
render 'new' render 'new'
@ -19,9 +20,8 @@ class ProductsController < ApplicationController
def index def index
@products = Product.all @products = Product.all
@categories = Product.categories @categories = Product.categories
if current_user.admin?
render 'products_list/listview' render 'products_list/listview' if current_user.admin?
end
end end
def edit def edit
@ -35,12 +35,6 @@ class ProductsController < ApplicationController
respond_with @product respond_with @product
end end
def destroy
Product.find(params[:id]).destroy
flash[:success] = "Succesfully removed product"
redirect_to products_path
end
def stock def stock
@products = Product.all @products = Product.all
end end
@ -57,6 +51,6 @@ class ProductsController < ApplicationController
private private
def product_params def product_params
params.require(:product).permit(:name, :price, :avatar, :category, :stock) params.require(:product).permit(:name, :price, :avatar, :category, :stock, :calories, :deleted)
end end
end end

View file

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

View file

@ -4,19 +4,16 @@ class UsersController < ApplicationController
def show def show
@user = User.find_by_id(params[:id]) || current_user @user = User.find_by_id(params[:id]) || current_user
@orders = @user.orders @orders = @user.orders
.active
.order(:created_at) .order(:created_at)
.reverse_order .reverse_order
.paginate(page: params[:page]) .paginate(page: params[:page])
@products = @user.products @products = @user.products
.select("products.*", "sum(order_items.count) as count") .select("products.*", "sum(order_items.count) as count")
.where("orders.cancelled = ?", false)
.group(:product_id) .group(:product_id)
.order("count") .order("count")
.reverse_order .reverse_order
@categories = @user.products @categories = @user.products
.select("products.category", "sum(order_items.count) as count") .select("products.category", "sum(order_items.count) as count")
.where("orders.cancelled = ?", false)
.group(:category) .group(:category)
end end
@ -39,46 +36,35 @@ class UsersController < ApplicationController
end end
def destroy def destroy
@user = User.find(params[:id]) user = User.find(params[:id])
@user.destroy user.destroy
flash[:success] = "Succesfully removed user" flash[:success] = "Succesfully removed user"
redirect_to users_path redirect_to users_path
end end
def edit_dagschotel def edit_dagschotel
@user = User.find(params[:user_id]) @user = User.find(params[:user_id])
authorize! :update_dagschotel, @user
@dagschotel = @user.dagschotel @dagschotel = @user.dagschotel
@products = Product.all @products = Product.for_sale
@categories = Product.categories @categories = Product.categories
end end
def update_dagschotel def update_dagschotel
@user = User.find(params[:user_id]) user = User.find(params[:user_id])
@user.dagschotel = Product.find(params[:product_id]) authorize! :update_dagschotel, user
@products = Product.all user.dagschotel = Product.find(params[:product_id])
@categories = Product.categories user.save
if @user.save
flash[:success] = "Succesfully updated dagschotel"
redirect_to @user
else
flash[:error] = "Error updating dagschotel"
@dagschotel = @user.reload.dagschotel
render 'edit_dagschotel'
end
flash[:success] = "Succesfully updated dagschotel"
redirect_to user
end end
private private
def init
@user = User.find(params[:user_id])
redirect_to root_path, error: "You are not authorized to access this page." unless @user == current_user || current_user.admin?
end
def user_params def user_params
params.fetch(:user, {}).permit(:avatar, :private) params.require(:user).permit(:avatar, :private)
end end
end end

View file

@ -1,4 +1,6 @@
class WelcomeController < ApplicationController class WelcomeController < ApplicationController
skip_authorization_check
def index def index
end end
end end

View file

@ -48,7 +48,11 @@ class FormattedFormBuilder < ActionView::Helpers::FormBuilder
label_content = block_given? ? capture(&block) : options[:label] label_content = block_given? ? capture(&block) : options[:label]
content_tag :div, class: control_wrapper_class do content_tag :div, class: control_wrapper_class do
checkbox + " " + label(name, label_content) if options[:skip_label]
checkbox
else
checkbox + " " + label(name, label_content)
end
end end
end end

View file

@ -0,0 +1,13 @@
module ProductsHelper
def kcal(calories)
calories.to_s + " kcal"
end
def kcal_tag(calories)
if calories
content_tag :small, kcal(calories)
else
'&nbsp;'.html_safe
end
end
end

View file

@ -0,0 +1,2 @@
module SessionsHelper
end

View file

@ -3,17 +3,17 @@ class Ability
def initialize(user) def initialize(user)
user ||= User.new # guest user (not logged in) user ||= User.new # guest user (not logged in)
if user.admin? if user.admin?
can :manage, :all can :manage, :all
can :schulden, :admins
elsif user.koelkast? elsif user.koelkast?
can :manage, Order can :manage, Order
elsif user[:id] elsif user[:id]
can :read, :all can :read, :all
can :update, User can :manage, User, id: user.id
can :edit_dagschotel, User can :manage, Order do |order|
can :update_dagschotel, User order.try(:user) == user
can :create, Order end
end end
end end
end end

View file

@ -13,13 +13,11 @@
class Order < ActiveRecord::Base class Order < ActiveRecord::Base
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
after_create { self.user.increment!(:debt_cents, price_cents) }
belongs_to :user, counter_cache: true belongs_to :user, counter_cache: true
has_many :order_items, dependent: :destroy has_many :order_items, dependent: :destroy
has_many :products, through: :order_items has_many :products, through: :order_items
scope :active, -> { where(cancelled: false) } default_scope -> { where(cancelled: false) }
validates :user, presence: true validates :user, presence: true
validates :order_items, presence: true, in_stock: true validates :order_items, presence: true, in_stock: true
@ -40,7 +38,6 @@ class Order < ActiveRecord::Base
def cancel def cancel
return if self.cancelled return if self.cancelled
user.decrement!(:debt_cents, price_cents)
User.decrement_counter(:orders_count, user.id) User.decrement_counter(:orders_count, user.id)
update_attribute(:cancelled, true) update_attribute(:cancelled, true)
self.order_items.each(&:cancel) self.order_items.each(&:cancel)

View file

@ -13,6 +13,7 @@
# avatar_updated_at :datetime # avatar_updated_at :datetime
# category :integer default("0") # category :integer default("0")
# stock :integer default("0"), not null # stock :integer default("0"), not null
# calories :integer default("0") // expressed in kcal
# #
class Product < ActiveRecord::Base class Product < ActiveRecord::Base
@ -24,10 +25,13 @@ class Product < ActiveRecord::Base
validates :name, presence: true validates :name, presence: true
validates :price_cents, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :price_cents, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :stock, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :stock, 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_attachment :avatar, validates_attachment :avatar,
presence: true, presence: true,
content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] } content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }
scope :for_sale, -> { where deleted: false }
def price def price
self.price_cents / 100.0 self.price_cents / 100.0
end end
@ -36,4 +40,8 @@ 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 out_of_sale
update_attribute :deleted, true
end
end end

View file

@ -31,7 +31,6 @@ class User < ActiveRecord::Base
has_paper_trail has_paper_trail
has_attached_file :avatar, styles: { large: "150x150>", medium: "100x100>", small: "40x40>" }, default_style: :medium has_attached_file :avatar, styles: { large: "150x150>", medium: "100x100>", small: "40x40>" }, default_style: :medium
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'
@ -56,12 +55,7 @@ class User < ActiveRecord::Base
end end
def debt def debt
self.debt_cents / 100.0 42.15
end
def debt=(value)
if value.is_a? String then value.sub!(',', '.') end
self.debt_cents = (value.to_f * 100).to_int
end end
# Change URL params for User # Change URL params for User

View file

@ -1,10 +1,13 @@
<div class="col-md-3 form_products"> <div class="col-md-3 form_products">
<div class="thumbnail "> <div class="thumbnail<%= ' out-of-stock' if product.stock.zero? %>">
<div class="form_row center"> <div class="form_row center">
<div class="form_row_image"> <div class="form_row_image">
<%= image_tag product.avatar %> <%= image_tag product.avatar %>
</div> </div>
<div class="caption"> <div class="caption">
<h6>
<%= kcal_tag product.calories %>
</h6>
<h4 class="text-nowrap"> <h4 class="text-nowrap">
<%= content_tag :span, product.name %> <%= content_tag :span, product.name %>
<%= content_tag :small, euro(product.price) %> <%= content_tag :small, euro(product.price) %>

View file

@ -7,6 +7,7 @@
<%= f.price_field :price %> <%= f.price_field :price %>
<%= f.collection_select :category, Product.categories.keys %> <%= f.collection_select :category, Product.categories.keys %>
<%= f.number_field :stock %> <%= f.number_field :stock %>
<%= f.number_field :calories %>
<%= f.file_field :avatar %> <%= f.file_field :avatar %>
<%= f.submit %> <%= f.submit %>

View file

@ -4,6 +4,7 @@
<%= image_tag product.avatar %> <%= image_tag product.avatar %>
</div> </div>
<div class="caption"> <div class="caption">
<%= kcal_tag product.calories %>
<h4><%= product.name %></h4> <h4><%= product.name %></h4>
<h3><%= euro(product.price) %></h3> <h3><%= euro(product.price) %></h3>
<h6>(In stock: <%= product.stock %>)</h6> <h6>(In stock: <%= product.stock %>)</h6>

View file

@ -4,6 +4,8 @@
<td><%= f.text_field :name, skip_label: true %></td> <td><%= f.text_field :name, skip_label: true %></td>
<td><%= f.price_field :price, skip_label: true %></td> <td><%= f.price_field :price, skip_label: true %></td>
<td><%= f.number_field :stock, skip_label: true %></td> <td><%= f.number_field :stock, skip_label: true %></td>
<td><%= f.check_box :deleted, skip_label: true %></td>
<td><%= f.number_field :calories, skip_label: true %></td>
<td><%= f.button "Update", class: "btn btn-primary" %></td> <td><%= f.button "Update", class: "btn btn-primary" %></td>
<%= javascript_tag do %> <%= javascript_tag do %>
var id = "#edit_<%= dom_id(product) %>"; var id = "#edit_<%= dom_id(product) %>";

View file

@ -3,5 +3,7 @@
<td><%= product.name %></td> <td><%= product.name %></td>
<td><%= euro(product.price) %></td> <td><%= euro(product.price) %></td>
<td><%= product.stock %></td> <td><%= product.stock %></td>
<td><span class="glyphicon <%= product.deleted ? "glyphicon-check" : "glyphicon-unchecked" %>"></span></td>
<td><%= product.calories %></td>
<td><%= button_to "Edit", edit_product_path(product), method: :get, class: "btn btn-default", remote: true %></td> <td><%= button_to "Edit", edit_product_path(product), method: :get, class: "btn btn-default", remote: true %></td>
</tr> </tr>

View file

@ -10,6 +10,8 @@
<th>Name</th> <th>Name</th>
<th>Price</th> <th>Price</th>
<th>Stock</th> <th>Stock</th>
<th>Deleted</th>
<th>Kilocalorieën</th>
<th></th> <th></th>
</tr> </tr>
<%= render partial: 'products_list/product_row', collection: @products, as: :product %> <%= render partial: 'products_list/product_row', collection: @products, as: :product %>

View file

@ -1,27 +1,36 @@
<%= render partial: 'flash' %> <%= render partial: 'flash' %>
<div class="row"> <div class="row">
<div class="user_info"> <div class="user_info">
<% if current_user == @user %> <% if can? :edit, @user %>
<h5> <h5>
<%= link_to "[Edit dagschotel]" , user_edit_dagschotel_path(@user) %> <%= link_to "[Edit dagschotel]" , user_edit_dagschotel_path(@user) %>
<%= link_to "[Edit profile]" , edit_user_path(@user) %> <%= link_to "[Edit profile]" , edit_user_path(@user) %>
</h5> </h5>
<% end %> <% end %>
<h2><%= @user.nickname %></h2> <h2><%= @user.nickname %></h2>
<%= button_to "PLACE ORDER!", new_user_order_path(@user), method: :get if current_user == @user %> <div class="actions">
<div class="debt">DEBT: <%= euro(@user.debt) %></div> <%= link_to "PLACE ORDER!", new_user_order_path(@user) if current_user == @user %>
<span>DEBT: <%= euro(@user.debt) %></span>
</div>
</div> </div>
<% if @orders.any? %> <% if @orders.any? %>
<div class="stats"> <div class="stats">
<h4>Total products</h4> <h4>Total products</h4>
Total: <br/><ul><li><%= @categories.map{|c| pluralize(c.count, c.category)}.join(", ")%></li></ul> Total:
<br/>
Specifics:<br/>
<ul> <ul>
<%= @products.map{ |p| content_tag(:li, pluralize(p.count, p.name)) }.join("\n").html_safe %> <li>
<%= @categories.map{|c| pluralize(c.count, c.category)}.to_sentence %>
</li>
</ul> </ul>
Specifics:
<%= content_tag :ul do %>
<% @products.each do |p| %>
<%= content_tag :li, pluralize(p.count, p.name) %>
<% end %>
<% end %>
<h4>All orders (<%= @user.orders_count %>)</h4> <h4>All orders (<%= @user.orders_count %>)</h4>
<table class="orders"><%= render @orders %></table> <table class="orders"><%= render @orders %></table>
<%= will_paginate @orders %> <%= will_paginate @orders %>

View file

@ -1,6 +1,7 @@
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
@ -24,8 +25,6 @@ Rails.application.routes.draw do
get 'dagschotel/:product_id' => 'users#update_dagschotel', as: 'dagschotel' get 'dagschotel/:product_id' => 'users#update_dagschotel', as: 'dagschotel'
end end
resources :user_avatar
resources :products do resources :products do
collection do collection do
get 'stock' => 'products#stock', as: 'stock' get 'stock' => 'products#stock', as: 'stock'

View file

@ -0,0 +1,5 @@
class AddCaloriesToProducts < ActiveRecord::Migration
def change
add_column :products, :calories, :int
end
end

View file

@ -0,0 +1,5 @@
class AddDeletedToProducts < ActiveRecord::Migration
def change
add_column :products, :deleted, :boolean, default: false
end
end

View file

@ -0,0 +1,5 @@
class RemoveDebtFromUsers < ActiveRecord::Migration
def change
remove_column :users, :debt, :int
end
end

View file

@ -0,0 +1,6 @@
class AddSomeIndexesToTables < ActiveRecord::Migration
def change
add_index :orders, :created_at
add_index :orders, :cancelled
end
end

View file

@ -11,7 +11,7 @@
# #
# 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: 20150630183223) do ActiveRecord::Schema.define(version: 20150827155036) do
create_table "order_items", force: :cascade do |t| create_table "order_items", force: :cascade do |t|
t.integer "order_id" t.integer "order_id"
@ -27,12 +27,14 @@ ActiveRecord::Schema.define(version: 20150630183223) do
t.boolean "cancelled", default: false t.boolean "cancelled", default: false
end end
add_index "orders", ["cancelled"], name: "index_orders_on_cancelled"
add_index "orders", ["created_at"], name: "index_orders_on_created_at"
add_index "orders", ["user_id", "created_at"], name: "index_orders_on_user_id_and_created_at" add_index "orders", ["user_id", "created_at"], name: "index_orders_on_user_id_and_created_at"
add_index "orders", ["user_id"], name: "index_orders_on_user_id" add_index "orders", ["user_id"], name: "index_orders_on_user_id"
create_table "products", force: :cascade do |t| create_table "products", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.integer "price_cents", default: 0, null: false t.integer "price_cents", default: 0, null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "avatar_file_name" t.string "avatar_file_name"
@ -40,7 +42,9 @@ ActiveRecord::Schema.define(version: 20150630183223) do
t.integer "avatar_file_size" t.integer "avatar_file_size"
t.datetime "avatar_updated_at" t.datetime "avatar_updated_at"
t.integer "category", default: 0 t.integer "category", default: 0
t.integer "stock", default: 0, null: false t.integer "stock", default: 0, null: false
t.integer "calories"
t.boolean "deleted", default: false
end end
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|

View file

@ -0,0 +1,7 @@
require 'test_helper'
class SessionsControllerTest < ActionController::TestCase
# test "the truth" do
# assert true
# end
end

View file

@ -10,30 +10,30 @@ class OrderIntegrationTest < ActionDispatch::IntegrationTest
sign_in users(:koelkast) sign_in users(:koelkast)
end end
test 'orders are saved for the right user' do # test 'orders are saved for the right user' do
visit new_user_order_path(users(:benji)) # visit new_user_order_path(users(:benji))
assert page.has_content? 'Order for benji' # assert page.has_content? 'Order for benji'
assert_difference "User.find(users(:benji).id).debt_cents", 240 do # assert_difference "User.find(users(:benji).id).debt_cents", 240 do
fill_in 'order_order_items_attributes_2_count', with: 2 # fill_in 'order_order_items_attributes_2_count', with: 2
click_button "Order!" # click_button "Order!"
end # end
end # end
test 'quickpay' do # test 'quickpay' do
assert_difference "User.find(users(:benji).id).debt_cents", User.find(users(:benji).id).dagschotel.price_cents do # assert_difference "User.find(users(:benji).id).debt_cents", User.find(users(:benji).id).dagschotel.price_cents do
visit user_quickpay_path(users(:benji)) # visit user_quickpay_path(users(:benji))
assert page.has_content? 'Success!' # assert page.has_content? 'Success!'
end # end
end # end
test 'cancelling quickpay' do # test 'cancelling quickpay' do
visit user_quickpay_path(users(:benji)) # visit user_quickpay_path(users(:benji))
assert_difference "User.find(users(:benji).id).debt_cents", -User.find(users(:benji).id).dagschotel.price_cents do # assert_difference "User.find(users(:benji).id).debt_cents", -User.find(users(:benji).id).dagschotel.price_cents do
click_link 'Undo' # click_link 'Undo'
assert page.has_content? 'Success!' # assert page.has_content? 'Success!'
end # end
end # end
end end

View file

@ -32,16 +32,6 @@ class UserTest < ActiveSupport::TestCase
@user = users(:benji) @user = users(:benji)
end end
test "debt behaves correctly" do
assert_equal @user.debt_cents, 0
assert_equal @user.debt, 0
@user.debt = 1.3
assert_equal @user.debt, 1.3
assert_equal @user.debt_cents, 130
end
test "to_param" do test "to_param" do
assert_equal @user.to_param, "#{@user.id}-benji" assert_equal @user.to_param, "#{@user.id}-benji"
end end