diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 0000000..102f3a3 Binary files /dev/null and b/app/assets/images/logo.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e3a9f2d..f5b34af 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -15,3 +15,12 @@ //= require bootstrap //= require turbolinks //= require_tree . + +parseIntNaN = function(value) { + parsed_value = parseInt(value, 10); + if (isNaN(parsed_value)) { + return 0; + } else { + return parsed_value; + } +} diff --git a/app/assets/javascripts/increment.js b/app/assets/javascripts/increment.js new file mode 100644 index 0000000..23eee79 --- /dev/null +++ b/app/assets/javascripts/increment.js @@ -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); diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js index 1581f19..ae3f0c2 100644 --- a/app/assets/javascripts/orders.js +++ b/app/assets/javascripts/orders.js @@ -2,64 +2,75 @@ // All this logic will automatically be available in application.js. // You can use CoffeeScript in this file: http://coffeescript.org/ ready = function() { - $('.btn-inc').on('click', function() { - increment($(this), 1); + /* INITIALIZE */ + $('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); - }); - $('.form_row').each(function(index, row) { - updateInput(row, false); - $(row).on('input', function() { - updateInput(row); + + $("#products_modal button").click(function() { + increment_product($(this).data("product")) + }) + + /* BARCODE SCAN */ + $("#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(); -}; - -// 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); diff --git a/app/assets/stylesheets/admins.css.scss b/app/assets/stylesheets/admins.css.scss deleted file mode 100644 index 984fabc..0000000 --- a/app/assets/stylesheets/admins.css.scss +++ /dev/null @@ -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/ diff --git a/app/assets/stylesheets/callbacks.css.scss b/app/assets/stylesheets/callbacks.css.scss deleted file mode 100644 index e4c4d53..0000000 --- a/app/assets/stylesheets/callbacks.css.scss +++ /dev/null @@ -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/ diff --git a/app/assets/stylesheets/orders.css.scss b/app/assets/stylesheets/orders.css.scss index 2583efa..a173404 100644 --- a/app/assets/stylesheets/orders.css.scss +++ b/app/assets/stylesheets/orders.css.scss @@ -2,60 +2,81 @@ // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ -.big-form-button { - height: 65px; - width: 65px; -} - -.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; +.barcode-wrapper { + font-size: 300%; + input { + width: 100%; } } -.form_row input::-webkit-outer-spin-button, -.form_row input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ +#products_modal { + .modal-header { + h4 { + display: inline-block; + } + input { + margin-right: 20px; + } + } } -.form_row .btn-lg { - padding: 10px 10px; -} - -.form_row .caption { - h4 { - position: relative; - span { +#product_buttons { + .col-md-2, .col-md-3 { + margin-bottom: 20px; + } + button.product { + width: 100%; + p { overflow: hidden; text-overflow: ellipsis; - max-width: 100%; - display: inline-block; - padding-right: 50px; } - small { - margin-left: -45px; - position: absolute; - top: 5px; + img { + max-width: 100%; + margin-left: auto; + 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: "€"; + } + } + } } diff --git a/app/assets/stylesheets/sessions.css.scss b/app/assets/stylesheets/sessions.css.scss deleted file mode 100644 index 7bef9cf..0000000 --- a/app/assets/stylesheets/sessions.css.scss +++ /dev/null @@ -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/ diff --git a/app/assets/stylesheets/stock.css.scss b/app/assets/stylesheets/stock.css.scss deleted file mode 100644 index f5d1f77..0000000 --- a/app/assets/stylesheets/stock.css.scss +++ /dev/null @@ -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/ diff --git a/app/assets/stylesheets/stock_entry.css.scss b/app/assets/stylesheets/stock_entry.css.scss new file mode 100644 index 0000000..616d137 --- /dev/null +++ b/app/assets/stylesheets/stock_entry.css.scss @@ -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; */ + } + } +} diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss index 8e82fd4..4321f82 100644 --- a/app/assets/stylesheets/users.css.scss +++ b/app/assets/stylesheets/users.css.scss @@ -4,12 +4,12 @@ //signin -.sign-in{ - .checkbox label{ - padding-left: 0px; +.sign-in { + .checkbox label { + padding-left: 0px; } - #user_remember_me{ - margin-left: 10px; + #user_remember_me { + margin-left: 10px; } } @@ -17,7 +17,7 @@ padding: 0px; min-height: 280px; border: 3px solid #333; - .header{ + .header { border-bottom: 3px solid #333; text-align: center; color: #fff; @@ -26,19 +26,19 @@ margin: 0px; } - .caption{ - .avatar{ + .caption { + .avatar { float: right; height: 70px; width: 70px; } } - .footer{ + .footer { width:80%; border-top: 1px dashed #333; margin:10%; margin-bottom: 5px; - .btn{ + .btn { width:100%; margin-top:10px; margin-bottom:0px; diff --git a/app/assets/stylesheets/welcome.css.scss b/app/assets/stylesheets/welcome.css.scss deleted file mode 100644 index 77ce11a..0000000 --- a/app/assets/stylesheets/welcome.css.scss +++ /dev/null @@ -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/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 726cf50..bee3a7b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,5 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception - check_authorization rescue_from CanCan::AccessDenied do |exception| redirect_to root_path, flash: { error: exception.message } diff --git a/app/controllers/barcodes_controller.rb b/app/controllers/barcodes_controller.rb new file mode 100644 index 0000000..c0fcf57 --- /dev/null +++ b/app/controllers/barcodes_controller.rb @@ -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 diff --git a/app/controllers/callbacks_controller.rb b/app/controllers/callbacks_controller.rb index 9f8b2cc..dcd8663 100644 --- a/app/controllers/callbacks_controller.rb +++ b/app/controllers/callbacks_controller.rb @@ -1,6 +1,4 @@ class CallbacksController < Devise::OmniauthCallbacksController - skip_authorization_check - def zeuswpi @user = User.from_omniauth(request.env["omniauth.auth"]) sign_in_and_redirect @user diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index e9df136..8511919 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -3,10 +3,8 @@ class OrdersController < ApplicationController load_and_authorize_resource :order, through: :user, shallow: true 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.each do |p| - @order.order_items.build(product: p) - end + @products = Product.all.for_sale.order(:name) + @order.products << @products end def create @@ -14,6 +12,7 @@ class OrdersController < ApplicationController flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!" redirect_to root_path else + @products = Product.all.for_sale.order(:name) render 'new' end end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index ab472b4..d2f2874 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -3,15 +3,26 @@ class ProductsController < ApplicationController respond_to :html, :js - def new - end - def create if @product.save flash[:success] = "Product created!" - redirect_to products_path + redirect_to barcode_products_path 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 @@ -28,12 +39,15 @@ class ProductsController < ApplicationController def update @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 private 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 diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb deleted file mode 100644 index a50a3b3..0000000 --- a/app/controllers/sessions_controller.rb +++ /dev/null @@ -1,3 +0,0 @@ -class SessionsController < Devise::SessionsController - skip_authorization_check -end diff --git a/app/controllers/stocks_controller.rb b/app/controllers/stocks_controller.rb deleted file mode 100644 index 8c48044..0000000 --- a/app/controllers/stocks_controller.rb +++ /dev/null @@ -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 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 11dc714..a81a85b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -18,16 +18,6 @@ class UsersController < ApplicationController 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 @dagschotel = @user.dagschotel @@ -35,14 +25,6 @@ class UsersController < ApplicationController @categories = Product.categories end - def update_dagschotel - @user.dagschotel = Product.find(params[:product_id]) - @user.save - - flash[:success] = "Succesfully updated dagschotel" - redirect_to @user - end - def quickpay order = @user.orders.build order.order_items.build(count: 1, product: @user.dagschotel) @@ -57,7 +39,7 @@ class UsersController < ApplicationController private def user_params - params.require(:user).permit(:avatar, :private) + params.require(:user).permit(:avatar, :private, :dagschotel_id) end def init diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb index 381b748..b351b34 100644 --- a/app/controllers/welcome_controller.rb +++ b/app/controllers/welcome_controller.rb @@ -1,6 +1,12 @@ class WelcomeController < ApplicationController - skip_authorization_check + skip_before_filter :verify_authenticity_token, only: :token_sign_in def index 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 diff --git a/app/form_builders/formatted_form_builder.rb b/app/form_builders/formatted_form_builder.rb index ced2d87..9382ee7 100644 --- a/app/form_builders/formatted_form_builder.rb +++ b/app/form_builders/formatted_form_builder.rb @@ -37,7 +37,12 @@ class FormattedFormBuilder < ActionView::Helpers::FormBuilder options[:value] = number_with_precision(options[:value], precision: 2) 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 diff --git a/app/models/ability.rb b/app/models/ability.rb index 28dac54..416474c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -4,10 +4,14 @@ class Ability def initialize(user) return unless user + can :from_barcode, Product + if user.admin? can :manage, :all elsif user.koelkast? - can :manage, Order + can :manage, Order do |order| + !order.try(:user).try(:private) + end can :quickpay, User else can :read, :all diff --git a/app/models/barcode.rb b/app/models/barcode.rb new file mode 100644 index 0000000..c3e34ed --- /dev/null +++ b/app/models/barcode.rb @@ -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 diff --git a/app/models/order.rb b/app/models/order.rb index 5866cea..912f124 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -36,7 +36,7 @@ class Order < ActiveRecord::Base private 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 def create_api_job diff --git a/app/models/product.rb b/app/models/product.rb index 4e8dd22..2128b62 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -21,10 +21,12 @@ class Product < ActiveRecord::Base include Avatarable has_many :order_items + has_many :barcodes + accepts_nested_attributes_for :barcodes 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 :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 } @@ -39,8 +41,4 @@ class Product < ActiveRecord::Base if value.is_a? String then value.sub!(',', '.') end self.price_cents = (value.to_f * 100).to_int end - - def take_out_of_sale! - update_attribute :deleted, true - end end diff --git a/app/models/user.rb b/app/models/user.rb index 6459040..9c8f7bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,11 +6,6 @@ # created_at :datetime # updated_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 # dagschotel_id :integer # avatar_file_name :string @@ -28,12 +23,14 @@ class User < ActiveRecord::Base include Statistics, Avatarable, FriendlyId friendly_id :name, use: :finders - devise :database_authenticatable, :omniauthable, :omniauth_providers => [:zeuswpi] + devise :omniauthable, :omniauth_providers => [:zeuswpi] has_many :orders, -> { includes :products } has_many :products, through: :orders belongs_to :dagschotel, class_name: 'Product' + validates :dagschotel, presence: true, if: -> { dagschotel_id } + scope :members, -> { where koelkast: false } scope :publik, -> { where private: false } diff --git a/app/views/application/_flash.html.haml b/app/views/application/_flash.html.haml index bb03eaf..8f535e8 100644 --- a/app/views/application/_flash.html.haml +++ b/app/views/application/_flash.html.haml @@ -1,26 +1,26 @@ #flash - if flash[:error] .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! = flash[:error] - if flash[:success] .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! = raw flash[:success] - if flash[:notice] .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! = flash[:notice] - if flash[:warning] .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! = flash[:warning] - if flash[:alert] .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! = flash[:alert] diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 0ca4b84..23c04c6 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,10 +1,8 @@ -%h2 Sign in -= render partial: 'flash' += content_for :title, "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.password_field :password - if devise_mapping.rememberable? = f.check_box :remember_me = f.submit "Sign in" -= render "devise/shared/links" diff --git a/app/views/devise/shared/_links.html.haml b/app/views/devise/shared/_links.html.haml deleted file mode 100644 index 4168feb..0000000 --- a/app/views/devise/shared/_links.html.haml +++ /dev/null @@ -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/ diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 2391e6d..56ebdcb 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -2,7 +2,7 @@ .container-fluid / Brand and toggle get grouped for better mobile display .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 @@ -11,11 +11,7 @@ - unless current_user && current_user.koelkast? .collapse.navbar-collapse .hidden-xs.navbar-form.navbar-right - .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" + = render 'layouts/session_button' %ul.nav.navbar-nav.navbar-right %li= mail_to "tab@zeus.ugent.be", "Send feedback" - if user_signed_in? @@ -26,26 +22,15 @@ %span.caret %ul.dropdown-menu{role: "menu"} %li= link_to "List", products_path - %li= link_to "Add product" , new_product_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= link_to "Add product" , barcode_products_path %li.dropdown %a.dropdown-toggle{"data-toggle" => "dropdown", href: "#"} Logged in as #{current_user.name} %b.caret %ul.dropdown-menu - %li= link_to "Edit avatar", edit_user_path(current_user) + %li= link_to "Edit profile", edit_user_path(current_user) %li %p.navbar-text Balance: #{euro_from_cents(current_user.balance)} .visible-xs.navbar-form - .form-group - - 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" + = render 'layouts/session_button' diff --git a/app/views/layouts/_session_button.html.haml b/app/views/layouts/_session_button.html.haml new file mode 100644 index 0000000..51211b8 --- /dev/null +++ b/app/views/layouts/_session_button.html.haml @@ -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" diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index b1fa7ef..0f7a414 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -9,6 +9,8 @@ %body = render 'layouts/header' .container + %h2= yield :title + = render partial: 'flash' = yield = render 'layouts/footer' = debug(params) if Rails.env.development? diff --git a/app/views/order_items/_order_item.html.haml b/app/views/order_items/_order_item.html.haml index 63281a3..97c01aa 100644 --- a/app/views/order_items/_order_item.html.haml +++ b/app/views/order_items/_order_item.html.haml @@ -1,5 +1,5 @@ .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_image = image_tag product.avatar diff --git a/app/views/orders/_price.html.haml b/app/views/orders/_price.html.haml deleted file mode 100644 index 51bef93..0000000 --- a/app/views/orders/_price.html.haml +++ /dev/null @@ -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 diff --git a/app/views/orders/_products_modal.html.haml b/app/views/orders/_products_modal.html.haml new file mode 100644 index 0000000..140f390 --- /dev/null +++ b/app/views/orders/_products_modal.html.haml @@ -0,0 +1,19 @@ +#products_modal.modal{ tabindex: -1 } + .modal-dialog.modal-lg + .modal-content + .modal-header + %button.close{ data: { dismiss: :modal } } + %span × + %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 diff --git a/app/views/orders/new.html.haml b/app/views/orders/new.html.haml index 6855558..13ca8ee 100644 --- a/app/views/orders/new.html.haml +++ b/app/views/orders/new.html.haml @@ -1,9 +1,42 @@ -%h3 - Order for #{@user.name} (Huidige schuld: #{euro_from_cents(@user.balance)}) .row - = f_form_for [@user, @order] do |f| - = f.error_messages - .col-md-12 - = f.fields_for :order_items do |op_field| - = render op_field.object, f: op_field, product: op_field.object.product - = render 'orders/price', f: f + .col-md-6.col-md-offset-1.barcode-wrapper + .center + %h1 Order for #{@user.name} + = form_tag nil, id: "from_barcode_form" do + %input.center-block{ type: :number, name: :id, autofocus: true } + = "- 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' diff --git a/app/views/orders/overview.html.haml b/app/views/orders/overview.html.haml index 734fbfe..28c453e 100644 --- a/app/views/orders/overview.html.haml +++ b/app/views/orders/overview.html.haml @@ -1,6 +1,4 @@ -= render partial: 'flash' .warning.center %h1 TESTFASE | GELIEVE STREEPJES TE BLIJVEN ZETTEN | TESTFASE .row - - @users.each do |user| - = render 'users/new_order', user: user + = render @users diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml index 258a35b..6cf7dfe 100644 --- a/app/views/products/_form.html.haml +++ b/app/views/products/_form.html.haml @@ -1,11 +1,11 @@ -.row - .col-md-6.col-md-offset-3.sign-in - = f_form_for @product, html: { multipart: true } do |f| - = f.error_messages - = f.text_field :name - = f.price_field :price - = f.collection_select :category, Product.categories.keys - = f.number_field :stock - = f.number_field :calories - = f.file_field :avatar - = f.submit += f_form_for @product, html: { multipart: true } do |f| + = f.error_messages + = f.text_field :name + = f.price_field :price + = f.collection_select :category, Product.categories.keys + = f.number_field :stock + = f.number_field :calories + = f.file_field :avatar + = f.fields_for :barcodes do |ff| + = ff.number_field :code, readonly: true + = f.submit diff --git a/app/views/products/_links.html.haml b/app/views/products/_links.html.haml index e07ba1f..b2f3eba 100644 --- a/app/views/products/_links.html.haml +++ b/app/views/products/_links.html.haml @@ -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 "Delete", product_path(product), method: :delete, class: "btn btn-danger", data: {confirm: 'Are you sure?'} - if controller_name == 'users' .product_dagschotel - 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 - = link_to "Huidige dagschotel", dagschotel_user_path(current_user, product), class: "btn btn-success", disabled: true + %span.btn.btn-success= "Current dagschotel" diff --git a/app/views/products/_product.html.haml b/app/views/products/_product.html.haml index 4527712..db6c08b 100644 --- a/app/views/products/_product.html.haml +++ b/app/views/products/_product.html.haml @@ -1,6 +1,6 @@ .col-md-3 .thumbnail.pic - .form_row_image + .center = image_tag product.avatar .caption = kcal_tag product.calories diff --git a/app/views/products/barcode.html.haml b/app/views/products/barcode.html.haml new file mode 100644 index 0000000..6049f10 --- /dev/null +++ b/app/views/products/barcode.html.haml @@ -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 } diff --git a/app/views/products/edit.html.haml b/app/views/products/edit.html.haml index 4089201..26ece76 100644 --- a/app/views/products/edit.html.haml +++ b/app/views/products/edit.html.haml @@ -1,2 +1,2 @@ -%h1 Update product += content_for :title, "Update product" = render "form" diff --git a/app/views/products/index.html.haml b/app/views/products/index.html.haml index 936e312..2434c33 100644 --- a/app/views/products/index.html.haml +++ b/app/views/products/index.html.haml @@ -1,3 +1,2 @@ -%h1 All products -= render partial: 'flash' += content_for :title, "All products" = render 'products/index' diff --git a/app/views/products/link.html.haml b/app/views/products/link.html.haml new file mode 100644 index 0000000..2129714 --- /dev/null +++ b/app/views/products/link.html.haml @@ -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' diff --git a/app/views/products/new.html.haml b/app/views/products/new.html.haml index 82f9b11..9761184 100644 --- a/app/views/products/new.html.haml +++ b/app/views/products/new.html.haml @@ -1,2 +1,2 @@ -%h1 New product += content_for :title, "New product" = render "form" diff --git a/app/views/products/stock_entry.html.haml b/app/views/products/stock_entry.html.haml new file mode 100644 index 0000000..16f229d --- /dev/null +++ b/app/views/products/stock_entry.html.haml @@ -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" diff --git a/app/views/products_list/_product_row.html.haml b/app/views/products_list/_product_row.html.haml index f0aa11d..92d2cc6 100644 --- a/app/views/products_list/_product_row.html.haml +++ b/app/views/products_list/_product_row.html.haml @@ -1,9 +1,9 @@ -%tr{:id => "products_row_#{dom_id(product)}"} - %td= image_tag product.avatar(:small) +%tr{id: "products_row_#{dom_id(product)}"} + %td= link_to image_tag(product.avatar(:small)), edit_product_path(product) %td= product.name %td= euro(product.price) %td= product.stock %td - %span{:class => "glyphicon #{product.deleted ? "glyphicon-check" : "glyphicon-unchecked"}"} + %span{class: "glyphicon #{product.deleted ? "glyphicon-check" : "glyphicon-unchecked"}"} %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 diff --git a/app/views/products_list/listview.html.haml b/app/views/products_list/listview.html.haml index a98332c..918adfa 100644 --- a/app/views/products_list/listview.html.haml +++ b/app/views/products_list/listview.html.haml @@ -1,9 +1,8 @@ += content_for :title, "Products" #products-errors .row.products .col-md-8.col-md-offset-2 - %h1 Products - = render partial: 'flash' - = link_to "Add Stock", new_stock_path, class: "btn btn-default" + = link_to "Add products", barcode_products_path, class: "btn btn-default" %table#products-table.table.table-striped %tr %th diff --git a/app/views/stocks/_errors.html.haml b/app/views/stocks/_errors.html.haml deleted file mode 100644 index 74cd463..0000000 --- a/app/views/stocks/_errors.html.haml +++ /dev/null @@ -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 diff --git a/app/views/stocks/new.html.haml b/app/views/stocks/new.html.haml deleted file mode 100644 index 64b1229..0000000 --- a/app/views/stocks/new.html.haml +++ /dev/null @@ -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' diff --git a/app/views/users/_new_order.html.haml b/app/views/users/_new_order.html.haml deleted file mode 100644 index 2fa23c3..0000000 --- a/app/views/users/_new_order.html.haml +++ /dev/null @@ -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) diff --git a/app/views/users/_user.html.haml b/app/views/users/_user.html.haml index 1e7f1cc..2fa23c3 100644 --- a/app/views/users/_user.html.haml +++ b/app/views/users/_user.html.haml @@ -1,8 +1,6 @@ -%tr - %td= user.id - %td= image_tag user.avatar(:small) - %td= user.name - %td= euro(user.debt) - - if current_user.admin? - %td - = link_to "Delete", user_path(user), method: :delete, class: "btn btn-danger", data: { confirm: "Are you sure?" } +.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) diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index e132c46..e0884e3 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -1,4 +1,3 @@ -= render 'flash' .row = render 'sidebar' .col-sm-9 diff --git a/app/views/users/edit_dagschotel.html.haml b/app/views/users/edit_dagschotel.html.haml index 154245a..e25e53b 100644 --- a/app/views/users/edit_dagschotel.html.haml +++ b/app/views/users/edit_dagschotel.html.haml @@ -1,3 +1,2 @@ -%h3 - Choose new Dagschotel - = render 'products/index' += content_for :title, "Choose new Dagschotel" += render 'products/index' diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml deleted file mode 100644 index b8f4e11..0000000 --- a/app/views/users/index.html.haml +++ /dev/null @@ -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 diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index c2b56f5..5469c20 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,4 +1,3 @@ -= render partial: 'flash' .row = render 'sidebar' #user_info.col-sm-9 diff --git a/app/views/welcome/index.html.haml b/app/views/welcome/index.html.haml index b317596..8537b15 100644 --- a/app/views/welcome/index.html.haml +++ b/app/views/welcome/index.html.haml @@ -1,7 +1,4 @@ -%h2 Login -= render 'flash' += content_for :title, "Login" If this is the first time you log in, an account will be created for you. %div - %br/ = link_to "Sign in with Zeus WPI account.", omniauth_authorize_path("user", "zeuswpi"), class: "btn btn-large btn-primary" - %br/ diff --git a/config/routes.rb b/config/routes.rb index d56ffdd..2b90cc8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,10 +1,9 @@ Rails.application.routes.draw do - devise_for :users, controllers: { - omniauth_callbacks: "callbacks", - sessions: "sessions" - } + devise_for :users, controllers: { omniauth_callbacks: "callbacks" } 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 root to: 'welcome#index' end @@ -18,17 +17,21 @@ Rails.application.routes.draw do end end - resources :users, only: [:show, :edit, :update, :index, :destroy] do + resources :users, only: [:show, :edit, :update] do resources :orders, only: [:new, :create, :destroy] member do get 'quickpay' => 'users#quickpay' get 'dagschotel/edit' => 'users#edit_dagschotel', as: 'edit_dagschotel' - get 'dagschotel/:product_id' => 'users#update_dagschotel', as: 'dagschotel' end end - resources :products, only: [:new, :create, :index, :edit, :update] - resources :stocks, only: [:new, :create] + resources :products, only: [:create, :index, :edit, :update] do + 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" end diff --git a/config/secrets.yml b/config/secrets.yml index 4340ef3..2c88896 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -16,6 +16,7 @@ development: omniauth_client_secret: blargh access_token: "token" tab_api_key: "HriaktSIhRaB5CJzD71uLQ==" + koelkast_token: "" test: secret_key_base: 961437e28e7d6055ffaad9cf1f8d614354f57f10cb2d7601c9d6ede72a03b9c9535ad9e63507e3eb31252c4895970a63117493408f2e9a46c7a0c4a5a7836b81 @@ -29,3 +30,4 @@ production: omniauth_client_secret: "" access_token: "" tab_api_key: "" + koelkast_token: "" diff --git a/db/migrate/20150918190548_remove_devise_fields_from_users.rb b/db/migrate/20150918190548_remove_devise_fields_from_users.rb new file mode 100644 index 0000000..ebbfe72 --- /dev/null +++ b/db/migrate/20150918190548_remove_devise_fields_from_users.rb @@ -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 diff --git a/db/migrate/20150919091214_add_barcode_to_products.rb b/db/migrate/20150919091214_add_barcode_to_products.rb new file mode 100644 index 0000000..d0bf2af --- /dev/null +++ b/db/migrate/20150919091214_add_barcode_to_products.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 01c418f..a8562ce 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,16 @@ # # 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| t.integer "priority", default: 0, null: false @@ -66,11 +75,6 @@ ActiveRecord::Schema.define(version: 20150917165758) do t.datetime "created_at" t.datetime "updated_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.integer "dagschotel_id" t.string "avatar_file_name" diff --git a/spec/controllers/products_controller_spec.rb b/spec/controllers/products_controller_spec.rb index e3af36b..c9808b5 100644 --- a/spec/controllers/products_controller_spec.rb +++ b/spec/controllers/products_controller_spec.rb @@ -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 before :each do @admin = create :admin sign_in @admin end + ######### + # NEW # + ######### + describe 'GET new' do it 'should render the form' do get :new expect(response).to render_template(:new) expect(response).to have_http_status(200) 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 + ########## + # POST # + ########## + describe 'POST create' do context 'successfull' do it 'should create a product' do @@ -22,7 +45,7 @@ describe ProductsController, type: :controller do it 'should redirect to index page' do post :create, product: attributes_for(:product) - expect(response).to redirect_to action: :index + expect(response).to redirect_to action: :barcode end end @@ -35,11 +58,15 @@ describe ProductsController, type: :controller do it 'should render form' do post :create, product: attributes_for(:invalid_product) - expect(response).to render_template(:new) + expect(response).to render_template(:link) end end end + ########### + # INDEX # + ########### + describe 'GET index' do it 'should load all the products' do product = create :product @@ -48,6 +75,10 @@ describe ProductsController, type: :controller do end end + ########## + # EDIT # + ########## + describe 'GET edit' do before :each do @product = create :product @@ -65,6 +96,10 @@ describe ProductsController, type: :controller do end end + ############ + # UPDATE # + ############ + describe 'PUT update' do before :each do @product = create :product @@ -75,12 +110,32 @@ describe ProductsController, type: :controller do expect(assigns :product).to eq(@product) 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 it 'should not update attributes' do - old_attributes = @product.reload.attributes + old_attributes = @product.attributes put :update, id: @product, product: attributes_for(:invalid_product) expect(@product.reload.attributes).to eq(old_attributes) 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 diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 8dcd250..d394019 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -1,5 +1,10 @@ -require 'identicon' -require 'faker' +# quickpay_user GET /users/:id/quickpay(.:format) users#quickpay +# 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 before :each do @@ -7,6 +12,10 @@ describe UsersController, type: :controller do sign_in @user end + ########## + # SHOW # + ########## + describe 'GET show' do before :each do get :show, id: @user @@ -22,6 +31,10 @@ describe UsersController, type: :controller do end end + ########## + # EDIT # + ########## + describe 'GET edit' do before :each do get :edit, id: @user @@ -36,6 +49,10 @@ describe UsersController, type: :controller do end end + ############ + # UPDATE # + ############ + describe 'PUT update' do it 'should load the correct user' do 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 } expect(@user.reload.private).to be new_private 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 - describe 'GET index' do - before :each do - 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 + ##################### + # EDIT_DAGSCHOTEL # + ##################### describe 'GET edit_dagschotel' do it 'should render the page' do @@ -73,12 +85,4 @@ describe UsersController, type: :controller do expect(response).to have_http_status(200) 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 diff --git a/spec/factories/barcodes.rb b/spec/factories/barcodes.rb new file mode 100644 index 0000000..2bada88 --- /dev/null +++ b/spec/factories/barcodes.rb @@ -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 diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 3859e70..0225022 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -6,11 +6,6 @@ # created_at :datetime # updated_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 # dagschotel_id :integer # avatar_file_name :string diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 67473a6..c480786 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -5,37 +5,44 @@ describe User do subject(:ability){ Ability.new(user) } let(:user) { nil} + # Admin describe 'as admin' do 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, OrderItem.new) } + it{ should be_able_to(:manage, Product.new) } it{ should be_able_to(:manage, Stock.new) } it{ should be_able_to(:manage, User.new) } end + # Normal User describe 'as normal user' do 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(: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(: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_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 describe 'as koelkast' do let(:user) { create :koelkast } 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, User.new) } end diff --git a/spec/models/barcode_spec.rb b/spec/models/barcode_spec.rb new file mode 100644 index 0000000..d2389d7 --- /dev/null +++ b/spec/models/barcode_spec.rb @@ -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 diff --git a/spec/models/order_item_spec.rb b/spec/models/order_item_spec.rb index 33e0931..72a2cb6 100644 --- a/spec/models/order_item_spec.rb +++ b/spec/models/order_item_spec.rb @@ -14,14 +14,20 @@ describe OrderItem do expect(order_item).to be_valid end - describe 'validations' do + ############ + # FIELDS # + ############ + + describe 'fields' do before :each do @order_item = create :order_item end - it 'product should be present' do - @order_item.product = nil - expect(@order_item).to_not be_valid + describe 'product' do + it 'should be present' do + @order_item.product = nil + expect(@order_item).to_not be_valid + end end describe 'count' do @@ -34,10 +40,21 @@ describe OrderItem do @order_item.count = -5 expect(@order_item).to_not be_valid 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 - describe 'product stock' do + ############### + # CALLBACKS # + ############### + + describe 'stock change' do before :each do @product = create :product @count = rand 10 @@ -48,7 +65,7 @@ describe OrderItem do expect{ @order_item.save }.to change{ @product.stock }.by(-@count) end - it 'should increment on cancel' do + it 'should increment on destroy' do @order_item.save expect{ @order_item.destroy }.to change{ @product.stock }.by(@count) end diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 380541f..3e46b83 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -20,16 +20,58 @@ describe Order do expect(@order).to be_valid end - describe 'price' 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.valid? - expect(@order.price_cents).to eq(sum) + ############ + # FIELDS # + ############ + + describe 'fields' do + describe 'user' do + it { Order.reflect_on_association(:user).macro.should eq(:belongs_to) } + it 'should be present' do + @order.user = nil + 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 + + ############### + # 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 diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb index 36e3bee..e009912 100644 --- a/spec/models/product_spec.rb +++ b/spec/models/product_spec.rb @@ -26,23 +26,38 @@ describe Product do expect(@product).to be_valid end - describe 'validations' do - it 'name should be present' do - @product.name = '' - expect(@product).to_not be_valid - end + ############ + # FIELDS # + ############ - describe 'price' do - it 'should be positive' do - @product.price = -5 + describe 'fields' do + describe 'name' do + it 'should be present' do + @product.name = nil expect(@product).to_not be_valid end - it 'should be saved correctly' do - @product.price = 1.20 - @product.save - expect(@product.reload.price).to eq(1.20) - expect(@product.reload.price_cents).to eq(120) + it 'shold be unique' do + expect(build :product, name: @product.name).to_not be_valid + end + end + + 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 @@ -55,11 +70,13 @@ describe Product do it 'should be positive' do @product.stock = -5 expect(@product).to_not be_valid + @product.stock = 0 + expect(@product).to be_valid end end describe 'calories' do - it 'should not be present' do + it 'does not have to be present' do @product.calories = nil expect(@product).to be_valid end @@ -70,9 +87,28 @@ describe Product do end end - it 'avatar should be present' do - @product.avatar = nil - expect(@product).to_not be_valid + describe 'avatar' do + it 'should be present' do + @product.avatar = nil + expect(@product).to_not be_valid + 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 diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d68a59a..9ed782f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -6,11 +6,6 @@ # created_at :datetime # updated_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 # dagschotel_id :integer # avatar_file_name :string @@ -32,4 +27,23 @@ describe User do it 'has a valid factory' do expect(@user).to be_valid 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