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/orders.js b/app/assets/javascripts/orders.js index 1581f19..c4ed965 100644 --- a/app/assets/javascripts/orders.js +++ b/app/assets/javascripts/orders.js @@ -2,64 +2,68 @@ // 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); - }); - $('.btn-dec').on('click', function() { - increment($(this), -1); - }); - $('.form_row').each(function(index, row) { - updateInput(row, false); - $(row).on('input', function() { - updateInput(row); - }); - }); - recalculate(); -}; + 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(); + }) -// Validate input, and then update -updateInput = function(row, useRecalculate) { - if (useRecalculate == null) { - useRecalculate = true; + $('#products_modal').on('hidden.bs.modal', function () { + $('#product_search').val(''); + }); + + increment_product = function(product_id) { + input = $("#current_order").find(".order_item_wrapper[data-product=" + product_id + "]").find("input[type=number]"); + $(input).val(parseInt($(input).val()) + 1).change(); } - cell = row.querySelector("input"); - if (!cell.validity.valid) { - if (parseInt(cell.value) > parseInt(cell.max)) { - cell.value = parseInt(cell.max); - } else { - cell.value = 0; + + $("#products_modal button").click(function() { + increment_product($(this).data("product")) + }) + + $("#from_barcode_form").on("ajax:before", function(xhr, settings) { + // Stuff you wanna do after sending form + }).on("ajax:success", function(data, status, xhr) { + if (status != null) { + increment_product(status["id"]) } + }).on("ajax:error", function(xhr, status, error) { + // Display an error or something, whatever + }).on("ajax:complete", function(xhr, status) { + $("#from_barcode_form")[0].reset(); + // Do more stuff + }) + + $('tr.order_item_wrapper').hide(); + $('tr.order_item_wrapper').filter(function() { + return parseInt($(this).find('[type=number]').val()) > 0; + }).show(); + + $('tr.order_item_wrapper input[type=number]').change(function() { + tr = $(this).closest('tr.order_item_wrapper') + $(tr).toggle(parseInt($(this).val()) > 0); + $(tr).find("td").last().html(((parseInt($(tr).data("price")) * parseInt($(this).val())) / 100.0).toFixed(2)) + recalculate(); + }) + + recalculate = function() { + /* Total Price */ + array = $('tr.order_item_wrapper').map(function() { + return parseInt($(this).data("price")) * parseInt($(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 parseInt($(this).val()) > 0; + }).length)); } - 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]) + recalculate(); } $(document).ready(ready); diff --git a/app/assets/stylesheets/orders.css.scss b/app/assets/stylesheets/orders.css.scss index 2583efa..b7f19f6 100644 --- a/app/assets/stylesheets/orders.css.scss +++ b/app/assets/stylesheets/orders.css.scss @@ -2,60 +2,78 @@ // 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 */ -} - -.form_row .btn-lg { - padding: 10px 10px; -} - -.form_row .caption { - h4 { - position: relative; - span { +#products_modal { + .modal-header { + h4 { + display: inline-block; + } + input { + margin-right: 20px; + } + } + .col-md-2 { + 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/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..956d5cf 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -31,9 +31,13 @@ class ProductsController < ApplicationController respond_with @product end + def from_barcode + render json: Product.find_by_barcode(params.require(:barcode)) + 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) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 28dac54..513a6af 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -4,6 +4,8 @@ class Ability def initialize(user) return unless user + can :from_barcode, Product + if user.admin? can :manage, :all elsif user.koelkast? diff --git a/app/models/product.rb b/app/models/product.rb index 4e8dd22..93abee4 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -28,6 +28,7 @@ class Product < ActiveRecord::Base 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 } + validates :barcode, presence: true, uniqueness: true scope :for_sale, -> { where deleted: false } diff --git a/app/models/user.rb b/app/models/user.rb index b98deb9..6459040 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,7 +44,19 @@ class User < ActiveRecord::Base end end - def debt - 42.15 + def balance + @balance || begin + headers = { + "Authorization" => "Token token=#{Rails.application.secrets.tab_api_key}", + "Content-type" => "application/json" + } + result = HTTParty.get(File.join(Rails.application.config.api_url, "users", "#{name}.json"), headers: headers) + + if result.code == 200 + JSON.parse(result.body)["balance"] + else + 0 + end + end end end diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 5b96e95..534e840 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -32,6 +32,6 @@ %li= link_to "Edit profile", edit_user_path(current_user) %li %p.navbar-text - Balance: #{euro_from_cents(current_user.debt)} + Balance: #{euro_from_cents(current_user.balance)} .visible-xs.navbar-form = render 'layouts/session_button' diff --git a/app/views/orders/_price.html.haml b/app/views/orders/_price.html.haml deleted file mode 100644 index 1939d7a..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 € - %span#order_price.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..0b493c2 --- /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 + .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 555be8c..a7c076b 100644 --- a/app/views/orders/new.html.haml +++ b/app/views/orders/new.html.haml @@ -1,9 +1,43 @@ -%h3 - Order for #{@user.name} -.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 +.center + .row + .col-md-6.col-md-offset-1.barcode-wrapper + %h1 Order for #{@user.name} + = form_tag from_barcode_products_path, id: "from_barcode_form", remote: true do + %input.center-block{ type: :number, name: :barcode, 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 + -# Huidige schuld: #{euro_from_cents @user.balance} + #current_order + .div.center + = image_tag "logo.png" + = form_for [@user, @order] do |f| + %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' +- p @order.errors diff --git a/config/routes.rb b/config/routes.rb index ddeb240..7cf5e89 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,7 +27,11 @@ Rails.application.routes.draw do end end - resources :products, only: [:new, :create, :index, :edit, :update] + resources :products, only: [:new, :create, :index, :edit, :update] do + collection do + post 'barcode' => 'products#from_barcode', as: :from_barcode + end + end resources :stocks, only: [:new, :create] get 'overview' => 'orders#overview', as: "orders" 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..6952895 --- /dev/null +++ b/db/migrate/20150919091214_add_barcode_to_products.rb @@ -0,0 +1,5 @@ +class AddBarcodeToProducts < ActiveRecord::Migration + def change + add_column :products, :barcode, :string, null: false, default: "" + end +end diff --git a/db/schema.rb b/db/schema.rb index f8a275a..6c24368 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150918190548) do +ActiveRecord::Schema.define(version: 20150919091214) do create_table "delayed_jobs", force: :cascade do |t| t.integer "priority", default: 0, null: false @@ -60,6 +60,7 @@ ActiveRecord::Schema.define(version: 20150918190548) do t.integer "stock", default: 0, null: false t.integer "calories" t.boolean "deleted", default: false + t.string "barcode", default: "", null: false end create_table "users", force: :cascade do |t| diff --git a/spec/factories/products.rb b/spec/factories/products.rb index 16dfb06..5748ee6 100644 --- a/spec/factories/products.rb +++ b/spec/factories/products.rb @@ -28,6 +28,7 @@ FactoryGirl.define do stock { 30 + rand(30) } calories { rand 20 } avatar { Identicon.data_url_for name } + sequence :barcode factory :invalid_product do name nil