From 64038358723a99471303cdd3964ed68a6aaf59a6 Mon Sep 17 00:00:00 2001 From: benji Date: Sun, 20 Sep 2015 21:21:18 +0200 Subject: [PATCH] Use barcode to create new order --- app/assets/images/logo.png | Bin 0 -> 3354 bytes app/assets/javascripts/orders.js | 110 +++++++++--------- app/assets/stylesheets/orders.css.scss | 110 ++++++++++-------- app/controllers/orders_controller.rb | 7 +- app/controllers/products_controller.rb | 6 +- app/models/ability.rb | 2 + app/models/product.rb | 1 + app/models/user.rb | 16 ++- app/views/layouts/_header.html.haml | 2 +- app/views/orders/_price.html.haml | 7 -- app/views/orders/_products_modal.html.haml | 19 +++ app/views/orders/new.html.haml | 52 +++++++-- config/routes.rb | 6 +- .../20150919091214_add_barcode_to_products.rb | 5 + db/schema.rb | 3 +- spec/factories/products.rb | 1 + 16 files changed, 222 insertions(+), 125 deletions(-) create mode 100644 app/assets/images/logo.png delete mode 100644 app/views/orders/_price.html.haml create mode 100644 app/views/orders/_products_modal.html.haml create mode 100644 db/migrate/20150919091214_add_barcode_to_products.rb diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..102f3a311d9f26941c9a984622d9d875656747ed GIT binary patch literal 3354 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 k3`R 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