diff --git a/.idea/.generators b/.idea/.generators index 16189766..98526fe7 100644 --- a/.idea/.generators +++ b/.idea/.generators @@ -5,4 +5,4 @@ You are allowed to: 2. Remove generators 3. Add installed generators To add new installed generators automatically delete this file and reload the project. ---> +--> diff --git a/.idea/sxrestaurant.iml b/.idea/sxrestaurant.iml index 2a0f8404..53fef9e5 100644 --- a/.idea/sxrestaurant.iml +++ b/.idea/sxrestaurant.iml @@ -38,6 +38,7 @@ + @@ -79,6 +80,7 @@ + @@ -111,6 +113,7 @@ + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5eaab1be..705b1a97 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,122 +2,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - + + + @@ -131,187 +27,113 @@ + + + + + + + @@ -522,8 +363,15 @@ + + + + + + + @@ -551,7 +399,7 @@ - + @@ -559,10 +407,10 @@ - - + + - + @@ -590,6 +438,25 @@ + + + + + + + + + + + + + + + + + + + project @@ -780,6 +647,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -826,12 +717,20 @@ - - - - - + + + + + + + + + + + + + - + + + + + + + + - + - - + + - - - - + + + + - + - + @@ -893,287 +803,167 @@ - + + - + - - + + - + - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + - - + + - + - - + + - + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1181,190 +971,250 @@ - + - - + + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gemfile b/Gemfile index 44a85e51..93b245eb 100644 --- a/Gemfile +++ b/Gemfile @@ -34,7 +34,7 @@ gem 'simple_form' gem 'nested_form' gem 'bootstrap', '~> 4.0.0.alpha3' gem 'tether-rails' -gem "font-awesome-rails" +gem 'font-awesome-rails', '~> 4.7', '>= 4.7.0.2' gem 'rack-cors' # image upload @@ -111,9 +111,8 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'httparty', '~> 0.15.5' -# gem 'momentjs-rails', '>= 2.9.0' -# gem 'bootstrap-datepicker-rails' -# # gem 'momentjs-rails', '>= 2.9.0' -# gem 'bootstrap3-datetimepicker-rails'z -gem 'bootstrap-datepicker-rails' +gem 'momentjs-rails' # for date-range selector +gem 'bootstrap-daterangepicker-rails' # date-range picker +gem 'bootstrap-datepicker-rails' # date picker gem 'jquery-datetimepicker-rails' +gem 'select2-rails' # for multi-select and auto-complete select box diff --git a/Gemfile.lock b/Gemfile.lock index f2d875dd..b2a6e59c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,6 +48,8 @@ GEM sass (>= 3.4.19) bootstrap-datepicker-rails (1.6.4.1) railties (>= 3.0) + bootstrap-daterangepicker-rails (0.0.8) + railties (>= 3.1) builder (3.2.3) byebug (9.0.6) cancancan (1.17.0) @@ -121,6 +123,8 @@ GEM mime-types-data (3.2016.0521) mini_portile2 (2.2.0) minitest (5.10.3) + momentjs-rails (2.17.1) + railties (>= 3.1) multi_json (1.12.1) multi_xml (0.6.0) mysql2 (0.4.6) @@ -197,6 +201,8 @@ GEM tilt (>= 1.1, < 3) schema_to_scaffold (0.8.0) activesupport (>= 3.2.1) + select2-rails (4.0.3) + thor (~> 0.14) shoulda-matchers (3.1.1) activesupport (>= 4.0.0) sidekiq (5.0.3) @@ -254,6 +260,7 @@ DEPENDENCIES bcrypt (~> 3.1.7) bootstrap (~> 4.0.0.alpha3) bootstrap-datepicker-rails + bootstrap-daterangepicker-rails byebug cancancan (~> 1.10) carrierwave (~> 1.0) @@ -263,7 +270,7 @@ DEPENDENCIES factory_girl_rails (~> 4.0) faker filterrific - font-awesome-rails + font-awesome-rails (~> 4.7, >= 4.7.0.2) httparty (~> 0.15.5) jbuilder (~> 2.5) jquery-datetimepicker-rails @@ -271,6 +278,7 @@ DEPENDENCIES jquery-ui-rails kaminari (~> 1.0.1) listen (~> 3.0.5) + momentjs-rails mysql2 (>= 0.3.18, < 0.5) nested_form pg @@ -282,6 +290,7 @@ DEPENDENCIES rspec-rails (~> 3.5) sass-rails (~> 5.0) schema_to_scaffold + select2-rails shoulda-matchers (~> 3.1) sidekiq simple_form diff --git a/README.md b/README.md index 85739c2a..ee9eb1e6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ # README -SXRestuarant is a new Dedicate project for SmartSales Restaurant. It is improvement from v1.2 with upgrade to latest Bootstrap and Rails 5. The API is better define and route are refactor. +SXRestuarant is a new Dedicate project for SmartSales Restaurant. It is improvement from v1.2 with upgrade to latest Bootstrap and Rails 5.2. The API is better define and route are refactor. Things you may want to cover: * Ruby version -ruby 2.3.2p217 +ruby 2.4.1p111 * ToDo list + +1. Cloud Sync +2. Action Cable +3. Shop Setup +4. Payment Integation * System dependencies @@ -23,4 +28,55 @@ ruby 2.3.2p217 * Deployment instructions -* ... + +* Features + +1. OQS + + 1. Filter + 2. Order Item to each Stations + 3. Edit Order Item + +2. Origami(Sale) + + 1. Sale + 2. Add Customer for membership + 3. Discount and Member Discount for Sale + 4. Assign Commissioner + 5. Add other charges to Sale + +3. CRM + + 1. Customer Management + + 2. Queue Management + +4. Backend + + 1. Dining Setup + + 2. Queue Station Setup + + 3. Menu Setup + + 4. Cashier Terminal Setup + + 5. Tax Profile Setup + + 6. Printer Setup + + 7. Payment Integration + + 8. Employee Management + + 9. Promotion Setup + + 10. Commissioner Setup + + 11. Membership Integration + + +5. Inventory + + +6. Report diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 675bca2e..b1879bba 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -20,8 +20,12 @@ //= require settings/processing_items //= require jquery-ui //= require bootstrap-datepicker +//= require moment +//= require daterangepicker +//= require select2 //= require jquery.datetimepicker + $(document).on('turbolinks:load', function() { $('.datepicker').datepicker({ format : 'dd-mm-yyyy', diff --git a/app/assets/javascripts/origami.js b/app/assets/javascripts/origami.js index 0886d04e..6b2af56b 100644 --- a/app/assets/javascripts/origami.js +++ b/app/assets/javascripts/origami.js @@ -6,6 +6,7 @@ //= require cable //= require jquery-ui //= require bootstrap-datepicker +//= require jquery.datetimepicker $(document).ready(function(){ // auto refresh every 60 seconds diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 6ff7113c..4aabe81b 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -4,6 +4,8 @@ @import "theme"; @import "jquery-ui"; @import "bootstrap-datepicker3"; +@import "daterangepicker-bs3"; +@import "select2"; @import "jquery.datetimepicker"; /* Show it is fixed to the top */ diff --git a/app/assets/stylesheets/origami.scss b/app/assets/stylesheets/origami.scss index 10e2b319..63e64159 100644 --- a/app/assets/stylesheets/origami.scss +++ b/app/assets/stylesheets/origami.scss @@ -4,6 +4,8 @@ @import "jquery-ui"; @import "bootstrap-datepicker3"; +@import "jquery.datetimepicker"; + /* Show it is fixed to the top */ // body { // min-height: 75rem; diff --git a/app/assets/stylesheets/settings.scss b/app/assets/stylesheets/settings.scss index ec02f7c4..e17a0f93 100644 --- a/app/assets/stylesheets/settings.scss +++ b/app/assets/stylesheets/settings.scss @@ -60,4 +60,31 @@ ul.dropdown-menu li a{ .padding-10 { padding: 10px; - } \ No newline at end of file + } + +/* Colors */ +.purple { + background-color:#7a62d3; +} + +.orange{ + background-color:#FF7F50; +} + +.red { + background-color:#ff0000; +} + +.green{ + background-color: #009900; +} + +.orange{ + background-color: #FF8C00; +} + +.blue{ + background-color: blue; +} + +/* End Colors */ \ No newline at end of file diff --git a/app/controllers/api/restaurant/menu_controller.rb b/app/controllers/api/restaurant/menu_controller.rb index b45e1189..ad8ad7f9 100644 --- a/app/controllers/api/restaurant/menu_controller.rb +++ b/app/controllers/api/restaurant/menu_controller.rb @@ -4,8 +4,25 @@ class Api::Restaurant::MenuController < Api::ApiController # Pull the default menu details and also other available (active) menus # Input Params - order_id def index - @menus = Menu.all - # @current_menu = Menu.current_menu + param_checksum = params[:checksum] + # checksum = File.readlines("public/checksums/menu_json.txt").pop.chomp + + all_menu = Menu.all + + # to hash + menu_array = [] + all_menu.each do |m| + menu_array.push(m.to_json(:include => {:menu_categories => { :include => { :menu_items => { :include => [:menu_item_sets, :menu_item_instances => {:include => :menu_instance_item_sets}]} } }})) + end + + #export Checksum file generate by md5 + menu_checksum = Digest::MD5.hexdigest(menu_array.to_json) + + if menu_checksum != param_checksum + response.headers['CHECKSUM'] = menu_checksum + @menus = all_menu + end + # @current_menu = Menu.current_menu end #Description diff --git a/app/controllers/crm/customers_controller.rb b/app/controllers/crm/customers_controller.rb index ffa714a2..2a0626dd 100644 --- a/app/controllers/crm/customers_controller.rb +++ b/app/controllers/crm/customers_controller.rb @@ -6,13 +6,33 @@ class Crm::CustomersController < BaseCrmController # GET /crm/customers.json def index filter = params[:filter] + type = params[:type] if filter.nil? @crm_customers = Customer.all else @crm_customers = Customer.search(filter) - + # search account no from paypar + if !@crm_customers.present? && type == "card" + response = Customer.search_paypar_account_no(filter) + if response["status"] == true + @crm_customers = Customer.new + @crm_customers.name = response["customer_data"]["name"] + @crm_customers.contact_no = response["customer_data"]["phone"] + @crm_customers.email = response["customer_data"]["email"] + @crm_customers.date_of_birth = response["customer_data"]["DOB"] + @crm_customers.nrc_no = response["customer_data"]["NRC"] + @crm_customers.address = response["customer_data"]["address"] + @crm_customers.card_no = response["customer_data"]["customer_card_no"] + @crm_customers.paypar_account_no = filter + @crm_customers.membership_id = response["customer_data"]["id"] + @crm_customers.membership_type = response["customer_data"]["member_group_id"] + @crm_customers.customer_type = "Dinein" + @crm_customers.tax_profiles = ["1", "2"] + @crm_customers.save + @crm_customers = Customer.search(filter) + end + end end - #@crm_customers = Customer.all @crm_customers = Kaminari.paginate_array(@crm_customers).page(params[:page]).per(50) @crm_customer = Customer.new @count_customer = Customer.count_customer diff --git a/app/controllers/origami/commissioners_controller.rb b/app/controllers/origami/commissioners_controller.rb deleted file mode 100644 index 096887ce..00000000 --- a/app/controllers/origami/commissioners_controller.rb +++ /dev/null @@ -1,75 +0,0 @@ -class Origami::CommissionersController < BaseOrigamiController - before_action :set_commissioner, only: [:show, :edit, :update, :destroy] - - # GET /commissioners - # GET /commissioners.json - def index - @commissioners = Commissioner.all.order("id asc") - end - - # GET /commissioners/1 - # GET /commissioners/1.json - def show - end - - # GET /commissioners/new - def new - @commissioner = Commissioner.new - @employee = Employee.all.order('name asc') - end - - # GET /commissioners/1/edit - def edit - end - - # POST /commissioners - # POST /commissioners.json - def create - @commissioner = Commissioner.new(commissioner_params) - @commissioner.created_by = current_user.id - respond_to do |format| - if @commissioner.save - format.html { redirect_to origami_commissioners_path , notice: 'Commissioner was successfully created.' } - format.json { render :show, status: :created, location: @commissioner } - else - format.html { render :new } - format.json { render json: @commissioner.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /commissioners/1 - # PATCH/PUT /commissioners/1.json - def update - respond_to do |format| - if @commissioner.update(commissioner_params) - format.html { redirect_to origami_commissioner_path(@commissioner) , notice: 'Commissioner was successfully updated.' } - format.json { render :show, status: :ok, location: @commissioner } - else - format.html { render :edit } - format.json { render json: @commissioner.errors, status: :unprocessable_entity } - end - end - end - - # DELETE /commissioners/1 - # DELETE /commissioners/1.json - def destroy - @commissioner.destroy - respond_to do |format| - format.html { redirect_to origami_commissioners_path , notice: 'Commissioner was successfully destroyed.' } - format.json { head :no_content } - end - end - - private - # Use callbacks to share common setup or constraints between actions. - def set_commissioner - @commissioner = Commissioner.find(params[:id]) - end - - # Never trust parameters from the scary internet, only allow the white list through. - def commissioner_params - params.require(:commissioner).permit(:name,:emp_id,:created_by,:commission_type, :is_active) - end -end diff --git a/app/controllers/origami/customers_controller.rb b/app/controllers/origami/customers_controller.rb index 8faaa221..72597613 100644 --- a/app/controllers/origami/customers_controller.rb +++ b/app/controllers/origami/customers_controller.rb @@ -9,13 +9,40 @@ class Origami::CustomersController < BaseOrigamiController end def get_customer + filter = params[:filter] + type = params[:type] + if filter.nil? @crm_customers = Customer.order("customer_id").page(params[:page]) #@products = Product.order("name").page(params[:page]).per(5) else - @crm_customers = Customer.search(filter) + @crm_customers = Customer.search(filter) + # search account no from paypar + if !@crm_customers.present? && type == "card" + response = Customer.search_paypar_account_no(filter) + if response["status"] == true + @crm_customers = Customer.new + @crm_customers.name = response["customer_data"]["name"] + @crm_customers.contact_no = response["customer_data"]["phone"] + @crm_customers.email = response["customer_data"]["email"] + @crm_customers.date_of_birth = response["customer_data"]["DOB"] + @crm_customers.nrc_no = response["customer_data"]["NRC"] + @crm_customers.address = response["customer_data"]["address"] + @crm_customers.card_no = response["customer_data"]["customer_card_no"] + @crm_customers.paypar_account_no = filter + @crm_customers.membership_id = response["customer_data"]["id"] + @crm_customers.membership_type = response["customer_data"]["member_group_id"] + @crm_customers.customer_type = "Dinein" + @crm_customers.tax_profiles = ["1", "2"] + @crm_customers.save + @crm_customers = Customer.search(filter) + else + @crm_customers = [{"customer_id": response["status"],"message": response["message"] }] + end + end end + render :json => @crm_customers.to_json end diff --git a/app/controllers/origami/in_juties_controller.rb b/app/controllers/origami/in_juties_controller.rb index 821aa409..6ac04659 100644 --- a/app/controllers/origami/in_juties_controller.rb +++ b/app/controllers/origami/in_juties_controller.rb @@ -8,10 +8,10 @@ class Origami::InJutiesController < BaseOrigamiController end def index_in_juty - @juties_in= InJuty.where("dinning_id=?",params[:table_id]) + @juty_in= InJuty.where("dinning_id=?",params[:table_id]) @table = DiningFacility.find(params[:table_id]) @in_juty = InJuty.new - + @juties_in = Kaminari.paginate_array(@juty_in).page(params[:page]).per(10) end # GET /in_juties/1 # GET /in_juties/1.json @@ -31,6 +31,9 @@ class Origami::InJutiesController < BaseOrigamiController def edit_in_juty @in_juty = InJuty.find(params[:id]) @table = DiningFacility.find(params[:table_id]) + @commissioner = @in_juty.commissioner + + render json: {in_juty: @in_juty, commissioner: @commissioner} end def assign_in_juty @@ -56,10 +59,10 @@ class Origami::InJutiesController < BaseOrigamiController def create_for_in_juty @in_juty = InJuty.new - @in_juty.dinning_id = in_juty_params[:dinning_id] - @in_juty.commissioner_ids = in_juty_params[:commissioner_ids] - @in_juty.in_time = in_juty_params[:in_time] - @in_juty.out_time = in_juty_params[:out_time] + @in_juty.dinning_id = in_juty_params[:dinning_id] + @in_juty.commissioner_ids = in_juty_params[:commissioner_ids] + @in_juty.in_time = in_juty_params[:in_time] + @in_juty.out_time = in_juty_params[:out_time] respond_to do |format| @@ -71,7 +74,7 @@ class Origami::InJutiesController < BaseOrigamiController format.json { render json: @in_juty.errors, status: :unprocessable_entity } end end - + end # PATCH/PUT /in_juties/1 @@ -90,11 +93,18 @@ class Origami::InJutiesController < BaseOrigamiController def update_for_in_juty - @in_juty.dinning_id = params[:dining_id] - @in_juty.commissioner_ids = params[:commissioner_ids] - @in_juty.in_time = params[:in_time] - @in_juty.out_time = params[:out_time] - @in_juty.save + @in_juty.commissioner_ids = in_juty_params[:commissioner_ids] + @in_juty.in_time = in_juty_params[:in_time] + @in_juty.out_time = in_juty_params[:out_time] + respond_to do |format| + if @in_juty.save + format.html { redirect_to origami_index_in_juty_path(in_juty_params[:dinning_id]), notice: 'In juty was successfully updated.' } + format.json { render :show, status: :ok, location: @in_juty } + else + format.html { render :edit } + format.json { render json: @in_juty.errors, status: :unprocessable_entity } + end + end end # DELETE /in_juties/1 @@ -110,7 +120,7 @@ class Origami::InJutiesController < BaseOrigamiController def destroy_in_juty @table_id = params[:table_id] @in_juty.destroy - + respond_to do |format| format.html { redirect_to origami_index_in_juty_path(@table_id), notice: 'In juty was successfully destroyed.' } format.json { head :no_content } @@ -125,6 +135,6 @@ class Origami::InJutiesController < BaseOrigamiController # Never trust parameters from the scary internet, only allow the white list through. def in_juty_params - params.require(:in_juty).permit(:dinning_id,:commissioner_ids,:in_time,:out_time) + params.require(:in_juty).permit(:id,:dinning_id,:commissioner_ids,:in_time,:out_time) end end diff --git a/app/controllers/origami/product_commissions_controller.rb b/app/controllers/origami/product_commissions_controller.rb index e2cba46c..2bf38113 100644 --- a/app/controllers/origami/product_commissions_controller.rb +++ b/app/controllers/origami/product_commissions_controller.rb @@ -62,15 +62,16 @@ class Origami::ProductCommissionsController < ApplicationController end def set_commissioner_to_sale_item + # byebug deselect = false sale_item_id = params[:sale_item_id] commissioner_id = params[:commissioner_id] @sale_item = SaleItem.find(sale_item_id) @menu_item = MenuItem.find_by_item_code(@sale_item.product_code) - @commission = Commission.where('product_id = ? AND is_active = ?', @menu_item.id, true).take + @commission = Commission.where('product_code = ? AND is_active = ?', @menu_item.id, true).take @commissioner = Commissioner.where('id = ? AND is_active = ?', commissioner_id, true).take @product_commission = ProductCommission.where('sale_item_id = ?', @sale_item.id).take - # byebug + if !@product_commission.nil? if @product_commission.commissioner_id == @commissioner.id @product_commission.destroy @@ -81,7 +82,8 @@ class Origami::ProductCommissionsController < ApplicationController end else @product_commission = ProductCommission.new - @product_commission.product_id = @menu_item.id + @product_commission.product_code = @menu_item.id + @product_commission.product_type = 'menu_item' # use for dummy data ToDo::need to change product type unless @commission.nil? @product_commission.commission_id = @commission.id if @commission.commission_type == 'Percentage' diff --git a/app/controllers/reports/commission_controller.rb b/app/controllers/reports/commission_controller.rb new file mode 100644 index 00000000..98f98457 --- /dev/null +++ b/app/controllers/reports/commission_controller.rb @@ -0,0 +1,49 @@ +class Reports::CommissionController < BaseReportController + # authorize_resource :class => false + + def index + from_date = DateTime.now.beginning_of_day.utc.getlocal + to_date = DateTime.now.end_of_day.utc.getlocal + unless params[:daterange].blank? + from_date = Date.parse(params[:daterange].split(' - ')[0]).beginning_of_day.utc.getlocal + to_date = Date.parse(params[:daterange].split(' - ')[1]).end_of_day.utc.getlocal + @daterange = params[:daterange] + end + commissioner = params[:commissioner].to_i + @com_id = commissioner + @commissioner = Commissioner.active.all + + @transaction = ProductCommission.get_transaction(from_date, to_date, commissioner) + @from = from_date + @to = to_date + + respond_to do |format| + format.html + format.xls + end + end + + def show + from, to = get_date_range_from_params + + @sale_data = Sale.get_by_shift_sale(from,to,Sale::SALE_STATUS_COMPLETED) + + date_arr = Array.new + @sale_data.each do |sale| + local_opening_date = sale.opening_date.nil? ? '-' : sale.opening_date.utc.getlocal.strftime("%e %b %I:%M%p") + local_closing_date = sale.closing_date.nil? ? '-' : sale.closing_date.utc.getlocal.strftime("%e %b %I:%M%p") + opening_date = sale.opening_date.nil? ? '-' : sale.opening_date.utc + closing_date = sale.closing_date.nil? ? '-' : sale.closing_date.utc + shift_id = sale.id.nil? ? '-' : sale.id + str = {:shift_id => shift_id, :local_opening_date => local_opening_date, :local_closing_date => local_closing_date, :opening_date => opening_date, :closing_date => closing_date} + date_arr.push(str) + end + + out = {:status => 'ok', :message => date_arr} + + respond_to do |format| + format.json { render json: out } + end + end +end + diff --git a/app/controllers/settings/commissioners_controller.rb b/app/controllers/settings/commissioners_controller.rb new file mode 100644 index 00000000..d4d1fa53 --- /dev/null +++ b/app/controllers/settings/commissioners_controller.rb @@ -0,0 +1,93 @@ +class Settings::CommissionersController < ApplicationController + before_action :set_commissioner, only: [:show, :edit, :update, :destroy] + + # GET /commissioners + # GET /commissioners.json + def index + @commissioners = Commissioner.all.order("id asc") + end + + # GET /commissioners/1 + # GET /commissioners/1.json + def show + end + + # GET /commissioners/new + def new + @commissioner = Commissioner.new + @employee = Employee.all.order('name asc') + end + + # GET /commissioners/1/edit + def edit + end + + # POST /commissioners + # POST /commissioners.json + def create + @commissioner = Commissioner.new(commissioner_params) + @commissioner.created_by = current_user.id + unless @commissioner.joined_date.nil? + @commissioner.joined_date = @commissioner.joined_date.utc.getlocal.strftime('%Y-%b-%d') + end + unless @commissioner.resigned_date.nil? + @commissioner.resigned_date = @commissioner.resigned_date.utc.getlocal.strftime('%Y-%b-%d') + end + respond_to do |format| + if @commissioner.save + format.html {redirect_to settings_commissioners_path, notice: 'Commissioner was successfully created.'} + format.json {render :show, status: :created, location: @commissioner} + else + format.html {render :new} + format.json {render json: @commissioner.errors, status: :unprocessable_entity} + end + end + end + + # PATCH/PUT /commissioners/1 + # PATCH/PUT /commissioners/1.json + def update + respond_to do |format| + if @commissioner.update(commissioner_params) + format.html {redirect_to settings_commissioner_path(@commissioner), notice: 'Commissioner was successfully updated.'} + format.json {render :show, status: :ok, location: @commissioner} + else + format.html {render :edit} + format.json {render json: @commissioner.errors, status: :unprocessable_entity} + end + end + end + + # DELETE /commissioners/1 + # DELETE /commissioners/1.json + def destroy + @commissioner.destroy + respond_to do |format| + format.html {redirect_to settings_commissioners_path, notice: 'Commissioner was successfully destroyed.'} + format.json {head :no_content} + end + end + + def get_transaction_by_commissioner + commissioner_id = params[:commissioner_id] + @transactions = [] + @product_commissions = ProductCommission.where(commissioner_id: commissioner_id).order('updated_at desc') + @product_commissions.each_with_index do |p, i| + @transactions[i] = [] + @transactions[i] << p + @transactions[i] << p.commission.menu_item.name + end + render json: @transactions + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_commissioner + @commissioner = Commissioner.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def commissioner_params + params.require(:commissioner).permit(:name, :emp_id, :created_by, :commission_id, :joined_date, :resigned_date, :is_active) + end +end diff --git a/app/controllers/origami/commissions_controller.rb b/app/controllers/settings/commissions_controller.rb similarity index 81% rename from app/controllers/origami/commissions_controller.rb rename to app/controllers/settings/commissions_controller.rb index db49a188..2fe85807 100644 --- a/app/controllers/origami/commissions_controller.rb +++ b/app/controllers/settings/commissions_controller.rb @@ -1,4 +1,4 @@ -class Origami::CommissionsController < BaseOrigamiController +class Settings::CommissionsController < ApplicationController before_action :set_commission, only: [:show, :edit, :update, :destroy] # GET /commissions @@ -27,10 +27,11 @@ class Origami::CommissionsController < BaseOrigamiController # POST /commissions.json def create @commission = Commission.new(commission_params) + @commission.product_type = 'menu_item' respond_to do |format| if @commission.save - format.html {redirect_to origami_commissions_path, notice: 'Commission was successfully created.'} + format.html {redirect_to settings_commissions_path, notice: 'Commission was successfully created.'} format.json {render :show, status: :created, location: @commission} else format.html {render :new} @@ -44,7 +45,7 @@ class Origami::CommissionsController < BaseOrigamiController def update respond_to do |format| if @commission.update(commission_params) - format.html {redirect_to origami_commission_path(@commission), notice: 'Commission was successfully updated.'} + format.html {redirect_to settings_commission_path(@commission), notice: 'Commission was successfully updated.'} format.json {render :show, status: :ok, location: @commission} else format.html {render :edit} @@ -58,7 +59,7 @@ class Origami::CommissionsController < BaseOrigamiController def destroy @commission.destroy respond_to do |format| - format.html {redirect_to origami_commissions_path, notice: 'Commission was successfully destroyed.'} + format.html {redirect_to settings_commissions_path, notice: 'Commission was successfully destroyed.'} format.json {head :no_content} end end @@ -96,6 +97,6 @@ class Origami::CommissionsController < BaseOrigamiController # Never trust parameters from the scary internet, only allow the white list through. def commission_params - params.require(:commission).permit(:product_id, :amount, :commission_type, :is_active) + params.require(:commission).permit(:product_type, :product_code, :amount, :commission_type, :is_active) end end diff --git a/app/controllers/settings/simple_menu_items_controller.rb b/app/controllers/settings/simple_menu_items_controller.rb index 418cc2b8..e3010e30 100644 --- a/app/controllers/settings/simple_menu_items_controller.rb +++ b/app/controllers/settings/simple_menu_items_controller.rb @@ -79,7 +79,7 @@ class Settings::SimpleMenuItemsController < ApplicationController respond_to do |format| if @settings_menu_item.save menu_item = MenuItem.find(@settings_menu_item.id) - menu_item.update_attributes(item_options: params[:simple_menu_item][:item_options]) + menu_item.update_attributes(item_attributes: params[:simple_menu_item][:item_attributes], item_options: params[:simple_menu_item][:item_options]) format.html { redirect_to settings_menu_category_simple_menu_items_path, notice: 'Menu item was successfully created.' } format.json { render :show, status: :created, location: @settings_menu_item } diff --git a/app/inputs/date_picker_input.rb b/app/inputs/date_picker_input.rb new file mode 100644 index 00000000..3acf410e --- /dev/null +++ b/app/inputs/date_picker_input.rb @@ -0,0 +1,66 @@ +class DatePickerInput < SimpleForm::Inputs::StringInput + def input(wrapper_options) + set_html_options + set_value_html_option + + template.content_tag :div, class: 'input-group date datetimepicker' do + input = super(wrapper_options) # leave StringInput do the real rendering + input + input_button + end + end + + def input_html_classes + super.push '' # 'form-control' + end + + private + + def input_button + template.content_tag :span, class: 'input-group-btn' do + template.content_tag :button, class: 'btn btn-default', type: 'button' do + template.content_tag :span, '', class: 'fa fa-calendar' + end + end + end + + def set_html_options + input_html_options[:type] = 'text' + input_html_options[:data] ||= {} + input_html_options[:data].merge!(date_options: date_options) + end + + def set_value_html_option + return unless value.present? + input_html_options[:value] ||= I18n.localize(value, format: display_pattern) + end + + def value + object.send(attribute_name) if object.respond_to? attribute_name + end + + def display_pattern + I18n.t('datepicker.dformat', default: '%d/%m/%Y') + end + + def picker_pattern + I18n.t('datepicker.pformat', default: 'DD/MM/YYYY') + end + + def date_view_header_format + I18n.t('dayViewHeaderFormat', default: 'MMMM YYYY') + end + + def date_options_base + { + locale: I18n.locale.to_s, + format: picker_pattern, + dayViewHeaderFormat: date_view_header_format + } + end + + def date_options + custom_options = input_html_options[:data][:date_options] || {} + date_options_base.merge!(custom_options) + end + +end diff --git a/app/inputs/datetime_picker_input.rb b/app/inputs/datetime_picker_input.rb new file mode 100644 index 00000000..f66b02ec --- /dev/null +++ b/app/inputs/datetime_picker_input.rb @@ -0,0 +1,13 @@ +class DatetimePickerInput < DatePickerInput + private + + def display_pattern + I18n.t('datepicker.dformat', default: '%d/%m/%Y') + ' ' + + I18n.t('timepicker.dformat', default: '%R') + end + + def picker_pattern + I18n.t('datepicker.pformat', default: 'DD/MM/YYYY') + ' ' + + I18n.t('timepicker.pformat', default: 'HH:mm') + end +end diff --git a/app/inputs/time_picker_input.rb b/app/inputs/time_picker_input.rb new file mode 100644 index 00000000..2f5c70bf --- /dev/null +++ b/app/inputs/time_picker_input.rb @@ -0,0 +1,15 @@ +class TimePickerInput < DatePickerInput + private + + def display_pattern + I18n.t('timepicker.dformat', default: '%R') + end + + def picker_pattern + I18n.t('timepicker.pformat', default: 'HH:mm') + end + + def date_options + date_options_base + end +end diff --git a/app/models/commission.rb b/app/models/commission.rb index 943bb5c1..40d69f81 100644 --- a/app/models/commission.rb +++ b/app/models/commission.rb @@ -1,5 +1,15 @@ class Commission < ApplicationRecord - belongs_to :menu_item, foreign_key: 'product_id' + self.primary_key = 'commission_id' + + # primary key - need to be unique + before_create :generate_custom_id + + belongs_to :menu_item, foreign_key: 'product_code' has_many :commissioners has_many :product_commissions + + private + def generate_custom_id + self.commission_id = SeedGenerator.generate_id(self.class.name, 'COM') + end end diff --git a/app/models/commissioner.rb b/app/models/commissioner.rb index 8bec411d..17fa2036 100644 --- a/app/models/commissioner.rb +++ b/app/models/commissioner.rb @@ -1,6 +1,6 @@ class Commissioner < ApplicationRecord belongs_to :employee, foreign_key: 'emp_id' - belongs_to :commission, foreign_key: 'commission_type' + belongs_to :commission, foreign_key: 'commission_id' has_many :in_juties has_many :product_commissions scope :active, -> { where(is_active: true) } diff --git a/app/models/customer.rb b/app/models/customer.rb index dff92ba2..9e629721 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -91,46 +91,54 @@ class Customer < ApplicationRecord auth_token = memberaction.auth_token.to_s url = membership.gateway_url.to_s + memberaction.gateway_url.to_s - @customers = Customer.where("membership_type IS NOT NULL AND membership_id IS NULL") + @customers = Customer.where("membership_type IS NOT NULL AND membership_id IS NULL") @customers.each do |customer| - begin - response = HTTParty.post(url, - :body => - { name: customer.name,phone: customer.contact_no, - email: customer.email,dob: customer.date_of_birth, - address: customer.address,nrc:customer.nrc_no, - card_no:customer.card_no,member_group_id: customer.membership_type, - merchant_uid:merchant_uid,auth_token:auth_token - }.to_json, - :headers => { - 'Content-Type' => 'application/json', - 'Accept' => 'application/json' - }) - rescue Net::OpenTimeout - response = { status: false, message: "Server Time out" } - - rescue OpenURI::HTTPError - response = { status: false, message: "Can't connect server"} + member_params = { name: customer.name,phone: customer.contact_no, + email: customer.email,dob: customer.date_of_birth, + address: customer.address,nrc:customer.nrc_no, + card_no:customer.card_no,member_group_id: customer.membership_type, + merchant_uid:merchant_uid,auth_token:auth_token}.to_json - rescue SocketError - response = { status: false, message: "Can't connect server"} - end + # Check for paypar account exists + # if paypar_account_no != nil || paypar_account_no != '' + if paypar_account_no.present? + member_params = { name: customer.name,phone: customer.contact_no, + email: customer.email,dob: customer.date_of_birth, + address: customer.address,nrc:customer.nrc_no, + paypar_account_no: customer.paypar_account_no, + card_no:customer.card_no,member_group_id: customer.membership_type, + merchant_uid:merchant_uid,auth_token:auth_token}.to_json + end - if response["status"] == true - status = customer.update_attributes(membership_id: response["customer_datas"]["id"]) - end + begin + response = HTTParty.post(url, + :body => member_params, + :headers => { + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + }) + rescue Net::OpenTimeout + response = { status: false, message: "Server Time out" } + + rescue OpenURI::HTTPError + response = { status: false, message: "Can't connect server"} + rescue SocketError + response = { status: false, message: "Can't connect server"} + end + + if response["status"] == true + status = customer.update_attributes(membership_id: response["customer_datas"]["id"]) + end end end def self.update_rebate - sales = Sale.where("rebate_status = 'false'") sales.each do |sale| if sale.customer.membership_id - response = self.rebat(Sale.find(sale.sale_id)) - puts response.to_json + response = self.rebat(Sale.find(sale.sale_id)) if response["status"] == true status = sale.update_attributes(rebate_status: "true") end @@ -188,12 +196,44 @@ class Customer < ApplicationRecord response = { "status": false, "message": "Can't connect server"} end - return response - puts response.to_json + return response end end end + def self.search_paypar_account_no(account_no) + membership = MembershipSetting.find_by_membership_type("paypar_url") + memberaction = MembershipAction.find_by_membership_type("search_paypar_account_no") + merchant_uid = memberaction.merchant_account_id.to_s + auth_token = memberaction.auth_token.to_s + url = membership.gateway_url.to_s + memberaction.gateway_url.to_s + begin + response = HTTParty.get(url, + :body => { paypar_account_no:account_no, + merchant_uid:merchant_uid, + auth_token:auth_token + }.to_json, + :headers => { + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + }, + :timeout => 10 + ) + rescue HTTParty::Error + response = {status: false, message: "Server Error"} + + rescue Net::OpenTimeout + response = { status: false , message: "Server Time out"} + + rescue OpenURI::HTTPError + response = { status: false, message: "Can't connect server"} + + rescue SocketError + response = { status: false, message: "Can't connect server"} + end + return response + end + def self.search(search) if search # find(:all, :conditions => ['name LIKE ? OR contact_no LIKE ?', "%#{search}%", "%#{search}%"]) diff --git a/app/models/menu_item_attribute.rb b/app/models/menu_item_attribute.rb index 54d2f34e..e57085cb 100644 --- a/app/models/menu_item_attribute.rb +++ b/app/models/menu_item_attribute.rb @@ -1,6 +1,6 @@ class MenuItemAttribute < ApplicationRecord validates_presence_of :attribute_type, :name, :value def self.collection - MenuItemAttribute.select("name, value").map { |e| [e.name, e.value] } + MenuItemAttribute.select("name").map { |e| [e.name, e.name] } end end diff --git a/app/models/order.rb b/app/models/order.rb index 0ef19729..e26d1ee2 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -90,7 +90,7 @@ class Order < ApplicationRecord # self.employee_name) # end - OrderItem.processs_item(menu_item[:item_code], menu_item[:name], menu_item[:alt_name], menu_item[:account_id], + OrderItem.processs_item(menu_item[:item_code], item[:item_instance_code], menu_item[:name], menu_item[:alt_name], menu_item[:account_id], item[:quantity],menu_item[:price], item[:options], set_order_items, self.id, self.employee_name) diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 897a4b1e..12c8c378 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -20,11 +20,12 @@ class OrderItem < ApplicationRecord # option_values : [], # sub_order_items : [], # } - def self.processs_item (item_code, menu_name, alt_name, account_id, qty,price, options, set_menu_items, order_id, item_order_by) + def self.processs_item (item_code, instance_code, menu_name, alt_name, account_id, qty,price, options, set_menu_items, order_id, item_order_by) orderitem = OrderItem.create do |oitem| oitem.order_id = order_id oitem.item_code = item_code + oitem.item_instance_code = instance_code oitem.item_name = menu_name oitem.alt_name = alt_name oitem.account_id = account_id diff --git a/app/models/product_commission.rb b/app/models/product_commission.rb index 59e64b79..0e39f9f3 100644 --- a/app/models/product_commission.rb +++ b/app/models/product_commission.rb @@ -5,7 +5,14 @@ class ProductCommission < ApplicationRecord belongs_to :sale_item, foreign_key: 'sale_item_id' belongs_to :sale, foreign_key: 'sale_id' - def self.check_product_commission(sale_item_id) - + def self.get_transaction(from, to, commissioner) + transaction = self.all + if !from.nil? && !to.nil? + transaction = transaction.where('updated_at between ? and ?', from, to) + end + if commissioner != 0 + transaction = transaction.where(commissioner_id: commissioner) + end + return transaction end end diff --git a/app/models/promotion.rb b/app/models/promotion.rb index 025abd4b..d60f25a9 100644 --- a/app/models/promotion.rb +++ b/app/models/promotion.rb @@ -23,12 +23,13 @@ class Promotion < ApplicationRecord end def self.is_between_promo_datetime(current_day,current_time) #database is not local time - promoList = Promotion.where("(TO_CHAR(promo_start_date, 'YYYY-MM-DD') <=? AND TO_CHAR(promo_end_date, 'YYYY-MM-DD') >=?) AND (promo_start_hour < ? AND promo_end_hour > ?)", current_day, current_day, current_time, current_time) + promoList = Promotion.where("(date_format(promo_start_date, 'YYYY-MM-DD') <=? AND date_format(promo_end_date, 'YYYY-MM-DD') >=?) AND (promo_start_hour < ? AND promo_end_hour > ?)", current_day, current_day, current_time, current_time) return promoList end def self.combine_item(saleObj) - itemList = saleObj.sale_items.group(:product_code).sum(:qty) + order_id = saleObj.sale_orders[0].order_id + itemList = OrderItem.where("order_id = ?", order_id).group(:item_instance_code).sum(:qty) end def self.is_promo_day(promoList, day, orderitemList, sale_id) @@ -44,7 +45,8 @@ class Promotion < ApplicationRecord end def self.find_promo_item(promo, orderitem, sale_id) - if promo.original_product.to_s == orderitem[0].to_s + item_code = OrderItem.find_by_item_instance_code(orderitem[0]).item_code + if promo.original_product.to_s == item_code if promo.min_qty.to_i > orderitem[1].to_i return false else @@ -69,13 +71,13 @@ class Promotion < ApplicationRecord give_promotion_nett_price(same,promo_product,promo.min_qty, orderitem, sale_id) elsif promo.promo_type == Promotion::PROMO_TYPE4 - give_promotion_nett_price(same,promo_product,promo.min_qty, orderitem, sale_id) + give_promotion_discount(same,promo_product,promo.min_qty, orderitem, sale_id) end end def self.check_giveaway_product(promo, orderitem) promo.promotion_products.each do |promo_product| - if promo_product.item_code == orderitem + if promo_product.item_code == OrderItem.find_by_item_instance_code(orderitem).item_code return true, promo_product else return false, promo_product @@ -110,7 +112,13 @@ class Promotion < ApplicationRecord else charge_qty += qty end - item = OrderItem.find_by_item_code(orderitem[0]) + item = OrderItem.find_by_item_instance_code(orderitem[0]) + byebug + # if promo_product == OrderItem.find_by_item_instance_code(orderitem[0]).item_code + # item = OrderItem.find_by_item_instance_code(orderitem[0]) + # else + # item = OrderItem.find_by_item_code(promo_product) + # end update_existing_item(foc_qty, item, sale_id, "promotion", item.price) puts "Charged - " + charge_qty.to_s @@ -124,7 +132,7 @@ class Promotion < ApplicationRecord if (foc_qty < promotion_qty) promotion_qty = foc_qty end - item = OrderItem.find_by_item_code(promo_product) + item = OrderItem.find_by_item_instance_code(promo_product,orderID) update_existing_item(promotion_qty, item, sale_id, "promotion", item.price) end @@ -138,8 +146,8 @@ class Promotion < ApplicationRecord sale_item.remark = type sale_item.qty = foc_qty * (-1) - sale_item.unit_price = item_price * (-1) - sale_item.taxable_price = item_price * (-1) + sale_item.unit_price = item_price # * (-1) + sale_item.taxable_price = item_price # * (-1) sale_item.price = foc_qty * item_price * (-1) sale_item.is_taxable = false @@ -156,11 +164,11 @@ class Promotion < ApplicationRecord if same foc_qty = orderitem[1].to_i / foc_min_qty - item = OrderItem.find_by_item_code(orderitem[0]) + item = OrderItem.find_by_item_instance_code(orderitem[0]) update_existing_item(foc_qty, item, sale_id, "promotion nett off", promo_product.net_off) else foc_qty = find_second_item_qty(sale_id, promo_product.item_code) - item = OrderItem.find_by_item_code(promo_product.item_code) + item = OrderItem.find_by_item_instance_code(promo_product.item_code) update_existing_item(foc_qty, item, sale_id, "promotion nett off", promo_product.net_off) end end @@ -170,12 +178,12 @@ class Promotion < ApplicationRecord if same foc_qty = orderitem[1].to_i / foc_min_qty - item = OrderItem.find_by_item_code(orderitem[0]) - price = item.price - promo_product.net_price + item = OrderItem.find_by_item_instance_code(orderitem[0]) # need to specify with menu item instance + price = item.price.to_i - promo_product.net_price.to_i update_existing_item(foc_qty, item, sale_id, "promotion nett price", price) else foc_qty = find_second_item_qty(sale_id, promo_product.item_code) - item = OrderItem.find_by_item_code(promo_product.item_code) + item = OrderItem.find_by_item_instance_code(promo_product.item_code) price = item.price - promo_product.net_price update_existing_item(foc_qty, item, sale_id, "promotion nett price", price) end @@ -186,14 +194,16 @@ class Promotion < ApplicationRecord if same foc_qty = orderitem[1].to_i / foc_min_qty - item = OrderItem.find_by_item_code(orderitem[0]) - total = orderitem[1].to_i * item.price + item = OrderItem.find_by_item_instance_code(orderitem[0]) + # total = orderitem[1].to_i * item.price + total = item.price price = calculate_discount(total, promo_product.percentage) update_existing_item(foc_qty, item, sale_id, "promotion discount", price) else foc_qty = find_second_item_qty(sale_id, promo_product.item_code) - item = OrderItem.find_by_item_code(promo_product.item_code) - total = item.price * foc_qty + item = OrderItem.find_by_item_instance_code(promo_product.item_code) + # total = item.price * foc_qty + total = item.price price = calculate_discount(total, promo_product.percentage) update_existing_item(foc_qty, item, sale_id, "promotion discount", price) end @@ -203,7 +213,7 @@ class Promotion < ApplicationRecord saleObj = Sale.find_by_sale_id(sale_id) itemList = combine_item(saleObj) itemList.each do |item| - if item[0] == promo_item + if OrderItem.find_by_item_instance_code(item[0]).item_code == promo_item return item[1] end end diff --git a/app/models/sale_payment.rb b/app/models/sale_payment.rb index 3435190d..4719e546 100644 --- a/app/models/sale_payment.rb +++ b/app/models/sale_payment.rb @@ -324,7 +324,6 @@ class SalePayment < ApplicationRecord update_shift end end - end # update for cashier shift diff --git a/app/views/api/restaurant/menu/_menu.json.jbuilder b/app/views/api/restaurant/menu/_menu.json.jbuilder index cfa950d7..29982631 100644 --- a/app/views/api/restaurant/menu/_menu.json.jbuilder +++ b/app/views/api/restaurant/menu/_menu.json.jbuilder @@ -17,5 +17,4 @@ if (menu.menu_categories) end end end - end diff --git a/app/views/api/restaurant/menu/_menu_item.json.jbuilder b/app/views/api/restaurant/menu/_menu_item.json.jbuilder index 7a450850..3f8b5358 100644 --- a/app/views/api/restaurant/menu/_menu_item.json.jbuilder +++ b/app/views/api/restaurant/menu/_menu_item.json.jbuilder @@ -1,44 +1,82 @@ # Format for attributes json attr_format = [] -param_count = item.item_attributes.count -# Format for attributes json -item.item_attributes.each do|attr_id| - menu_attr = MenuItemAttribute.find(attr_id) - if attr_format.count == 0 - attr_format.push({ type: menu_attr.attribute_type, value: [] }) - end - - attr_format.each do |af| - if menu_attr.attribute_type == af[:type] - af[:value].push(menu_attr.value) - else - new_attr = {type: menu_attr.attribute_type, value: [ menu_attr.value ] } - attr_format.push(new_attr) +# Format for attributes json +if item.item_attributes.count > 0 + item.item_attributes.each do|attr_id| + menu_attr = MenuItemAttribute.find(attr_id) + if attr_format.count == 0 + attr_format.push({ type: menu_attr.attribute_type, values: [menu_attr.name] }) + next end - - break if attr_format.count > param_count - end - param_count -= 1 + attr_format.each do |af| + if menu_attr.attribute_type.in? attr_format.map {|k| k[:type]} + if menu_attr.attribute_type == af[:type] + af[:values].push(menu_attr.name) + end + else + new_attr = {type: menu_attr.attribute_type, values: [ menu_attr.name ] } + attr_format.push(new_attr) + break + end + end + end +end + +# Format for option json +opt_format = [] +# Format for attributes json +if item.item_options.count > 0 + item.item_options.each do|opt| + menu_opt = MenuItemOption.find(opt) + if opt_format.count == 0 + opt_format.push({ type: menu_opt.option_type, values: [menu_opt.name] }) + next + end + + opt_format.each do |of| + if menu_opt.option_type.in? opt_format.map {|k| k[:type]} + if menu_opt.option_type == of[:type] + of[:values].push(menu_opt.name) + end + else + new_opt = {type: menu_opt.option_type, values: [ menu_opt.name ] } + opt_format.push(new_opt) + break + end + end + end end #Menu Item Information json.id item.id -json.item_code item.item_code +json.code item.item_code json.name item.name json.alt_name item.alt_name json.image item.image_path.url json.description item.description -json.Information item.information +json.information item.information json.type item.type json.account_id item.account_id json.min_qty item.min_qty json.is_available item.is_available json.is_sub_item item.is_sub_item json.unit item.unit -json.item_sets item.item_sets + +# Item Sets of Menu Item +json.item_sets item.item_sets do |its| + json.id its.id + json.name its.name + json.alt_name its.alt_name + json.min_selectable_qty its.min_selectable_qty + json.max_selectable_qty its.max_selectable_qty + json.instances its.menu_item_instances do |i| + json.id i.id + end +end + json.attributes attr_format -json.options item.item_options +json.options opt_format # json.min_selectable_item item.min_selectable_item # json.max_selectable_item item.max_selectable_item @@ -52,6 +90,7 @@ json.options item.item_options # json.item_attributes = item_instance.item_attributes json.instances item.menu_item_instances do |is| + json.id is.id json.code is.item_instance_code json.name is.item_instance_name json.price is.price @@ -59,8 +98,8 @@ json.options item.item_options json.is_default is.is_default json.is_on_promotion is.is_on_promotion json.promotion_price is.promotion_price - json.item_attributes is.item_attributes - json.item_sets is.item_sets + json.values is.item_attributes + # json.item_sets is.item_sets end #Child Menu items diff --git a/app/views/api/restaurant/menu/index.json.jbuilder b/app/views/api/restaurant/menu/index.json.jbuilder index 55e876f6..46f35e60 100644 --- a/app/views/api/restaurant/menu/index.json.jbuilder +++ b/app/views/api/restaurant/menu/index.json.jbuilder @@ -1,4 +1,4 @@ -json.array! @menus do |menu| +menu_json = json.array! @menus do |menu| json.id menu.id json.name menu.name json.valid_days menu.valid_days @@ -12,3 +12,4 @@ json.array! @menus do |menu| # end # end end + diff --git a/app/views/crm/customers/_new_form.html.erb b/app/views/crm/customers/_new_form.html.erb index 35c3a46d..c39f065f 100644 --- a/app/views/crm/customers/_new_form.html.erb +++ b/app/views/crm/customers/_new_form.html.erb @@ -169,6 +169,7 @@ if(cardNo.length == 16){ $("#paypar_account_no").val(cardNo); $("#search").val(cardNo); + $("#type").val("card"); } } \ No newline at end of file diff --git a/app/views/crm/customers/index.html.erb b/app/views/crm/customers/index.html.erb index 71772c92..9d2fa62b 100644 --- a/app/views/crm/customers/index.html.erb +++ b/app/views/crm/customers/index.html.erb @@ -25,6 +25,7 @@ <%= form_tag crm_customers_path, :id => "filter_form", :method => :get do %>
+ diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 7b094582..47ab23d2 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -28,8 +28,8 @@
  • <%= link_to "Print Setting", print_settings_path, :tabindex =>"-1" %>

  • <%= link_to "Employees", settings_employees_path, :tabindex =>"-1" %>
  • -
  • <%= link_to "Commissions", origami_commissions_path , :tabindex =>"-1" %>
  • -
  • <%= link_to "Commissioners", origami_commissioners_path , :tabindex =>"-1" %>
  • +
  • <%= link_to "Commissions", settings_commissions_path , :tabindex =>"-1" %>
  • +
  • <%= link_to "Commissioners", settings_commissioners_path , :tabindex =>"-1" %>

  • <%= link_to "Accounts", settings_accounts_path, :tabindex =>"-1" %>

  • @@ -55,6 +55,7 @@
  • <%= link_to "Shift Sale Report", reports_shiftsale_index_path, :tabindex =>"-1" %>
  • <%= link_to "Credit Sale Report", reports_credit_payment_index_path, :tabindex =>"-1" %>
  • <%= link_to "Void Sale Report", reports_void_sale_index_path, :tabindex =>"-1" %>
  • +
  • <%= link_to "Commission Report", reports_commission_index_path, :tabindex =>"-1" %>
  • - - - - - + + + + \ No newline at end of file + diff --git a/app/views/origami/in_juties/_edit_in_juty.html.erb b/app/views/origami/in_juties/_edit_in_juty.html.erb index a2405f58..9eaf8353 100644 --- a/app/views/origami/in_juties/_edit_in_juty.html.erb +++ b/app/views/origami/in_juties/_edit_in_juty.html.erb @@ -2,8 +2,7 @@ <%= f.error_notification %>
    - <%= f.input :dinning_id %> - <%= f.input :commissioner_ids %> + <%= f.collection_select :commissioner_ids, Commissioner.all, :id, :name, {prompt: 'Select Commissioner'}, {class: 'form-control'} %>

    <%= f.input :in_time %> <%= f.input :out_time %>
    diff --git a/app/views/origami/in_juties/index_in_juty.html.erb b/app/views/origami/in_juties/index_in_juty.html.erb index 0141af05..bbb9912b 100644 --- a/app/views/origami/in_juties/index_in_juty.html.erb +++ b/app/views/origami/in_juties/index_in_juty.html.erb @@ -1,9 +1,10 @@
    - +
    + @@ -15,65 +16,67 @@ <% @juties_in.each do |in_juty| %> - + - - - + <% end %>
    Select Dining Facility Name Commissioner Ids In time
    - + <%= in_juty.dining_facility.name rescue '-' %> <%= in_juty.commissioner.name rescue '-' %> <%= in_juty.in_time.utc.getlocal.strftime("%Y-%m-%d/%I:%M %p") rescue '-' %> <%= in_juty.out_time.utc.getlocal.strftime("%Y-%m-%d/%I:%M %p") rescue '-' %><%= link_to 'Back', origami_path(in_juty.dining_facility.id) %><%= link_to 'Edit', origami_edit_in_juty_path(in_juty.dining_facility.id,in_juty) %><%= link_to 'Destroy', origami_destroy_in_juty_path(in_juty.dining_facility.id,in_juty),method: :delete, data: {confirm: 'Are you sure?'} %><%= link_to 'Destroy', origami_destroy_in_juty_path(in_juty.dining_facility.id, in_juty), method: :delete, data: {confirm: 'Are you sure?'} %>
    + <%= paginate @juties_in %>
    - +
    - <%= render 'assign_in_juty', in_juty: @in_juty ,table: @table %> + <%= render 'assign_in_juty', in_juty: @in_juty, table: @table %>
    +
    + +
    \ No newline at end of file + $('#in_juty_id').val(data.in_juty.id); + $('#in_juty_commissioner_ids').val(data.commissioner.id); + $('#in_juty_in_time').val(data.in_juty.in_time); + $('#in_juty_out_time').val(data.in_juty.out_time); + + $('#update').removeAttr('disabled').val(''); + $('#update').attr('value', 'Update'); + $('#create').attr('disabled', 'disabled'); + + $("#new_in_juty").attr('class', 'simple_form edit_in_juty'); + var id = "edit_in_juty_" + in_juty_id; + $("#new_in_juty").attr('id', id); + + $(".edit_in_juty").attr('id', id); + $(".edit_in_juty").attr('action', '/origami/edit_in_juty/' + $('#in_juty_id').val()); + $(".edit_in_juty").attr('action', '/origami/edit_in_juty/' + $('#in_juty_id').val()); + $(".patch_method").html(''); + } + }); + }); + + $('#back').on('click', function () { + window.location.href = '/origami/table/' + "<%= @table.id %>"; + }) + diff --git a/app/views/reports/commission/_commission_report_filter.html.erb b/app/views/reports/commission/_commission_report_filter.html.erb new file mode 100644 index 00000000..b7ac154e --- /dev/null +++ b/app/views/reports/commission/_commission_report_filter.html.erb @@ -0,0 +1,85 @@ +
    +
    + <%= form_tag report_path, :method => :get, :id => "frm_report", :class => "form" do %> + <% if period_type != false %> +
    + +
    + + <% if @daterange %> + + <% else %> + + <% end %> +
    +
    + + +
    +
    + +
    +
    + +
    +
    + <% end %> + + <% end %> +
    +
    + + diff --git a/app/views/reports/commission/index.html.erb b/app/views/reports/commission/index.html.erb new file mode 100644 index 00000000..4715b00c --- /dev/null +++ b/app/views/reports/commission/index.html.erb @@ -0,0 +1,81 @@ + + +
    + <%= render :partial => 'commission_report_filter', + :locals => {:period_type => true, :shift_name => true, :report_path => reports_commission_index_path} %> +
    +
    + +
    + +
    + +
    +
    +
    + + + + + + + + + + + + + + + + <% total_qty = 0 %> + <% total_price = 0 %> + <% total_amount = 0 %> + + <% @transaction.each do |result| %> + + + + + + + + + <% total_qty += result.qty.to_f %> + <% total_price += result.price.to_f %> + <% total_amount += result.amount.to_f %> + <% end %> + + + + + + + + + +
    From Date : <%= @from.utc.getlocal.strftime("%Y-%b-%d") rescue '-' %> - To Date : <%= @to.utc.getlocal.strftime("%Y-%b-%d") rescue '-' %>
    Commissioner NameProduct NameQtyCommission PriceCommission AmountDate
    + <%= result.commissioner.name rescue '-' %> + + <%= result.commission.menu_item.name rescue '-' %> + <%= sprintf "%.2f", result.qty.to_f.to_d rescue '-' %><%= sprintf "%.2f", result.price.to_f.to_d rescue '-' %><%= sprintf "%.2f", result.amount.to_f.to_d rescue '-' %><%= result.updated_at.strftime("%e %b %Y %I:%M%p") rescue '-' %>
    <%= sprintf("%.2f", total_qty) rescue '-' %><%= sprintf("%.2f", total_price) rescue '-' %><%= sprintf("%.2f", total_amount) rescue '-' %>
    +
    +
    +
    + + + diff --git a/app/views/reports/commission/index.xls.erb b/app/views/reports/commission/index.xls.erb new file mode 100644 index 00000000..4ea1f2f0 --- /dev/null +++ b/app/views/reports/commission/index.xls.erb @@ -0,0 +1,52 @@ +
    +
    +
    + + + + + + + + + + + + + + + + <% total_qty = 0 %> + <% total_price = 0 %> + <% total_amount = 0 %> + + <% @transaction.each do |result| %> + + + + + + + + + <% total_qty += result.qty.to_f %> + <% total_price += result.price.to_f %> + <% total_amount += result.amount.to_f %> + <% end %> + + + + + + + + + +
    From Date : <%= @from.utc.getlocal.strftime("%Y-%b-%d") rescue '-' %> - To Date : <%= @to.utc.getlocal.strftime("%Y-%b-%d") rescue '-' %>
    Commissioner NameProduct NameQtyCommission PriceCommission AmountDate
    + <%= result.commissioner.name rescue '-' %> + + <%= result.commission.menu_item.name rescue '-' %> + <%= sprintf "%.2f", result.qty.to_f.to_d rescue '-' %><%= sprintf "%.2f", result.price.to_f.to_d rescue '-' %><%= sprintf "%.2f", result.amount.to_f.to_d rescue '-' %><%= result.updated_at.strftime("%e %b %Y %I:%M%p") rescue '-' %>
    <%= sprintf("%.2f", total_qty) rescue '-' %><%= sprintf("%.2f", total_price) rescue '-' %><%= sprintf("%.2f", total_amount) rescue '-' %>
    +
    +
    +
    diff --git a/app/views/origami/commissioners/_commissioner.json.jbuilder b/app/views/settings/commissioners/_commissioner.json.jbuilder similarity index 100% rename from app/views/origami/commissioners/_commissioner.json.jbuilder rename to app/views/settings/commissioners/_commissioner.json.jbuilder diff --git a/app/views/settings/commissioners/_form.html.erb b/app/views/settings/commissioners/_form.html.erb new file mode 100644 index 00000000..e2d9d120 --- /dev/null +++ b/app/views/settings/commissioners/_form.html.erb @@ -0,0 +1,42 @@ +
    + <%= simple_form_for([:settings, @commissioner]) do |f| %> + <%= f.error_notification %> + +
    + <%= f.input :name %> + <%= f.label :emp_id, 'Employee' %> + <%= f.collection_select :emp_id, Employee.all.order('name asc'), :id, :name, {prompt: 'Select an Employee'}, {class: "form-control"} %> +
    + <%= f.label :commission_id, 'Commission' %>
    + <%= f.select :commission_id, Commission.all.map {|l| [l.menu_item.name, l.id]}, {prompt: 'Select a Product'}, {class: 'form-control'} %> +
    + <%= f.label :joined_date %>
    + <%= f.text_field :joined_date, {class: 'form-control', id: 'joined_date', readonly: true} %>
    + <%= f.label :resigned_date %>
    + <%= f.text_field :resigned_date, {class: 'form-control', id: 'resigned_date', readonly: true} %>
    + +
    +
    + +
    + <%= link_to 'Back', settings_commissioners_path, class: 'btn btn-success' %> + <%= f.button :submit, class: 'btn btn-info' %> +
    + <% end %> +
    + + diff --git a/app/views/origami/commissioners/edit.html.erb b/app/views/settings/commissioners/edit.html.erb similarity index 57% rename from app/views/origami/commissioners/edit.html.erb rename to app/views/settings/commissioners/edit.html.erb index ea5c44cd..a495cbac 100644 --- a/app/views/origami/commissioners/edit.html.erb +++ b/app/views/settings/commissioners/edit.html.erb @@ -1,8 +1,8 @@
    diff --git a/app/views/origami/commissioners/index.html.erb b/app/views/settings/commissioners/index.html.erb similarity index 52% rename from app/views/origami/commissioners/index.html.erb rename to app/views/settings/commissioners/index.html.erb index dfef0320..e6c1fdc9 100644 --- a/app/views/origami/commissioners/index.html.erb +++ b/app/views/settings/commissioners/index.html.erb @@ -1,9 +1,9 @@ @@ -16,8 +16,10 @@ Name Employee Name Commission type + Joined Date + Resigned Date Active - + @@ -29,10 +31,12 @@ <%= commissioner.employee.name rescue '-' %> <%= commissioner.commission.menu_item.name rescue '-' %> + <%= commissioner.joined_date.utc.getlocal.strftime('%Y-%b-%d') rescue '-' %> + <%= commissioner.resigned_date.utc.getlocal.strftime('%Y-%b-%d') rescue '-' %> <%= commissioner.is_active %> - <%= link_to 'Show', origami_commissioner_path(commissioner) %> - <%= link_to 'Edit', edit_origami_commissioner_path(commissioner) %> - <%= link_to 'Destroy', origami_commissioner_path(commissioner), method: :delete, data: {confirm: 'Are you sure?'} %> + <%= link_to 'Show', settings_commissioner_path(commissioner) %> + <%= link_to 'Edit', edit_settings_commissioner_path(commissioner) %> + <%= link_to 'Destroy', settings_commissioner_path(commissioner), method: :delete, data: {confirm: 'Are you sure?'} %> <% end %> diff --git a/app/views/origami/commissioners/index.json.jbuilder b/app/views/settings/commissioners/index.json.jbuilder similarity index 100% rename from app/views/origami/commissioners/index.json.jbuilder rename to app/views/settings/commissioners/index.json.jbuilder diff --git a/app/views/origami/commissioners/new.html.erb b/app/views/settings/commissioners/new.html.erb similarity index 53% rename from app/views/origami/commissioners/new.html.erb rename to app/views/settings/commissioners/new.html.erb index ea737022..cfdbbe30 100644 --- a/app/views/origami/commissioners/new.html.erb +++ b/app/views/settings/commissioners/new.html.erb @@ -1,8 +1,8 @@
    diff --git a/app/views/settings/commissioners/show.html.erb b/app/views/settings/commissioners/show.html.erb new file mode 100644 index 00000000..7f2005c6 --- /dev/null +++ b/app/views/settings/commissioners/show.html.erb @@ -0,0 +1,132 @@ + + + + + + +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Name<%= @commissioner.name %>
    Employee Name<%= @commissioner.employee.name rescue '-' %>
    Commission Type<%= @commissioner.commission.menu_item.name rescue '-' %>
    Joined Date<%= @commissioner.joined_date.utc.getlocal.strftime('%Y-%b-%d') rescue '-' %>
    Resigned Date<%= @commissioner.resigned_date.utc.getlocal.strftime('%Y-%b-%d') rescue '-' %>
    Active<%= @commissioner.is_active %>
    Created By<%= Employee.find(@commissioner.created_by).name %>
    + <%= link_to 'Back', settings_commissioners_path, class: 'btn btn-success' %> + <%= link_to 'Edit', edit_settings_commissioner_path(@commissioner), class: 'btn btn-info' %> + <%= link_to 'Destroy', settings_commissioner_path(@commissioner), method: :delete, data: {confirm: 'Are you sure?'}, class: 'btn btn-danger' %> +
    +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + +
    <%= 'Product Type' %><%= 'Product Name' %><%= 'Qty' %><%= 'Price' %><%= 'Amount' %><%= 'Date' %>
    +
    +
    +
    + +
    + +
    + + diff --git a/app/views/origami/commissioners/show.json.jbuilder b/app/views/settings/commissioners/show.json.jbuilder similarity index 100% rename from app/views/origami/commissioners/show.json.jbuilder rename to app/views/settings/commissioners/show.json.jbuilder diff --git a/app/views/origami/commissions/_commission.json.jbuilder b/app/views/settings/commissions/_commission.json.jbuilder similarity index 100% rename from app/views/origami/commissions/_commission.json.jbuilder rename to app/views/settings/commissions/_commission.json.jbuilder diff --git a/app/views/origami/commissions/_form.html.erb b/app/views/settings/commissions/_form.html.erb similarity index 58% rename from app/views/origami/commissions/_form.html.erb rename to app/views/settings/commissions/_form.html.erb index 5077f946..2ec9c115 100644 --- a/app/views/origami/commissions/_form.html.erb +++ b/app/views/settings/commissions/_form.html.erb @@ -1,17 +1,17 @@
    -<%= simple_form_for([:origami,@commission]) do |f| %> +<%= simple_form_for([:settings,@commission]) do |f| %> <%= f.error_notification %>
    - <%= f.label :product_id %> - <%= f.collection_select :product_id, @products, :id, :name, {prompt: 'Select a Product'}, {class: 'form-control'} %>
    - <%= f.input :amount %> + <%= f.label :product_code, 'Product' %> + <%= f.collection_select :product_code, @products, :id, :name, {prompt: 'Select a Product'}, {class: 'form-control'} %>
    <%= f.input :commission_type, :collection => ['Percentage','Net Amount'], prompt: 'Select Commission Type', class: 'form-control' %> + <%= f.input :amount %>

    - <%= link_to 'Back', origami_commissions_path, class: 'btn btn-success' %> + <%= link_to 'Back', settings_commissions_path, class: 'btn btn-success' %> <%= f.button :submit, class: 'btn btn-info' %>
    <% end %> diff --git a/app/views/origami/commissions/edit.html.erb b/app/views/settings/commissions/edit.html.erb similarity index 54% rename from app/views/origami/commissions/edit.html.erb rename to app/views/settings/commissions/edit.html.erb index f3b72f3d..ab93e2bc 100644 --- a/app/views/origami/commissions/edit.html.erb +++ b/app/views/settings/commissions/edit.html.erb @@ -1,8 +1,8 @@
    diff --git a/app/views/origami/commissions/index.html.erb b/app/views/settings/commissions/index.html.erb similarity index 62% rename from app/views/origami/commissions/index.html.erb rename to app/views/settings/commissions/index.html.erb index da589347..a30fda08 100644 --- a/app/views/origami/commissions/index.html.erb +++ b/app/views/settings/commissions/index.html.erb @@ -1,9 +1,9 @@ @@ -14,8 +14,8 @@ Product Name + Commission Type Amount - Commission type Active @@ -25,12 +25,12 @@ <% @commissions.each do |commission| %> <%= commission.menu_item.name rescue '-' %> - <%= commission.amount rescue '-' %> <%= commission.commission_type rescue '-' %> + <%= commission.amount rescue '-' %> <%= commission.is_active rescue '-' %> - <%= link_to 'Show', origami_commission_path(commission) %> - <%= link_to 'Edit', edit_origami_commission_path(commission) %> - <%= link_to 'Destroy', origami_commission_path(commission), method: :delete, data: {confirm: 'Are you sure?'} %> + <%= link_to 'Show', settings_commissions_path(commission) %> + <%= link_to 'Edit', edit_settings_commission_path(commission) %> + <%= link_to 'Destroy', settings_commission_path(commission), method: :delete, data: {confirm: 'Are you sure?'} %> <% end %> diff --git a/app/views/origami/commissions/index.json.jbuilder b/app/views/settings/commissions/index.json.jbuilder similarity index 100% rename from app/views/origami/commissions/index.json.jbuilder rename to app/views/settings/commissions/index.json.jbuilder diff --git a/app/views/origami/commissions/load_commissioners.html.erb b/app/views/settings/commissions/load_commissioners.html.erb similarity index 100% rename from app/views/origami/commissions/load_commissioners.html.erb rename to app/views/settings/commissions/load_commissioners.html.erb diff --git a/app/views/origami/commissions/new.html.erb b/app/views/settings/commissions/new.html.erb similarity index 54% rename from app/views/origami/commissions/new.html.erb rename to app/views/settings/commissions/new.html.erb index a13c8cb3..35560940 100644 --- a/app/views/origami/commissions/new.html.erb +++ b/app/views/settings/commissions/new.html.erb @@ -1,8 +1,8 @@
    diff --git a/app/views/origami/commissions/show.html.erb b/app/views/settings/commissions/show.html.erb similarity index 63% rename from app/views/origami/commissions/show.html.erb rename to app/views/settings/commissions/show.html.erb index 439e2c45..557c7465 100644 --- a/app/views/origami/commissions/show.html.erb +++ b/app/views/settings/commissions/show.html.erb @@ -2,8 +2,8 @@
    diff --git a/app/views/origami/commissions/show.json.jbuilder b/app/views/settings/commissions/show.json.jbuilder similarity index 100% rename from app/views/origami/commissions/show.json.jbuilder rename to app/views/settings/commissions/show.json.jbuilder diff --git a/app/views/settings/promotions/new.html.erb b/app/views/settings/promotions/new.html.erb index c7748bb6..321aa4d1 100644 --- a/app/views/settings/promotions/new.html.erb +++ b/app/views/settings/promotions/new.html.erb @@ -13,5 +13,4 @@ $("#promotion_promo_code").val(Math.random().toString(36).slice(5) + Math.random().toString(36).slice(5)); // $( "#fromtime" ).timepicker(); // $( "#totime" ).timepicker({ 'scrollDefault': 'now' }); -$('#scrollDefaultExample').timepicker({ 'scrollDefault': 'now' }); diff --git a/config/initializers/ranged_datetime_wrapper.rb b/config/initializers/ranged_datetime_wrapper.rb new file mode 100644 index 00000000..c70a7681 --- /dev/null +++ b/config/initializers/ranged_datetime_wrapper.rb @@ -0,0 +1,13 @@ +SimpleForm.setup do |config| + config.wrappers :ranged_datetime, tag: 'div', class: 'form-group col-md-6', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :readonly + + b.use :label, class: 'control-label' + b.use :input, class: 'form-control' + + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end +end diff --git a/config/routes.rb b/config/routes.rb index add37c76..c6deb5aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,10 +11,10 @@ Rails.application.routes.draw do #--------- SmartSales Installation ------------# get 'install' => 'install#index' - post 'install' => 'install#create' + post 'install' => 'install#create' #--------- Login/Authentication ------------# - get 'auth/:emp_id' => 'home#show' , as: :emp_login + get 'auth/:emp_id' => 'home#show', as: :emp_login patch "auth/:emp_id" => 'home#update', as: :emp_login_update post 'login' => 'home#create' @@ -23,14 +23,14 @@ Rails.application.routes.draw do #--------- API Routes ------------# - namespace :api, :defaults => { :format => 'json' } do + namespace :api, :defaults => {:format => 'json'} do #Session Login and Logout - post 'authenticate' => "authenticate#create" + post 'authenticate' => "authenticate#create" delete 'authenticate' => "authenticate#destroy" namespace :restaurant do get 'zones' => "zones#index" - resources :menu, only:[:index, :show] + resources :menu, only: [:index, :show] resources :menu_categories, only: [:index, :show] resources :menu_items, only: [:index, :show] resources :menu_item_attributes, only: [:index] @@ -57,10 +57,10 @@ Rails.application.routes.draw do get "customers/get_order/:id" => "customers#get_customer_order" #Generating Invoice and making payments - output render @sale - resources :invoices, only: [:index, :show, :create, :update, :destroy ] do - resources :sale_items, only:[:create, :update, :destroy] + resources :invoices, only: [:index, :show, :create, :update, :destroy] do + resources :sale_items, only: [:create, :update, :destroy] resources :discounts, only: [:create, :update, :destroy] - resources :memberships, only:[:create] + resources :memberships, only: [:create] post "payment/:payment_method" => "payment#create" put "payment/:id" => "payment#update" resources :receipt, only: [:create, :show] #generate receipt, show receipt @@ -72,8 +72,8 @@ Rails.application.routes.draw do #--------- Cashier ------------# namespace :origami do - resources :cash_ins, only:[:new, :create] - resources :cash_outs, only:[:new, :create] + resources :cash_ins, only: [:new, :create] + resources :cash_outs, only: [:new, :create] root "home#index" get "table/:dining_id" => "home#show" do #origami/:booking_id will show # resources :discounts, only: [:index,:new, :create ] #add discount type @@ -87,17 +87,13 @@ Rails.application.routes.draw do post 'item_void_cancel' => "sale_edit#item_void_cancel" post 'cancel_all_void' => 'sale_edit#cancel_all_void' post 'apply_void' => 'sale_edit#apply_void' - # commissions - get '/table/:table_id/sale/:sale_id/load_commissioners' => 'commissions#load_commissioners', as: 'load_commissioners' - post 'select_sale_item' => 'commissions#select_sale_item' - # product_commission - post 'select_commissioner' => 'product_commissions#set_commissioner_to_sale_item' # in_juties get '/table/:table_id/assign_in_juty' => 'in_juties#assign_in_juty', as: 'assign_in_juty' - post 'assign_in_juty' => 'in_juties#create_for_in_juty', as: 'create_for_in_juty' get 'assign_in_juty/:table_id' => 'in_juties#index_in_juty', as: 'index_in_juty' - get 'table/:table_id/in_juty/:id/edit' => 'in_juties#edit_in_juty' ,as: 'edit_in_juty' - put '/edit_in_juty/:id' => 'in_juties#update_for_in_juty', as: 'update_for_in_juty' + post 'assign_in_juty/:table_id' => 'in_juties#create_for_in_juty' + + get 'assign_in_juty/table/:table_id/in_juty/:id/edit' => 'in_juties#edit_in_juty' ,as: 'edit_in_juty' + patch 'edit_in_juty/:id' => 'in_juties#update_for_in_juty', as: 'update_for_in_juty' delete 'table/:table_id/destroy_in_juty/:id' => 'in_juties#destroy_in_juty', as: 'destroy_in_juty' get 'table/:dining_id/movetable' => "movetable#move_dining" @@ -122,8 +118,8 @@ Rails.application.routes.draw do # Discount for Member post "/:id/member_discount" => "discounts#member_discount" - get "/:id/request_bills" => "request_bills#print",:as => "request_bill" - get '/:sale_id/reprint' => 'payments#reprint' ,:defaults => { :format => 'json' } + get "/:id/request_bills" => "request_bills#print", :as => "request_bill" + get '/:sale_id/reprint' => 'payments#reprint', :defaults => {:format => 'json'} #---------Shift ---------------# resources :shifts, only: [:index, :new, :create, :edit] @@ -141,11 +137,11 @@ Rails.application.routes.draw do #payment - Outing payments - Cash only [ *Misc expeness tracking] #--------- Payment ------------# - post 'sale/:sale_id/rounding_adj' => 'payments#rounding_adj',:as => "calculate_rouding_adjs" - get 'sale/:sale_id/first_bill' => 'payments#first_bill', :defaults => { :format => 'json' } + post 'sale/:sale_id/rounding_adj' => 'payments#rounding_adj', :as => "calculate_rouding_adjs" + get 'sale/:sale_id/first_bill' => 'payments#first_bill', :defaults => {:format => 'json'} get 'sale/:sale_id/payment' => 'payments#show' - post 'payment/foc' => 'payments#foc', :defaults => { :format => 'json' } + post 'payment/foc' => 'payments#foc', :defaults => {:format => 'json'} post 'payment/cash' => 'payments#create' post 'payment/mpu' => "mpu#create" post 'payment/jcb' => "jcb#create" @@ -168,43 +164,41 @@ Rails.application.routes.draw do post 'sale/:sale_id/void' => 'void#overall_void' #---------Multiple Invoices --------------# - get 'table/:table_id/table_invoices' => "table_invoices#index" , :as => "table_invoice_index" - get 'table/:table_id/table_invoice/:invoice_id' => "table_invoices#show" , :as => "table_invoice_show" - get 'room/:room_id/room_invoices' => "room_invoices#index" , :as => "room_invoice_index" - get 'room/:room_id/room_invoice/:invoice_id' => "room_invoices#show" , :as => "room_invoice_show" + get 'table/:table_id/table_invoices' => "table_invoices#index", :as => "table_invoice_index" + get 'table/:table_id/table_invoice/:invoice_id' => "table_invoices#show", :as => "table_invoice_show" + get 'room/:room_id/room_invoices' => "room_invoices#index", :as => "room_invoice_index" + get 'room/:room_id/room_invoice/:invoice_id' => "room_invoices#show", :as => "room_invoice_show" #---------Add Customer --------------# #resources :customers get '/:sale_id/customers', to: "customers#add_customer" - get '/:customer_id/get_customer' => 'home#get_customer',:as => "show_customer_details" - post '/:sale_id/update_sale' , to: "customers#update_sale_by_customer" # update customer id in sale table + get '/:customer_id/get_customer' => 'home#get_customer', :as => "show_customer_details" + post '/:sale_id/update_sale', to: "customers#update_sale_by_customer" # update customer id in sale table post '/:sale_id/get_customer' => "customers#get_customer" resources :addorders - resources :commissions - resources :commissioners resources :in_juties end #--------- Waiter/Ordering Station ------------# namespace :oishi do - #zones - #tables - #orders + #zones + #tables + #orders end #--------- Customer Relationship Management ------------# namespace :crm do - root "home#index" - resources :customers - resources :dining_queues - post "update_booking" , to: "bookings#update_booking", as: "update_booking"#assign and cancel - get '/print/:id', to: "home#print_order"#print order for crm + root "home#index" + resources :customers + resources :dining_queues + post "update_booking", to: "bookings#update_booking", as: "update_booking" #assign and cancel + get '/print/:id', to: "home#print_order" #print order for crm - get "/dining_queues/:id/assign" =>"dining_queues#assign", :as => "assign" - post "/dining_queues/assign_table" =>"dining_queues#assign_table", :as => "assign_table" - post "/dining_queues/cancel_queue" =>"dining_queues#cancel_queue", :as => "cancel_queue" + get "/dining_queues/:id/assign" => "dining_queues#assign", :as => "assign" + post "/dining_queues/assign_table" => "dining_queues#assign_table", :as => "assign_table" + post "/dining_queues/cancel_queue" => "dining_queues#cancel_queue", :as => "cancel_queue" end @@ -223,7 +217,7 @@ Rails.application.routes.draw do get 'print/print/:id', to: "print#print" get 'print/print_order_summary/:id', to: "print#print_order_summary" - get "/get_items/:id" =>"home#get_items_by_oqs", :as => "get_order_items_by_oqs" + get "/get_items/:id" => "home#get_items_by_oqs", :as => "get_order_items_by_oqs" #dashboard # end @@ -235,7 +229,7 @@ Rails.application.routes.draw do #menu resources :menus do #menu_categories - resources :menu_categories, only: [:new, :create, :edit,:delete] + resources :menu_categories, only: [:new, :create, :edit, :delete] end resources :item_sets @@ -299,16 +293,28 @@ Rails.application.routes.draw do resources :promotion_products end + # commission + resources :commissions + resources :commissioners + + get '/get_transactions_by_commissioner' => 'commissioners#get_transaction_by_commissioner', as:'get_transaction_by_commissioner' + end + # commissions + get 'origami/table/:table_id/sale/:sale_id/load_commissioners' => 'settings/commissions#load_commissioners', as: 'load_commissioners' + post 'origami/select_sale_item' => 'settings/commissions#select_sale_item', as: 'select_sale_item' + # product_commission + post 'origami/select_commissioner' => 'origami/product_commissions#set_commissioner_to_sale_item', as: 'select_commissioner' + #--------- Transactions Sections ------------# namespace :transactions do resources :sales resources :orders resources :credit_notes - get "/sales/:sale_id/manual_complete_sale" =>"manual_sales#manual_complete_sale", :as => "manual_complete_sale" - get "/sales/:sale_id/void" =>"manual_sales#void", :as => "void" + get "/sales/:sale_id/manual_complete_sale" => "manual_sales#manual_complete_sale", :as => "manual_complete_sale" + get "/sales/:sale_id/void" => "manual_sales#void", :as => "void" post "sales/:sale_id/manual_void_sale", to: "manual_sales#manual_void_sale", :as => "manual_void_sale" end @@ -320,6 +326,7 @@ Rails.application.routes.draw do resources :shiftsale, :only => [:index, :show] resources :credit_payment, :only => [:index, :show] resources :void_sale, :only => [:index, :show] + resources :commission, :only => [:index, :show] get "receipt_no/get_shift_by_date", to: "receipt_no#get_shift_by_date", as: "get_shift_by_date" end diff --git a/db/migrate/20170403140820_create_order_items.rb b/db/migrate/20170403140820_create_order_items.rb index 1882bd62..0bca8500 100644 --- a/db/migrate/20170403140820_create_order_items.rb +++ b/db/migrate/20170403140820_create_order_items.rb @@ -6,6 +6,7 @@ class CreateOrderItems < ActiveRecord::Migration[5.1] t.string :order_item_status, :null => false, :default => "new" t.string :item_order_by #person who order this t.string :item_code, :null => false + t.string :item_instance_code t.string :item_name, :null => false t.string :alt_name, :null => false t.integer :account_id, :limit => 8, :null => false, :default => 1 diff --git a/db/migrate/20170818090115_create_commissioners.rb b/db/migrate/20170818090115_create_commissioners.rb deleted file mode 100644 index b8c4bca3..00000000 --- a/db/migrate/20170818090115_create_commissioners.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateCommissioners < ActiveRecord::Migration[5.1] - def change - create_table :commissioners do |t| - t.string :name, :null => false - t.string :emp_id - t.string :created_by - t.string :commission_type - t.boolean :is_active - t.timestamps - end - end -end diff --git a/db/migrate/20170821093252_create_commissions.rb b/db/migrate/20170821093252_create_commissions.rb deleted file mode 100644 index ebd7d8dd..00000000 --- a/db/migrate/20170821093252_create_commissions.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateCommissions < ActiveRecord::Migration[5.1] - def change - create_table :commissions do |t| - t.integer :product_id, null: false - t.integer :amount - t.string :commission_type - t.boolean :is_active - t.timestamps - end - end -end diff --git a/db/migrate/20170823034141_create_product_commissions.rb b/db/migrate/20170823034141_create_product_commissions.rb deleted file mode 100644 index cb0e5a3f..00000000 --- a/db/migrate/20170823034141_create_product_commissions.rb +++ /dev/null @@ -1,16 +0,0 @@ -class CreateProductCommissions < ActiveRecord::Migration[5.1] - # rake db:migrate:down VERSION=20170823034141 - def change - create_table :product_commissions do |t| - t.string :product_id - t.integer :commission_id - t.integer :commissioner_id - t.decimal :qty, :precision => 10, :scale => 2, :default => 0.00 - t.string :sale_id - t.string :sale_item_id - t.decimal :price, :precision => 10, :scale => 2, :default => 0.00 - t.decimal :amount, :precision => 10, :scale => 2, :default => 0.00 - t.timestamps - end - end -end diff --git a/db/migrate/20170825034141_create_product_commissions.rb b/db/migrate/20170825034141_create_product_commissions.rb new file mode 100644 index 00000000..f083bf68 --- /dev/null +++ b/db/migrate/20170825034141_create_product_commissions.rb @@ -0,0 +1,17 @@ +class CreateProductCommissions < ActiveRecord::Migration[5.1] + # rake db:migrate:down VERSION=20170825034141 + def change + create_table :product_commissions do |t| + t.string :product_type + t.string :product_code + t.string :commission_id + t.integer :commissioner_id + t.decimal :qty, precision: 10, scale: 2, default: 0.00 + t.string :sale_id + t.string :sale_item_id + t.decimal :price, precision: 10, scale: 2, default: 0.00 + t.decimal :amount, precision: 10, scale: 2, default: 0.00 + t.timestamps + end + end +end diff --git a/db/migrate/20170825090115_create_commissioners.rb b/db/migrate/20170825090115_create_commissioners.rb new file mode 100644 index 00000000..1794b5c7 --- /dev/null +++ b/db/migrate/20170825090115_create_commissioners.rb @@ -0,0 +1,15 @@ +class CreateCommissioners < ActiveRecord::Migration[5.1] + # rake db:migrate:down VERSION=20170825090115 + def change + create_table :commissioners do |t| + t.string :name + t.string :emp_id + t.string :created_by + t.string :commission_id + t.datetime :joined_date + t.datetime :resigned_date + t.boolean :is_active, default: true + t.timestamps + end + end +end diff --git a/db/migrate/20170825093252_create_commissions.rb b/db/migrate/20170825093252_create_commissions.rb new file mode 100644 index 00000000..8c344fbc --- /dev/null +++ b/db/migrate/20170825093252_create_commissions.rb @@ -0,0 +1,15 @@ +class CreateCommissions < ActiveRecord::Migration[5.1] + # rake db:migrate:down VERSION=20170825093252 + def change + create_table :commissions, id: false do |t| + t.string :commission_id, limit: 16, primary_key: true # custom primary key + + t.string :product_type + t.string :product_code + t.string :commission_type + t.integer :amount + t.boolean :is_active, default: true + t.timestamps + end + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 27859c89..0da65820 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -218,25 +218,26 @@ zone3 = Zone.create({id:3, name: "H3", is_active:true, created_by: "SYSTEM DEFAU table = Table.create({name:"77", zone: zone3, status:"available", seater: 2 , order_by:1, created_by:"SYSTEM DEFAULT"}) table = Table.create({name:"78", zone: zone3, status:"available", seater: 2 , order_by:1, created_by:"SYSTEM DEFAULT"}) -member_setting = MembershipSetting.create({membership_type:"paypar_url",gateway_url: "http://staging.membership.paypar.ws",merchant_account_id:"vWSsseoZCzxd6xcNf_uS"}) +member_setting = MembershipSetting.create({membership_type:"paypar_url",gateway_url: "http://staging.membership.paypar.ws",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv"}) -member_actions= MembershipAction.create([{membership_type:"get_account_balance",gateway_url:"/api/membership_campaigns/get_correspond_account_data",additional_parameter:{campaign_type_id:1},merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"redeem",gateway_url:"/api/membership_campaigns/redeem",additional_parameter:{campaign_type_id:1},merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"create_membership_customer",gateway_url:"/api/generic_customer/create_membership_customer",merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"update_membership_customer",gateway_url:"/api/generic_customer/update_membership_customer",merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"get_all_member_group",gateway_url:"/api/member_group/get_all_member_group",merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"rebate",gateway_url:"/api/membership_campaigns/rebate",additional_parameter:{campaign_type_id:1},merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"get_all_member_account",gateway_url:"/api/generic_customer/get_membership_data",merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"get_member_transactions",gateway_url:"/api/generic_customer/get_membership_transactions",merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"member_discount",gateway_url:"/api/membership_campaigns/discount",additional_parameter:{campaign_type_id:6},merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, - {membership_type:"get_member_campaign",gateway_url:"/api/membership_campaigns/get_member_campaign",additional_parameter:{campaign_type_id:6},merchant_account_id:"vWSsseoZCzxd6xcNf_uS",auth_token:"code2lab"}, +member_actions= MembershipAction.create([{membership_type:"get_account_balance",gateway_url:"/api/membership_campaigns/get_correspond_account_data",additional_parameter:{campaign_type_id:1},merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"redeem",gateway_url:"/api/membership_campaigns/redeem",additional_parameter:{campaign_type_id:1},merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"create_membership_customer",gateway_url:"/api/generic_customer/create_membership_customer",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"update_membership_customer",gateway_url:"/api/generic_customer/update_membership_customer",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"get_all_member_group",gateway_url:"/api/member_group/get_all_member_group",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"rebate",gateway_url:"/api/membership_campaigns/rebate",additional_parameter:{campaign_type_id:1},merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"get_all_member_account",gateway_url:"/api/generic_customer/get_membership_data",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"get_member_transactions",gateway_url:"/api/generic_customer/get_membership_transactions",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"member_discount",gateway_url:"/api/membership_campaigns/discount",additional_parameter:{campaign_type_id:6},merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"get_member_campaign",gateway_url:"/api/membership_campaigns/get_member_campaign",additional_parameter:{campaign_type_id:6},merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"}, + {membership_type:"search_paypar_account_no",gateway_url:"/api/generic_customer/get_membership_customer_data",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv",auth_token:"code2lab"} ]) payment_methods = PaymentMethodSetting.create({payment_method:"MPU",gateway_url: "http://membership.paypar.ws"}) payment_methods = PaymentMethodSetting.create({payment_method:"VISA",gateway_url: "http://membership.paypar.ws"}) payment_methods = PaymentMethodSetting.create({payment_method:"JCB",gateway_url: "http://membership.paypar.ws"}) payment_methods = PaymentMethodSetting.create({payment_method:"Master",gateway_url: "http://membership.paypar.ws"}) -payment_methods = PaymentMethodSetting.create({payment_method:"Redeem",gateway_url: "http://membership.paypar.ws",merchant_account_id:"vWSsseoZCzxd6xcNf_uS"}) +payment_methods = PaymentMethodSetting.create({payment_method:"Redeem",gateway_url: "http://membership.paypar.ws",merchant_account_id:"RxzaYyAGzm7VqAZ4hKnv"}) #Default Order Queue stations order_queue_station1 = OrderQueueStation.create({station_name: "K1", is_active: true,printer_name: "Cashier", processing_items: JSON.generate(['01001','01002','01003','01004']), print_copy:true, cut_per_item: false, use_alternate_name: false, created_by: "SYSTEM DEFAULT"}) diff --git a/vendor/assets/javascripts/bootstrap-datetimepicker.js b/vendor/assets/javascripts/bootstrap-datetimepicker.js new file mode 100644 index 00000000..74241c3c --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-datetimepicker.js @@ -0,0 +1,2053 @@ +/*! version : 4.7.15 + ========================================================= + bootstrap-datetimejs + https://github.com/Eonasdan/bootstrap-datetimepicker + Copyright (c) 2015 Jonathan Peterson + ========================================================= + */ +/* + The MIT License (MIT) + + Copyright (c) 2015 Jonathan Peterson + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +/*global define:false */ +/*global exports:false */ +/*global require:false */ +/*global jQuery:false */ +/*global moment:false */ +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // AMD is used - Register as an anonymous module. + define(['jquery', 'moment'], factory); + } else if (typeof exports === 'object') { + factory(require('jquery'), require('moment')); + } else { + // Neither AMD nor CommonJS used. Use global variables. + if (typeof jQuery === 'undefined') { + throw 'bootstrap-datetimepicker requires jQuery to be loaded first'; + } + if (typeof moment === 'undefined') { + throw 'bootstrap-datetimepicker requires Moment.js to be loaded first'; + } + factory(jQuery, moment); + } +}(function ($, moment) { + 'use strict'; + if (!moment) { + throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first'); + } + + var dateTimePicker = function (element, options) { + var picker = {}, + date = moment().startOf('d'), + viewDate = date.clone(), + unset = true, + input, + component = false, + widget = false, + use24Hours, + minViewModeNumber = 0, + actualFormat, + parseFormats, + currentViewMode, + datePickerModes = [ + { + clsName: 'days', + navFnc: 'M', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'y', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'y', + navStep: 10 + } + ], + viewModes = ['days', 'months', 'years'], + verticalModes = ['top', 'bottom', 'auto'], + horizontalModes = ['left', 'right', 'auto'], + toolbarPlacements = ['default', 'top', 'bottom'], + keyMap = { + 'up': 38, + 38: 'up', + 'down': 40, + 40: 'down', + 'left': 37, + 37: 'left', + 'right': 39, + 39: 'right', + 'tab': 9, + 9: 'tab', + 'escape': 27, + 27: 'escape', + 'enter': 13, + 13: 'enter', + 'pageUp': 33, + 33: 'pageUp', + 'pageDown': 34, + 34: 'pageDown', + 'shift': 16, + 16: 'shift', + 'control': 17, + 17: 'control', + 'space': 32, + 32: 'space', + 't': 84, + 84: 't', + 'delete': 46, + 46: 'delete' + }, + keyState = {}, + + /******************************************************************************** + * + * Private functions + * + ********************************************************************************/ + isEnabled = function (granularity) { + if (typeof granularity !== 'string' || granularity.length > 1) { + throw new TypeError('isEnabled expects a single character string parameter'); + } + switch (granularity) { + case 'y': + return actualFormat.indexOf('Y') !== -1; + case 'M': + return actualFormat.indexOf('M') !== -1; + case 'd': + return actualFormat.toLowerCase().indexOf('d') !== -1; + case 'h': + case 'H': + return actualFormat.toLowerCase().indexOf('h') !== -1; + case 'm': + return actualFormat.indexOf('m') !== -1; + case 's': + return actualFormat.indexOf('s') !== -1; + default: + return false; + } + }, + + hasTime = function () { + return (isEnabled('h') || isEnabled('m') || isEnabled('s')); + }, + + hasDate = function () { + return (isEnabled('y') || isEnabled('M') || isEnabled('d')); + }, + + getDatePickerTemplate = function () { + var headTemplate = $('') + .append($('') + .append($('').addClass('prev').attr('data-action', 'previous') + .append($('').addClass(options.icons.previous)) + ) + .append($('').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5'))) + .append($('').addClass('next').attr('data-action', 'next') + .append($('').addClass(options.icons.next)) + ) + ), + contTemplate = $('') + .append($('') + .append($('').attr('colspan', (options.calendarWeeks ? '8' : '7'))) + ); + + return [ + $('
    ').addClass('datepicker-days') + .append($('').addClass('table-condensed') + .append(headTemplate) + .append($('')) + ), + $('
    ').addClass('datepicker-months') + .append($('
    ').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ), + $('
    ').addClass('datepicker-years') + .append($('
    ').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ) + ]; + }, + + getTimePickerMainTemplate = function () { + var topRow = $(''), + middleRow = $(''), + bottomRow = $(''); + + if (isEnabled('h')) { + topRow.append($('
    ') + .append($('').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'incrementHours') + .append($('').addClass(options.icons.up)))); + middleRow.append($('') + .append($('').addClass('timepicker-hour').attr('data-time-component', 'hours').attr('data-action', 'showHours'))); + bottomRow.append($('') + .append($('').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'decrementHours') + .append($('').addClass(options.icons.down)))); + } + if (isEnabled('m')) { + if (isEnabled('h')) { + topRow.append($('').addClass('separator')); + middleRow.append($('').addClass('separator').html(':')); + bottomRow.append($('').addClass('separator')); + } + topRow.append($('') + .append($('').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'incrementMinutes') + .append($('').addClass(options.icons.up)))); + middleRow.append($('') + .append($('').addClass('timepicker-minute').attr('data-time-component', 'minutes').attr('data-action', 'showMinutes'))); + bottomRow.append($('') + .append($('').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'decrementMinutes') + .append($('').addClass(options.icons.down)))); + } + if (isEnabled('s')) { + if (isEnabled('m')) { + topRow.append($('').addClass('separator')); + middleRow.append($('').addClass('separator').html(':')); + bottomRow.append($('').addClass('separator')); + } + topRow.append($('') + .append($('').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'incrementSeconds') + .append($('').addClass(options.icons.up)))); + middleRow.append($('') + .append($('').addClass('timepicker-second').attr('data-time-component', 'seconds').attr('data-action', 'showSeconds'))); + bottomRow.append($('') + .append($('').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'decrementSeconds') + .append($('').addClass(options.icons.down)))); + } + + if (!use24Hours) { + topRow.append($('').addClass('separator')); + middleRow.append($('') + .append($('').addClass('separator')); + } + + return $('
    ').addClass('timepicker-picker') + .append($('').addClass('table-condensed') + .append([topRow, middleRow, bottomRow])); + }, + + getTimePickerTemplate = function () { + var hoursView = $('
    ').addClass('timepicker-hours') + .append($('
    ').addClass('table-condensed')), + minutesView = $('
    ').addClass('timepicker-minutes') + .append($('
    ').addClass('table-condensed')), + secondsView = $('
    ').addClass('timepicker-seconds') + .append($('
    ').addClass('table-condensed')), + ret = [getTimePickerMainTemplate()]; + + if (isEnabled('h')) { + ret.push(hoursView); + } + if (isEnabled('m')) { + ret.push(minutesView); + } + if (isEnabled('s')) { + ret.push(secondsView); + } + + return ret; + }, + + getToolbar = function () { + var row = []; + if (options.showTodayButton) { + row.push($('
    ').append($('').attr('data-action', 'today').append($('').addClass(options.icons.today)))); + } + if (!options.sideBySide && hasDate() && hasTime()) { + row.push($('').append($('').attr('data-action', 'togglePicker').append($('').addClass(options.icons.time)))); + } + if (options.showClear) { + row.push($('').append($('').attr('data-action', 'clear').append($('').addClass(options.icons.clear)))); + } + if (options.showClose) { + row.push($('').append($('').attr('data-action', 'close').append($('').addClass(options.icons.close)))); + } + return $('').addClass('table-condensed').append($('').append($('').append(row))); + }, + + getTemplate = function () { + var template = $('
    ').addClass('bootstrap-datetimepicker-widget dropdown-menu'), + dateView = $('
    ').addClass('datepicker').append(getDatePickerTemplate()), + timeView = $('
    ').addClass('timepicker').append(getTimePickerTemplate()), + content = $('
      ').addClass('list-unstyled'), + toolbar = $('
    • ').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar()); + + if (options.inline) { + template.removeClass('dropdown-menu'); + } + + if (use24Hours) { + template.addClass('usetwentyfour'); + } + if (options.sideBySide && hasDate() && hasTime()) { + template.addClass('timepicker-sbs'); + template.append( + $('
      ').addClass('row') + .append(dateView.addClass('col-sm-6')) + .append(timeView.addClass('col-sm-6')) + ); + template.append(toolbar); + return template; + } + + if (options.toolbarPlacement === 'top') { + content.append(toolbar); + } + if (hasDate()) { + content.append($('
    • ').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView)); + } + if (options.toolbarPlacement === 'default') { + content.append(toolbar); + } + if (hasTime()) { + content.append($('
    • ').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView)); + } + if (options.toolbarPlacement === 'bottom') { + content.append(toolbar); + } + return template.append(content); + }, + + dataToOptions = function () { + var eData, + dataOptions = {}; + + if (element.is('input') || options.inline) { + eData = element.data(); + } else { + eData = element.find('input').data(); + } + + if (eData.dateOptions && eData.dateOptions instanceof Object) { + dataOptions = $.extend(true, dataOptions, eData.dateOptions); + } + + $.each(options, function (key) { + var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); + if (eData[attributeName] !== undefined) { + dataOptions[key] = eData[attributeName]; + } + }); + return dataOptions; + }, + + place = function () { + var position = (component || element).position(), + offset = (component || element).offset(), + vertical = options.widgetPositioning.vertical, + horizontal = options.widgetPositioning.horizontal, + parent; + + if (options.widgetParent) { + parent = options.widgetParent.append(widget); + } else if (element.is('input')) { + parent = element.parent().append(widget); + } else if (options.inline) { + parent = element.append(widget); + return; + } else { + parent = element; + element.children().first().after(widget); + } + + // Top and bottom logic + if (vertical === 'auto') { + if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && + widget.height() + element.outerHeight() < offset.top) { + vertical = 'top'; + } else { + vertical = 'bottom'; + } + } + + // Left and right logic + if (horizontal === 'auto') { + if (parent.width() < offset.left + widget.outerWidth() / 2 && + offset.left + widget.outerWidth() > $(window).width()) { + horizontal = 'right'; + } else { + horizontal = 'left'; + } + } + + if (vertical === 'top') { + widget.addClass('top').removeClass('bottom'); + } else { + widget.addClass('bottom').removeClass('top'); + } + + if (horizontal === 'right') { + widget.addClass('pull-right'); + } else { + widget.removeClass('pull-right'); + } + + // find the first parent element that has a relative css positioning + if (parent.css('position') !== 'relative') { + parent = parent.parents().filter(function () { + return $(this).css('position') === 'relative'; + }).first(); + } + + if (parent.length === 0) { + throw new Error('datetimepicker component should be placed within a relative positioned container'); + } + + widget.css({ + top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(), + bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto', + left: horizontal === 'left' ? parent.css('padding-left') : 'auto', + right: horizontal === 'left' ? 'auto' : parent.width() - element.outerWidth() + }); + }, + + notifyEvent = function (e) { + if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) { + return; + } + element.trigger(e); + }, + + showMode = function (dir) { + if (!widget) { + return; + } + if (dir) { + currentViewMode = Math.max(minViewModeNumber, Math.min(2, currentViewMode + dir)); + } + widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show(); + }, + + fillDow = function () { + var row = $('
    '), + currentDate = viewDate.clone().startOf('w'); + + if (options.calendarWeeks === true) { + row.append($(''); + if (options.calendarWeeks) { + row.append(''); + } + html.push(row); + } + clsName = ''; + if (currentDate.isBefore(viewDate, 'M')) { + clsName += ' old'; + } + if (currentDate.isAfter(viewDate, 'M')) { + clsName += ' new'; + } + if (currentDate.isSame(date, 'd') && !unset) { + clsName += ' active'; + } + if (!isValid(currentDate, 'd')) { + clsName += ' disabled'; + } + if (currentDate.isSame(moment(), 'd')) { + clsName += ' today'; + } + if (currentDate.day() === 0 || currentDate.day() === 6) { + clsName += ' weekend'; + } + row.append(''); + currentDate.add(1, 'd'); + } + + daysView.find('tbody').empty().append(html); + + updateMonths(); + + updateYears(); + }, + + fillHours = function () { + var table = widget.find('.timepicker-hours table'), + currentHour = viewDate.clone().startOf('d'), + html = [], + row = $(''); + + if (viewDate.hour() > 11 && !use24Hours) { + currentHour.hour(12); + } + while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) { + if (currentHour.hour() % 4 === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentHour.add(1, 'h'); + } + table.empty().append(html); + }, + + fillMinutes = function () { + var table = widget.find('.timepicker-minutes table'), + currentMinute = viewDate.clone().startOf('h'), + html = [], + row = $(''), + step = options.stepping === 1 ? 5 : options.stepping; + + while (viewDate.isSame(currentMinute, 'h')) { + if (currentMinute.minute() % (step * 4) === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentMinute.add(step, 'm'); + } + table.empty().append(html); + }, + + fillSeconds = function () { + var table = widget.find('.timepicker-seconds table'), + currentSecond = viewDate.clone().startOf('m'), + html = [], + row = $(''); + + while (viewDate.isSame(currentSecond, 'm')) { + if (currentSecond.second() % 20 === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentSecond.add(5, 's'); + } + + table.empty().append(html); + }, + + fillTime = function () { + var timeComponents = widget.find('.timepicker span[data-time-component]'); + if (!use24Hours) { + widget.find('.timepicker [data-action=togglePeriod]').text(date.format('A')); + } + timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh')); + timeComponents.filter('[data-time-component=minutes]').text(date.format('mm')); + timeComponents.filter('[data-time-component=seconds]').text(date.format('ss')); + + fillHours(); + fillMinutes(); + fillSeconds(); + }, + + update = function () { + if (!widget) { + return; + } + fillDate(); + fillTime(); + }, + + setValue = function (targetMoment) { + var oldDate = unset ? null : date; + + // case of calling setValue(null or false) + if (!targetMoment) { + unset = true; + input.val(''); + element.data('date', ''); + notifyEvent({ + type: 'dp.change', + date: null, + oldDate: oldDate + }); + update(); + return; + } + + targetMoment = targetMoment.clone().locale(options.locale); + + if (options.stepping !== 1) { + targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0); + } + + if (isValid(targetMoment)) { + date = targetMoment; + viewDate = date.clone(); + input.val(date.format(actualFormat)); + element.data('date', date.format(actualFormat)); + update(); + unset = false; + notifyEvent({ + type: 'dp.change', + date: date.clone(), + oldDate: oldDate + }); + } else { + if (!options.keepInvalid) { + input.val(unset ? '' : date.format(actualFormat)); + } + notifyEvent({ + type: 'dp.error', + date: targetMoment + }); + } + }, + + hide = function () { + var transitioning = false; + if (!widget) { + return picker; + } + // Ignore event if in the middle of a picker transition + widget.find('.collapse').each(function () { + var collapseData = $(this).data('collapse'); + if (collapseData && collapseData.transitioning) { + transitioning = true; + return false; + } + return true; + }); + if (transitioning) { + return picker; + } + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + widget.hide(); + + $(window).off('resize', place); + widget.off('click', '[data-action]'); + widget.off('mousedown', false); + + widget.remove(); + widget = false; + + notifyEvent({ + type: 'dp.hide', + date: date.clone() + }); + return picker; + }, + + clear = function () { + setValue(null); + }, + + /******************************************************************************** + * + * Widget UI interaction functions + * + ********************************************************************************/ + actions = { + next: function () { + viewDate.add(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc); + fillDate(); + }, + + previous: function () { + viewDate.subtract(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc); + fillDate(); + }, + + pickerSwitch: function () { + showMode(1); + }, + + selectMonth: function (e) { + var month = $(e.target).closest('tbody').find('span').index($(e.target)); + viewDate.month(month); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year()).month(viewDate.month())); + if (!options.inline) { + hide(); + } + } else { + showMode(-1); + fillDate(); + } + }, + + selectYear: function (e) { + var year = parseInt($(e.target).text(), 10) || 0; + viewDate.year(year); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year())); + if (!options.inline) { + hide(); + } + } else { + showMode(-1); + fillDate(); + } + }, + + selectDay: function (e) { + var day = viewDate.clone(); + if ($(e.target).is('.old')) { + day.subtract(1, 'M'); + } + if ($(e.target).is('.new')) { + day.add(1, 'M'); + } + setValue(day.date(parseInt($(e.target).text(), 10))); + if (!hasTime() && !options.keepOpen && !options.inline) { + hide(); + } + }, + + incrementHours: function () { + setValue(date.clone().add(1, 'h')); + }, + + incrementMinutes: function () { + setValue(date.clone().add(options.stepping, 'm')); + }, + + incrementSeconds: function () { + setValue(date.clone().add(1, 's')); + }, + + decrementHours: function () { + setValue(date.clone().subtract(1, 'h')); + }, + + decrementMinutes: function () { + setValue(date.clone().subtract(options.stepping, 'm')); + }, + + decrementSeconds: function () { + setValue(date.clone().subtract(1, 's')); + }, + + togglePeriod: function () { + setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h')); + }, + + togglePicker: function (e) { + var $this = $(e.target), + $parent = $this.closest('ul'), + expanded = $parent.find('.in'), + closed = $parent.find('.collapse:not(.in)'), + collapseData; + + if (expanded && expanded.length) { + collapseData = expanded.data('collapse'); + if (collapseData && collapseData.transitioning) { + return; + } + if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it + expanded.collapse('hide'); + closed.collapse('show'); + } else { // otherwise just toggle in class on the two views + expanded.removeClass('in'); + closed.addClass('in'); + } + if ($this.is('span')) { + $this.toggleClass(options.icons.time + ' ' + options.icons.date); + } else { + $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); + } + + // NOTE: uncomment if toggled state will be restored in show() + //if (component) { + // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); + //} + } + }, + + showPicker: function () { + widget.find('.timepicker > div:not(.timepicker-picker)').hide(); + widget.find('.timepicker .timepicker-picker').show(); + }, + + showHours: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-hours').show(); + }, + + showMinutes: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-minutes').show(); + }, + + showSeconds: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-seconds').show(); + }, + + selectHour: function (e) { + var hour = parseInt($(e.target).text(), 10); + + if (!use24Hours) { + if (date.hours() >= 12) { + if (hour !== 12) { + hour += 12; + } + } else { + if (hour === 12) { + hour = 0; + } + } + } + setValue(date.clone().hours(hour)); + actions.showPicker.call(picker); + }, + + selectMinute: function (e) { + setValue(date.clone().minutes(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + + selectSecond: function (e) { + setValue(date.clone().seconds(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + + clear: clear, + + today: function () { + setValue(moment()); + }, + + close: hide + }, + + doAction = function (e) { + if ($(e.currentTarget).is('.disabled')) { + return false; + } + actions[$(e.currentTarget).data('action')].apply(picker, arguments); + return false; + }, + + show = function () { + var currentMoment, + useCurrentGranularity = { + 'year': function (m) { + return m.month(0).date(1).hours(0).seconds(0).minutes(0); + }, + 'month': function (m) { + return m.date(1).hours(0).seconds(0).minutes(0); + }, + 'day': function (m) { + return m.hours(0).seconds(0).minutes(0); + }, + 'hour': function (m) { + return m.seconds(0).minutes(0); + }, + 'minute': function (m) { + return m.seconds(0); + } + }; + + if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) { + return picker; + } + if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) { + currentMoment = moment(); + if (typeof options.useCurrent === 'string') { + currentMoment = useCurrentGranularity[options.useCurrent](currentMoment); + } + setValue(currentMoment); + } + + widget = getTemplate(); + + fillDow(); + fillMonths(); + + widget.find('.timepicker-hours').hide(); + widget.find('.timepicker-minutes').hide(); + widget.find('.timepicker-seconds').hide(); + + update(); + showMode(); + + $(window).on('resize', place); + widget.on('click', '[data-action]', doAction); // this handles clicks on the widget + widget.on('mousedown', false); + + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + widget.show(); + place(); + + if (!input.is(':focus')) { + input.focus(); + } + + notifyEvent({ + type: 'dp.show' + }); + return picker; + }, + + toggle = function () { + return (widget ? hide() : show()); + }, + + parseInputDate = function (inputDate) { + if (moment.isMoment(inputDate) || inputDate instanceof Date) { + inputDate = moment(inputDate); + } else { + inputDate = moment(inputDate, parseFormats, options.useStrict); + } + inputDate.locale(options.locale); + return inputDate; + }, + + keydown = function (e) { + //if (e.keyCode === 27 && widget) { // allow escape to hide picker + // hide(); + // return false; + //} + //if (e.keyCode === 40 && !widget) { // allow down to show picker + // show(); + // e.preventDefault(); + //} + //return true; + + var handler = null, + index, + index2, + pressedKeys = [], + pressedModifiers = {}, + currentKey = e.which, + keyBindKeys, + allModifiersPressed, + pressed = 'p'; + + keyState[currentKey] = pressed; + + for (index in keyState) { + if (keyState.hasOwnProperty(index) && keyState[index] === pressed) { + pressedKeys.push(index); + if (parseInt(index, 10) !== currentKey) { + pressedModifiers[index] = true; + } + } + } + + for (index in options.keyBinds) { + if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') { + keyBindKeys = index.split(' '); + if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) { + allModifiersPressed = true; + for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) { + if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) { + allModifiersPressed = false; + break; + } + } + if (allModifiersPressed) { + handler = options.keyBinds[index]; + break; + } + } + } + } + + if (handler) { + handler.call(picker, widget); + e.stopPropagation(); + e.preventDefault(); + } + }, + + keyup = function (e) { + keyState[e.which] = 'r'; + e.stopPropagation(); + e.preventDefault(); + }, + + change = function (e) { + var val = $(e.target).val().trim(), + parsedDate = val ? parseInputDate(val) : null; + setValue(parsedDate); + e.stopImmediatePropagation(); + return false; + }, + + attachDatePickerElementEvents = function () { + input.on({ + 'change': change, + 'blur': options.debug ? '' : hide, + 'keydown': keydown, + 'keyup': keyup + }); + + if (element.is('input')) { + input.on({ + 'focus': show + }); + } else if (component) { + component.on('click', toggle); + component.on('mousedown', false); + } + }, + + detachDatePickerElementEvents = function () { + input.off({ + 'change': change, + 'blur': hide, + 'keydown': keydown, + 'keyup': keyup + }); + + if (element.is('input')) { + input.off({ + 'focus': show + }); + } else if (component) { + component.off('click', toggle); + component.off('mousedown', false); + } + }, + + indexGivenDates = function (givenDatesArray) { + // Store given enabledDates and disabledDates as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledDates['2014-02-27'] === true) + var givenDatesIndexed = {}; + $.each(givenDatesArray, function () { + var dDate = parseInputDate(this); + if (dDate.isValid()) { + givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; + } + }); + return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false; + }, + + initFormatting = function () { + var format = options.format || 'L LT'; + + actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { + var newinput = date.localeData().longDateFormat(formatInput) || formatInput; + return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740 + return date.localeData().longDateFormat(formatInput2) || formatInput2; + }); + }); + + + parseFormats = options.extraFormats ? options.extraFormats.slice() : []; + if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) { + parseFormats.push(actualFormat); + } + + use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.indexOf('h') < 1); + + if (isEnabled('y')) { + minViewModeNumber = 2; + } + if (isEnabled('M')) { + minViewModeNumber = 1; + } + if (isEnabled('d')) { + minViewModeNumber = 0; + } + + currentViewMode = Math.max(minViewModeNumber, currentViewMode); + + if (!unset) { + setValue(date); + } + }; + + /******************************************************************************** + * + * Public API functions + * ===================== + * + * Important: Do not expose direct references to private objects or the options + * object to the outer world. Always return a clone when returning values or make + * a clone when setting a private variable. + * + ********************************************************************************/ + picker.destroy = function () { + hide(); + detachDatePickerElementEvents(); + element.removeData('DateTimePicker'); + element.removeData('date'); + }; + + picker.toggle = toggle; + + picker.show = show; + + picker.hide = hide; + + picker.disable = function () { + hide(); + if (component && component.hasClass('btn')) { + component.addClass('disabled'); + } + input.prop('disabled', true); + return picker; + }; + + picker.enable = function () { + if (component && component.hasClass('btn')) { + component.removeClass('disabled'); + } + input.prop('disabled', false); + return picker; + }; + + picker.ignoreReadonly = function (ignoreReadonly) { + if (arguments.length === 0) { + return options.ignoreReadonly; + } + if (typeof ignoreReadonly !== 'boolean') { + throw new TypeError('ignoreReadonly () expects a boolean parameter'); + } + options.ignoreReadonly = ignoreReadonly; + return picker; + }; + + picker.options = function (newOptions) { + if (arguments.length === 0) { + return $.extend(true, {}, options); + } + + if (!(newOptions instanceof Object)) { + throw new TypeError('options() options parameter should be an object'); + } + $.extend(true, options, newOptions); + $.each(options, function (key, value) { + if (picker[key] !== undefined) { + picker[key](value); + } else { + throw new TypeError('option ' + key + ' is not recognized!'); + } + }); + return picker; + }; + + picker.date = function (newDate) { + if (arguments.length === 0) { + if (unset) { + return null; + } + return date.clone(); + } + + if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('date() parameter must be one of [null, string, moment or Date]'); + } + + setValue(newDate === null ? null : parseInputDate(newDate)); + return picker; + }; + + picker.format = function (newFormat) { + if (arguments.length === 0) { + return options.format; + } + + if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) { + throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat); + } + + options.format = newFormat; + if (actualFormat) { + initFormatting(); // reinit formatting + } + return picker; + }; + + picker.dayViewHeaderFormat = function (newFormat) { + if (arguments.length === 0) { + return options.dayViewHeaderFormat; + } + + if (typeof newFormat !== 'string') { + throw new TypeError('dayViewHeaderFormat() expects a string parameter'); + } + + options.dayViewHeaderFormat = newFormat; + return picker; + }; + + picker.extraFormats = function (formats) { + if (arguments.length === 0) { + return options.extraFormats; + } + + if (formats !== false && !(formats instanceof Array)) { + throw new TypeError('extraFormats() expects an array or false parameter'); + } + + options.extraFormats = formats; + if (parseFormats) { + initFormatting(); // reinit formatting + } + return picker; + }; + + picker.disabledDates = function (dates) { + if (arguments.length === 0) { + return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates); + } + + if (!dates) { + options.disabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('disabledDates() expects an array parameter'); + } + options.disabledDates = indexGivenDates(dates); + options.enabledDates = false; + update(); + return picker; + }; + + picker.enabledDates = function (dates) { + if (arguments.length === 0) { + return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates); + } + + if (!dates) { + options.enabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('enabledDates() expects an array parameter'); + } + options.enabledDates = indexGivenDates(dates); + options.disabledDates = false; + update(); + return picker; + }; + + picker.daysOfWeekDisabled = function (daysOfWeekDisabled) { + if (arguments.length === 0) { + return options.daysOfWeekDisabled.splice(0); + } + + if (!(daysOfWeekDisabled instanceof Array)) { + throw new TypeError('daysOfWeekDisabled() expects an array parameter'); + } + options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) { + currentValue = parseInt(currentValue, 10); + if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) { + return previousValue; + } + if (previousValue.indexOf(currentValue) === -1) { + previousValue.push(currentValue); + } + return previousValue; + }, []).sort(); + update(); + return picker; + }; + + picker.maxDate = function (maxDate) { + if (arguments.length === 0) { + return options.maxDate ? options.maxDate.clone() : options.maxDate; + } + + if ((typeof maxDate === 'boolean') && maxDate === false) { + options.maxDate = false; + update(); + return picker; + } + + if (typeof maxDate === 'string') { + if (maxDate === 'now' || maxDate === 'moment') { + maxDate = moment(); + } + } + + var parsedDate = parseInputDate(maxDate); + + if (!parsedDate.isValid()) { + throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate); + } + if (options.minDate && parsedDate.isBefore(options.minDate)) { + throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat)); + } + options.maxDate = parsedDate; + if (options.maxDate.isBefore(maxDate)) { + setValue(options.maxDate); + } + if (viewDate.isAfter(parsedDate)) { + viewDate = parsedDate.clone(); + } + update(); + return picker; + }; + + picker.minDate = function (minDate) { + if (arguments.length === 0) { + return options.minDate ? options.minDate.clone() : options.minDate; + } + + if ((typeof minDate === 'boolean') && minDate === false) { + options.minDate = false; + update(); + return picker; + } + + if (typeof minDate === 'string') { + if (minDate === 'now' || minDate === 'moment') { + minDate = moment(); + } + } + + var parsedDate = parseInputDate(minDate); + + if (!parsedDate.isValid()) { + throw new TypeError('minDate() Could not parse date parameter: ' + minDate); + } + if (options.maxDate && parsedDate.isAfter(options.maxDate)) { + throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat)); + } + options.minDate = parsedDate; + if (options.minDate.isAfter(minDate)) { + setValue(options.minDate); + } + if (viewDate.isBefore(parsedDate)) { + viewDate = parsedDate.clone(); + } + update(); + return picker; + }; + + picker.defaultDate = function (defaultDate) { + if (arguments.length === 0) { + return options.defaultDate ? options.defaultDate.clone() : options.defaultDate; + } + if (!defaultDate) { + options.defaultDate = false; + return picker; + } + + if (typeof defaultDate === 'string') { + if (defaultDate === 'now' || defaultDate === 'moment') { + defaultDate = moment(); + } + } + + var parsedDate = parseInputDate(defaultDate); + if (!parsedDate.isValid()) { + throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate); + } + if (!isValid(parsedDate)) { + throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); + } + + options.defaultDate = parsedDate; + + if (options.defaultDate && input.val().trim() === '' && input.attr('placeholder') === undefined) { + setValue(options.defaultDate); + } + return picker; + }; + + picker.locale = function (locale) { + if (arguments.length === 0) { + return options.locale; + } + + if (!moment.localeData(locale)) { + throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!'); + } + + options.locale = locale; + date.locale(options.locale); + viewDate.locale(options.locale); + + if (actualFormat) { + initFormatting(); // reinit formatting + } + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.stepping = function (stepping) { + if (arguments.length === 0) { + return options.stepping; + } + + stepping = parseInt(stepping, 10); + if (isNaN(stepping) || stepping < 1) { + stepping = 1; + } + options.stepping = stepping; + return picker; + }; + + picker.useCurrent = function (useCurrent) { + var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute']; + if (arguments.length === 0) { + return options.useCurrent; + } + + if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) { + throw new TypeError('useCurrent() expects a boolean or string parameter'); + } + if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) { + throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', ')); + } + options.useCurrent = useCurrent; + return picker; + }; + + picker.collapse = function (collapse) { + if (arguments.length === 0) { + return options.collapse; + } + + if (typeof collapse !== 'boolean') { + throw new TypeError('collapse() expects a boolean parameter'); + } + if (options.collapse === collapse) { + return picker; + } + options.collapse = collapse; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.icons = function (icons) { + if (arguments.length === 0) { + return $.extend({}, options.icons); + } + + if (!(icons instanceof Object)) { + throw new TypeError('icons() expects parameter to be an Object'); + } + $.extend(options.icons, icons); + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.useStrict = function (useStrict) { + if (arguments.length === 0) { + return options.useStrict; + } + + if (typeof useStrict !== 'boolean') { + throw new TypeError('useStrict() expects a boolean parameter'); + } + options.useStrict = useStrict; + return picker; + }; + + picker.sideBySide = function (sideBySide) { + if (arguments.length === 0) { + return options.sideBySide; + } + + if (typeof sideBySide !== 'boolean') { + throw new TypeError('sideBySide() expects a boolean parameter'); + } + options.sideBySide = sideBySide; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.viewMode = function (viewMode) { + if (arguments.length === 0) { + return options.viewMode; + } + + if (typeof viewMode !== 'string') { + throw new TypeError('viewMode() expects a string parameter'); + } + + if (viewModes.indexOf(viewMode) === -1) { + throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value'); + } + + options.viewMode = viewMode; + currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber); + + showMode(); + return picker; + }; + + picker.toolbarPlacement = function (toolbarPlacement) { + if (arguments.length === 0) { + return options.toolbarPlacement; + } + + if (typeof toolbarPlacement !== 'string') { + throw new TypeError('toolbarPlacement() expects a string parameter'); + } + if (toolbarPlacements.indexOf(toolbarPlacement) === -1) { + throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value'); + } + options.toolbarPlacement = toolbarPlacement; + + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.widgetPositioning = function (widgetPositioning) { + if (arguments.length === 0) { + return $.extend({}, options.widgetPositioning); + } + + if (({}).toString.call(widgetPositioning) !== '[object Object]') { + throw new TypeError('widgetPositioning() expects an object variable'); + } + if (widgetPositioning.horizontal) { + if (typeof widgetPositioning.horizontal !== 'string') { + throw new TypeError('widgetPositioning() horizontal variable must be a string'); + } + widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase(); + if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) { + throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')'); + } + options.widgetPositioning.horizontal = widgetPositioning.horizontal; + } + if (widgetPositioning.vertical) { + if (typeof widgetPositioning.vertical !== 'string') { + throw new TypeError('widgetPositioning() vertical variable must be a string'); + } + widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase(); + if (verticalModes.indexOf(widgetPositioning.vertical) === -1) { + throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')'); + } + options.widgetPositioning.vertical = widgetPositioning.vertical; + } + update(); + return picker; + }; + + picker.calendarWeeks = function (calendarWeeks) { + if (arguments.length === 0) { + return options.calendarWeeks; + } + + if (typeof calendarWeeks !== 'boolean') { + throw new TypeError('calendarWeeks() expects parameter to be a boolean value'); + } + + options.calendarWeeks = calendarWeeks; + update(); + return picker; + }; + + picker.showTodayButton = function (showTodayButton) { + if (arguments.length === 0) { + return options.showTodayButton; + } + + if (typeof showTodayButton !== 'boolean') { + throw new TypeError('showTodayButton() expects a boolean parameter'); + } + + options.showTodayButton = showTodayButton; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.showClear = function (showClear) { + if (arguments.length === 0) { + return options.showClear; + } + + if (typeof showClear !== 'boolean') { + throw new TypeError('showClear() expects a boolean parameter'); + } + + options.showClear = showClear; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.widgetParent = function (widgetParent) { + if (arguments.length === 0) { + return options.widgetParent; + } + + if (typeof widgetParent === 'string') { + widgetParent = $(widgetParent); + } + + if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) { + throw new TypeError('widgetParent() expects a string or a jQuery object parameter'); + } + + options.widgetParent = widgetParent; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.keepOpen = function (keepOpen) { + if (arguments.length === 0) { + return options.keepOpen; + } + + if (typeof keepOpen !== 'boolean') { + throw new TypeError('keepOpen() expects a boolean parameter'); + } + + options.keepOpen = keepOpen; + return picker; + }; + + picker.inline = function (inline) { + if (arguments.length === 0) { + return options.inline; + } + + if (typeof inline !== 'boolean') { + throw new TypeError('inline() expects a boolean parameter'); + } + + options.inline = inline; + return picker; + }; + + picker.clear = function () { + clear(); + return picker; + }; + + picker.keyBinds = function (keyBinds) { + options.keyBinds = keyBinds; + return picker; + }; + + picker.debug = function (debug) { + if (typeof debug !== 'boolean') { + throw new TypeError('debug() expects a boolean parameter'); + } + + options.debug = debug; + return picker; + }; + + picker.showClose = function (showClose) { + if (arguments.length === 0) { + return options.showClose; + } + + if (typeof showClose !== 'boolean') { + throw new TypeError('showClose() expects a boolean parameter'); + } + + options.showClose = showClose; + return picker; + }; + + picker.keepInvalid = function (keepInvalid) { + if (arguments.length === 0) { + return options.keepInvalid; + } + + if (typeof keepInvalid !== 'boolean') { + throw new TypeError('keepInvalid() expects a boolean parameter'); + } + options.keepInvalid = keepInvalid; + return picker; + }; + + picker.datepickerInput = function (datepickerInput) { + if (arguments.length === 0) { + return options.datepickerInput; + } + + if (typeof datepickerInput !== 'string') { + throw new TypeError('datepickerInput() expects a string parameter'); + } + + options.datepickerInput = datepickerInput; + return picker; + }; + + // initializing element and component attributes + if (element.is('input')) { + input = element; + } else { + input = element.find(options.datepickerInput); + if (input.size() === 0) { + input = element.find('input'); + } else if (!input.is('input')) { + throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element'); + } + } + + if (element.hasClass('input-group')) { + // in case there is more then one 'input-group-addon' Issue #48 + if (element.find('.datepickerbutton').size() === 0) { + component = element.find('[class^="input-group-"]'); + } else { + component = element.find('.datepickerbutton'); + } + } + + if (!options.inline && !input.is('input')) { + throw new Error('Could not initialize DateTimePicker without an input element'); + } + + $.extend(true, options, dataToOptions()); + + picker.options(options); + + initFormatting(); + + attachDatePickerElementEvents(); + + if (input.prop('disabled')) { + picker.disable(); + } + if (input.is('input') && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } + else if (options.defaultDate && input.attr('placeholder') === undefined) { + setValue(options.defaultDate); + } + if (options.inline) { + show(); + } + return picker; + }; + + /******************************************************************************** + * + * jQuery plugin constructor and defaults object + * + ********************************************************************************/ + + $.fn.datetimepicker = function (options) { + return this.each(function () { + var $this = $(this); + if (!$this.data('DateTimePicker')) { + // create a private copy of the defaults object + options = $.extend(true, {}, $.fn.datetimepicker.defaults, options); + $this.data('DateTimePicker', dateTimePicker($this, options)); + } + }); + }; + + $.fn.datetimepicker.defaults = { + format: false, + dayViewHeaderFormat: 'MMMM YYYY', + extraFormats: false, + stepping: 1, + minDate: false, + maxDate: false, + useCurrent: true, + collapse: true, + locale: moment.locale(), + defaultDate: false, + disabledDates: false, + enabledDates: false, + icons: { + time: 'glyphicon glyphicon-time', + date: 'glyphicon glyphicon-calendar', + up: 'glyphicon glyphicon-chevron-up', + down: 'glyphicon glyphicon-chevron-down', + previous: 'glyphicon glyphicon-chevron-left', + next: 'glyphicon glyphicon-chevron-right', + today: 'glyphicon glyphicon-screenshot', + clear: 'glyphicon glyphicon-trash', + close: 'glyphicon glyphicon-remove' + }, + useStrict: false, + sideBySide: false, + daysOfWeekDisabled: [], + calendarWeeks: false, + viewMode: 'days', + toolbarPlacement: 'default', + showTodayButton: false, + showClear: false, + showClose: false, + widgetPositioning: { + horizontal: 'auto', + vertical: 'auto' + }, + widgetParent: null, + ignoreReadonly: false, + keepOpen: false, + inline: false, + keepInvalid: false, + datepickerInput: '.datepickerinput', + keyBinds: { + up: function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(7, 'd')); + } else { + this.date(d.clone().add(1, 'm')); + } + }, + down: function (widget) { + if (!widget) { + this.show(); + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(7, 'd')); + } else { + this.date(d.clone().subtract(1, 'm')); + } + }, + 'control up': function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'y')); + } else { + this.date(d.clone().add(1, 'h')); + } + }, + 'control down': function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'y')); + } else { + this.date(d.clone().subtract(1, 'h')); + } + }, + left: function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'd')); + } + }, + right: function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'd')); + } + }, + pageUp: function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'M')); + } + }, + pageDown: function (widget) { + if (!widget) { + return; + } + var d = this.date() || moment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'M')); + } + }, + enter: function () { + this.hide(); + }, + escape: function () { + this.hide(); + }, + //tab: function (widget) { //this break the flow of the form. disabling for now + // var toggle = widget.find('.picker-switch a[data-action="togglePicker"]'); + // if(toggle.length > 0) toggle.click(); + //}, + 'control space': function (widget) { + if (widget.find('.timepicker').is(':visible')) { + widget.find('.btn[data-action="togglePeriod"]').click(); + } + }, + t: function () { + this.date(moment()); + }, + 'delete': function () { + this.clear(); + } + }, + debug: false + }; +})); diff --git a/vendor/assets/javascripts/pickers.js b/vendor/assets/javascripts/pickers.js new file mode 100644 index 00000000..63c9a7ba --- /dev/null +++ b/vendor/assets/javascripts/pickers.js @@ -0,0 +1,42 @@ +$(document).on('ready page:change', function() { + $('.datetimepicker').datetimepicker({ + // put here your custom picker options, that should be applied for all pickers + icons: { + date: 'fa fa-calendar', + time: 'fa fa-clock-o', + up: 'fa fa-chevron-up', + down: 'fa fa-chevron-down', + previous: 'fa fa-chevron-left', + next: 'fa fa-chevron-right', + today: 'fa fa-crosshairs', + clear: 'fa fa-trash-o', + close: 'fa fa-times' + } + }); + + $('.datetimerange').each(function(){ + var $this = $(this) + var range1 = $($this.find('.input-group')[0]) + var range2 = $($this.find('.input-group')[1]) + + if(range1.data("DateTimePicker").date() != null) + range2.data("DateTimePicker").minDate(range1.data("DateTimePicker").date()); + + if(range2.data("DateTimePicker").date() != null) + range1.data("DateTimePicker").maxDate(range2.data("DateTimePicker").date()); + + range1.on("dp.change",function (e) { + if(e.date) + range2.data("DateTimePicker").minDate(e.date); + else + range2.data("DateTimePicker").minDate(false); + }); + + range2.on("dp.change",function (e) { + if(e.date) + range1.data("DateTimePicker").maxDate(e.date); + else + range1.data("DateTimePicker").maxDate(false); + }); + }) +}); diff --git a/vendor/assets/stylesheets/bootstrap-datetimepicker.css b/vendor/assets/stylesheets/bootstrap-datetimepicker.css new file mode 100644 index 00000000..c7021619 --- /dev/null +++ b/vendor/assets/stylesheets/bootstrap-datetimepicker.css @@ -0,0 +1,366 @@ +/*! + * Datetimepicker for Bootstrap 3 + * ! version : 4.7.14 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: 0 0 7px 7px solid transparent; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/vendor/assets/stylesheets/bootstrap-datetimepicker.min.css b/vendor/assets/stylesheets/bootstrap-datetimepicker.min.css new file mode 100644 index 00000000..c7021619 --- /dev/null +++ b/vendor/assets/stylesheets/bootstrap-datetimepicker.min.css @@ -0,0 +1,366 @@ +/*! + * Datetimepicker for Bootstrap 3 + * ! version : 4.7.14 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: 0 0 7px 7px solid transparent; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +}
    ').addClass('cw').text('#')); + } + + while (currentDate.isBefore(viewDate.clone().endOf('w'))) { + row.append($('').addClass('dow').text(currentDate.format('dd'))); + currentDate.add(1, 'd'); + } + widget.find('.datepicker-days thead').append(row); + }, + + isInDisabledDates = function (testDate) { + return options.disabledDates[testDate.format('YYYY-MM-DD')] === true; + }, + + isInEnabledDates = function (testDate) { + return options.enabledDates[testDate.format('YYYY-MM-DD')] === true; + }, + + isValid = function (targetMoment, granularity) { + if (!targetMoment.isValid()) { + return false; + } + if (options.disabledDates && isInDisabledDates(targetMoment) && granularity !== 'M') { + return false; + } + if (options.enabledDates && !isInEnabledDates(targetMoment) && granularity !== 'M') { + return false; + } + if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) { + return false; + } + if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) { + return false; + } + if (granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { //widget && widget.find('.datepicker-days').length > 0 + return false; + } + return true; + }, + + fillMonths = function () { + var spans = [], + monthsShort = viewDate.clone().startOf('y').hour(12); // hour is changed to avoid DST issues in some browsers + while (monthsShort.isSame(viewDate, 'y')) { + spans.push($('').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); + monthsShort.add(1, 'M'); + } + widget.find('.datepicker-months td').empty().append(spans); + }, + + updateMonths = function () { + var monthsView = widget.find('.datepicker-months'), + monthsViewHeader = monthsView.find('th'), + months = monthsView.find('tbody').find('span'); + + monthsView.find('.disabled').removeClass('disabled'); + + if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) { + monthsViewHeader.eq(0).addClass('disabled'); + } + + monthsViewHeader.eq(1).text(viewDate.year()); + + if (!isValid(viewDate.clone().add(1, 'y'), 'y')) { + monthsViewHeader.eq(2).addClass('disabled'); + } + + months.removeClass('active'); + if (date.isSame(viewDate, 'y')) { + months.eq(date.month()).addClass('active'); + } + + months.each(function (index) { + if (!isValid(viewDate.clone().month(index), 'M')) { + $(this).addClass('disabled'); + } + }); + }, + + updateYears = function () { + var yearsView = widget.find('.datepicker-years'), + yearsViewHeader = yearsView.find('th'), + startYear = viewDate.clone().subtract(5, 'y'), + endYear = viewDate.clone().add(6, 'y'), + html = ''; + + yearsView.find('.disabled').removeClass('disabled'); + + if (options.minDate && options.minDate.isAfter(startYear, 'y')) { + yearsViewHeader.eq(0).addClass('disabled'); + } + + yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year()); + + if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) { + yearsViewHeader.eq(2).addClass('disabled'); + } + + while (!startYear.isAfter(endYear, 'y')) { + html += '' + startYear.year() + ''; + startYear.add(1, 'y'); + } + + yearsView.find('td').html(html); + }, + + fillDate = function () { + var daysView = widget.find('.datepicker-days'), + daysViewHeader = daysView.find('th'), + currentDate, + html = [], + row, + clsName, + i; + + if (!hasDate()) { + return; + } + + daysView.find('.disabled').removeClass('disabled'); + daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat)); + + if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) { + daysViewHeader.eq(0).addClass('disabled'); + } + if (!isValid(viewDate.clone().add(1, 'M'), 'M')) { + daysViewHeader.eq(2).addClass('disabled'); + } + + currentDate = viewDate.clone().startOf('M').startOf('week'); + + // while (!viewDate.clone().endOf('M').endOf('w').isBefore(currentDate, 'd')) { + for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks) + if (currentDate.weekday() === 0) { + row = $('
    ' + currentDate.week() + '' + currentDate.date() + '
    ' + currentHour.format(use24Hours ? 'HH' : 'hh') + '
    ' + currentMinute.format('mm') + '
    ' + currentSecond.format('ss') + '