Use barcode to create new order

This commit is contained in:
benji 2015-09-20 21:21:18 +02:00
parent ec2b516727
commit 6403835872
16 changed files with 222 additions and 125 deletions

BIN
app/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -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);

View file

@ -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: "";
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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?

View file

@ -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 }

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -0,0 +1,19 @@
#products_modal.modal{ tabindex: -1 }
.modal-dialog.modal-lg
.modal-content
.modal-header
%button.close{ data: { dismiss: :modal } }
%span &times;
%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

View file

@ -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

View file

@ -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"

View file

@ -0,0 +1,5 @@
class AddBarcodeToProducts < ActiveRecord::Migration
def change
add_column :products, :barcode, :string, null: false, default: ""
end
end

View file

@ -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|

View file

@ -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