Use barcode to create new order
This commit is contained in:
parent
ec2b516727
commit
6403835872
BIN
app/assets/images/logo.png
Normal file
BIN
app/assets/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
|
@ -2,64 +2,68 @@
|
||||||
// All this logic will automatically be available in application.js.
|
// All this logic will automatically be available in application.js.
|
||||||
// You can use CoffeeScript in this file: http://coffeescript.org/
|
// You can use CoffeeScript in this file: http://coffeescript.org/
|
||||||
ready = function() {
|
ready = function() {
|
||||||
$('.btn-inc').on('click', function() {
|
products_ordered = $('#product_search').keyup(function () {
|
||||||
increment($(this), 1);
|
var rex = new RegExp($(this).val(), 'i');
|
||||||
});
|
$('[data-name]').hide();
|
||||||
$('.btn-dec').on('click', function() {
|
$('[data-name]').filter(function () {
|
||||||
increment($(this), -1);
|
return rex.test($(this).data("name"));
|
||||||
});
|
}).show();
|
||||||
$('.form_row').each(function(index, row) {
|
})
|
||||||
updateInput(row, false);
|
|
||||||
$(row).on('input', function() {
|
|
||||||
updateInput(row);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
recalculate();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate input, and then update
|
$('#products_modal').on('hidden.bs.modal', function () {
|
||||||
updateInput = function(row, useRecalculate) {
|
$('#product_search').val('');
|
||||||
if (useRecalculate == null) {
|
});
|
||||||
useRecalculate = true;
|
|
||||||
|
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) {
|
$("#products_modal button").click(function() {
|
||||||
if (parseInt(cell.value) > parseInt(cell.max)) {
|
increment_product($(this).data("product"))
|
||||||
cell.value = parseInt(cell.max);
|
})
|
||||||
} else {
|
|
||||||
cell.value = 0;
|
$("#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) {
|
recalculate();
|
||||||
counter = parseInt($(row).find('.row_counter').val())
|
|
||||||
$(row).find('.btn-dec').prop('disabled', counter === 0)
|
|
||||||
$(row).find('.btn-inc').prop('disabled', counter === parseInt($(row).find('.row_counter').attr('max')))
|
|
||||||
};
|
|
||||||
|
|
||||||
recalculate = function() {
|
|
||||||
sum = 0
|
|
||||||
$('.row_counter').each(function(i, value) { sum += parseInt($(value).val()) * parseInt($(value).data('price')) })
|
|
||||||
return $('#order_price').html((sum / 100.0).toFixed(2))
|
|
||||||
};
|
|
||||||
|
|
||||||
increment = function(button, n) {
|
|
||||||
row = $(button).closest('.form_row')
|
|
||||||
|
|
||||||
// Fix the counter
|
|
||||||
counter = $(row).find('.row_counter')
|
|
||||||
value = parseInt(counter.val())
|
|
||||||
if (isNaN(value)) {
|
|
||||||
value = 0
|
|
||||||
}
|
|
||||||
counter.val(value + n)
|
|
||||||
|
|
||||||
updateInput(row[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(ready);
|
$(document).ready(ready);
|
||||||
|
|
|
@ -2,60 +2,78 @@
|
||||||
// They will automatically be included in application.css.
|
// They will automatically be included in application.css.
|
||||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||||
|
|
||||||
.big-form-button {
|
.barcode-wrapper {
|
||||||
height: 65px;
|
font-size: 300%;
|
||||||
width: 65px;
|
input {
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
.form-control.big-form-field {
|
|
||||||
height: 65px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form_row_image {
|
|
||||||
height: 100px;
|
|
||||||
width: 100px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
position: relative;
|
|
||||||
img {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
margin: auto;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form_row input::-webkit-outer-spin-button,
|
#products_modal {
|
||||||
.form_row input::-webkit-inner-spin-button {
|
.modal-header {
|
||||||
-webkit-appearance: none;
|
h4 {
|
||||||
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
input {
|
||||||
.form_row .btn-lg {
|
margin-right: 20px;
|
||||||
padding: 10px 10px;
|
}
|
||||||
}
|
}
|
||||||
|
.col-md-2 {
|
||||||
.form_row .caption {
|
margin-bottom: 20px;
|
||||||
h4 {
|
}
|
||||||
position: relative;
|
button.product {
|
||||||
span {
|
width: 100%;
|
||||||
|
p {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
padding-right: 50px;
|
|
||||||
}
|
}
|
||||||
small {
|
img {
|
||||||
margin-left: -45px;
|
max-width: 100%;
|
||||||
position: absolute;
|
margin-left: auto;
|
||||||
top: 5px;
|
margin-right: auto;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#order_price {
|
|
||||||
width: 50px;
|
#current_order {
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 10px;
|
||||||
|
div.center {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.order_item_wrapper {
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-top: 1px dashed;
|
||||||
|
border-collapse: separate;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
td:first-child {
|
||||||
|
width: 40px;
|
||||||
|
input {
|
||||||
|
text-align: right;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr:last-child td {
|
||||||
|
border-top: 1px dashed;
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
tr.margin {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
td.euro {
|
||||||
|
text-align: right;
|
||||||
|
&::before {
|
||||||
|
content: "€";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,8 @@ class OrdersController < ApplicationController
|
||||||
load_and_authorize_resource :order, through: :user, shallow: true
|
load_and_authorize_resource :order, through: :user, shallow: true
|
||||||
|
|
||||||
def new
|
def new
|
||||||
products = (@user.products.for_sale.select("products.*", "sum(order_items.count) as count").group(:product_id).order("count desc") | Product.for_sale)
|
@products = Product.all.for_sale.order(:name)
|
||||||
products.each do |p|
|
@order.products << @products
|
||||||
@order.order_items.build(product: p)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -14,6 +12,7 @@ class OrdersController < ApplicationController
|
||||||
flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!"
|
flash[:success] = "#{@order.to_sentence} ordered. Enjoy it!"
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
else
|
else
|
||||||
|
@products = Product.all.for_sale.order(:name)
|
||||||
render 'new'
|
render 'new'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,9 +31,13 @@ class ProductsController < ApplicationController
|
||||||
respond_with @product
|
respond_with @product
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def from_barcode
|
||||||
|
render json: Product.find_by_barcode(params.require(:barcode))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def product_params
|
def product_params
|
||||||
params.require(:product).permit(:name, :price, :avatar, :category, :stock, :calories, :deleted)
|
params.require(:product).permit(:name, :price, :avatar, :category, :stock, :calories, :deleted, :barcode)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,8 @@ class Ability
|
||||||
def initialize(user)
|
def initialize(user)
|
||||||
return unless user
|
return unless user
|
||||||
|
|
||||||
|
can :from_barcode, Product
|
||||||
|
|
||||||
if user.admin?
|
if user.admin?
|
||||||
can :manage, :all
|
can :manage, :all
|
||||||
elsif user.koelkast?
|
elsif user.koelkast?
|
||||||
|
|
|
@ -28,6 +28,7 @@ class Product < ActiveRecord::Base
|
||||||
validates :price_cents, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
validates :price_cents, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
||||||
validates :stock, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
validates :stock, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||||
validates :calories, numericality: { only_integer: true, allow_nil: true, greater_than_or_equal_to: 0 }
|
validates :calories, numericality: { only_integer: true, allow_nil: true, greater_than_or_equal_to: 0 }
|
||||||
|
validates :barcode, presence: true, uniqueness: true
|
||||||
|
|
||||||
scope :for_sale, -> { where deleted: false }
|
scope :for_sale, -> { where deleted: false }
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,19 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def debt
|
def balance
|
||||||
42.15
|
@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
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,6 @@
|
||||||
%li= link_to "Edit profile", edit_user_path(current_user)
|
%li= link_to "Edit profile", edit_user_path(current_user)
|
||||||
%li
|
%li
|
||||||
%p.navbar-text
|
%p.navbar-text
|
||||||
Balance: #{euro_from_cents(current_user.debt)}
|
Balance: #{euro_from_cents(current_user.balance)}
|
||||||
.visible-xs.navbar-form
|
.visible-xs.navbar-form
|
||||||
= render 'layouts/session_button'
|
= render 'layouts/session_button'
|
||||||
|
|
|
@ -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
|
|
19
app/views/orders/_products_modal.html.haml
Normal file
19
app/views/orders/_products_modal.html.haml
Normal file
|
@ -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
|
|
@ -1,9 +1,43 @@
|
||||||
%h3
|
.center
|
||||||
Order for #{@user.name}
|
.row
|
||||||
.row
|
.col-md-6.col-md-offset-1.barcode-wrapper
|
||||||
= f_form_for [@user, @order] do |f|
|
%h1 Order for #{@user.name}
|
||||||
= f.error_messages
|
= form_tag from_barcode_products_path, id: "from_barcode_form", remote: true do
|
||||||
.col-md-12
|
%input.center-block{ type: :number, name: :barcode, autofocus: true }
|
||||||
= f.fields_for :order_items do |op_field|
|
= "- OR -"
|
||||||
= render op_field.object, f: op_field, product: op_field.object.product
|
%button.btn.btn-default.center-block{ data: { toggle: :modal, target: "#products_modal" } }
|
||||||
= render 'orders/price', f: f
|
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
|
||||||
|
|
|
@ -27,7 +27,11 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
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]
|
resources :stocks, only: [:new, :create]
|
||||||
|
|
||||||
get 'overview' => 'orders#overview', as: "orders"
|
get 'overview' => 'orders#overview', as: "orders"
|
||||||
|
|
5
db/migrate/20150919091214_add_barcode_to_products.rb
Normal file
5
db/migrate/20150919091214_add_barcode_to_products.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class AddBarcodeToProducts < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :products, :barcode, :string, null: false, default: ""
|
||||||
|
end
|
||||||
|
end
|
|
@ -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: 20150918190548) do
|
ActiveRecord::Schema.define(version: 20150919091214) do
|
||||||
|
|
||||||
create_table "delayed_jobs", force: :cascade do |t|
|
create_table "delayed_jobs", force: :cascade do |t|
|
||||||
t.integer "priority", default: 0, null: false
|
t.integer "priority", default: 0, null: false
|
||||||
|
@ -60,6 +60,7 @@ ActiveRecord::Schema.define(version: 20150918190548) do
|
||||||
t.integer "stock", default: 0, null: false
|
t.integer "stock", default: 0, null: false
|
||||||
t.integer "calories"
|
t.integer "calories"
|
||||||
t.boolean "deleted", default: false
|
t.boolean "deleted", default: false
|
||||||
|
t.string "barcode", default: "", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
|
|
|
@ -28,6 +28,7 @@ FactoryGirl.define do
|
||||||
stock { 30 + rand(30) }
|
stock { 30 + rand(30) }
|
||||||
calories { rand 20 }
|
calories { rand 20 }
|
||||||
avatar { Identicon.data_url_for name }
|
avatar { Identicon.data_url_for name }
|
||||||
|
sequence :barcode
|
||||||
|
|
||||||
factory :invalid_product do
|
factory :invalid_product do
|
||||||
name nil
|
name nil
|
||||||
|
|
Loading…
Reference in a new issue