Browse Source

Initial commit.

Dalia Carlon 9 năm trước cách đây
commit
73d514c6e8
100 tập tin đã thay đổi với 6578 bổ sung0 xóa
  1. 35 0
      .gitignore
  2. 33 0
      .overcommit.yml
  3. 261 0
      .rubocop.yml
  4. 1 0
      .ruby-version
  5. 109 0
      Gemfile
  6. 334 0
      Gemfile.lock
  7. 28 0
      README.rdoc
  8. 6 0
      Rakefile
  9. BIN
      app/assets/fonts/font-awesome/fontawesome-webfont.eot
  10. 655 0
      app/assets/fonts/font-awesome/fontawesome-webfont.svg
  11. BIN
      app/assets/fonts/font-awesome/fontawesome-webfont.ttf
  12. BIN
      app/assets/fonts/font-awesome/fontawesome-webfont.woff
  13. BIN
      app/assets/fonts/font-awesome/fontawesome-webfont.woff2
  14. 0 0
      app/assets/images/.keep
  15. BIN
      app/assets/images/avatar.png
  16. BIN
      app/assets/images/logos/logo-default.png
  17. BIN
      app/assets/images/no-image.png
  18. BIN
      app/assets/images/profile.png
  19. BIN
      app/assets/images/sprite.png
  20. 4 0
      app/assets/javascripts/amcharts/ajax.js
  21. 70 0
      app/assets/javascripts/amcharts/ajax/request.js
  22. 26 0
      app/assets/javascripts/amcharts/ajax/response.js
  23. 11 0
      app/assets/javascripts/amcharts/all.js
  24. 381 0
      app/assets/javascripts/amcharts/amcharts.js
  25. 98 0
      app/assets/javascripts/amcharts/amstock.js
  26. 67 0
      app/assets/javascripts/amcharts/chart.js
  27. 24 0
      app/assets/javascripts/amcharts/ext/get_elements_by_class_name.js
  28. 41 0
      app/assets/javascripts/amcharts/ext/object_keys.js
  29. 17 0
      app/assets/javascripts/amcharts/funnel.js
  30. 20 0
      app/assets/javascripts/amcharts/gauge.js
  31. 131 0
      app/assets/javascripts/amcharts/helpers.js
  32. 10 0
      app/assets/javascripts/amcharts/pie.js
  33. 10 0
      app/assets/javascripts/amcharts/radar.js
  34. 59 0
      app/assets/javascripts/amcharts/remote_json_provider.js
  35. 57 0
      app/assets/javascripts/amcharts/serial.js
  36. 1 0
      app/assets/javascripts/amcharts/themes/light.js
  37. 71 0
      app/assets/javascripts/amcharts/util.js
  38. 17 0
      app/assets/javascripts/amcharts/xy.js
  39. 69 0
      app/assets/javascripts/application.js
  40. 3 0
      app/assets/javascripts/cash_outs.coffee
  41. 3 0
      app/assets/javascripts/cash_registers.coffee
  42. 3 0
      app/assets/javascripts/commissions.coffee
  43. 304 0
      app/assets/javascripts/datatable.js
  44. 3 0
      app/assets/javascripts/expensesconcepts.coffee
  45. 107 0
      app/assets/javascripts/jquery-barcodeListener.js
  46. 489 0
      app/assets/javascripts/json2.js
  47. 27 0
      app/assets/javascripts/modals.js.coffee
  48. 3 0
      app/assets/javascripts/pos_configs.coffee
  49. 3 0
      app/assets/javascripts/pre_purchases.coffee
  50. 3 0
      app/assets/javascripts/pre_transfers.coffee
  51. 3 0
      app/assets/javascripts/product_wastes.coffee
  52. 3 0
      app/assets/javascripts/products_returns.coffee
  53. 3 0
      app/assets/javascripts/sellers.coffee
  54. 3 0
      app/assets/javascripts/sellerscommissions.coffee
  55. 3 0
      app/assets/javascripts/special_prices.coffee
  56. 3 0
      app/assets/javascripts/storers.coffee
  57. 3 0
      app/assets/javascripts/transfers.coffee
  58. 3 0
      app/assets/javascripts/warehouses.coffee
  59. 36 0
      app/assets/stylesheets/application.css
  60. 3 0
      app/assets/stylesheets/cash_outs.scss
  61. 3 0
      app/assets/stylesheets/cash_registers.scss
  62. 3 0
      app/assets/stylesheets/commissions.scss
  63. 6 0
      app/assets/stylesheets/custom.css
  64. 3 0
      app/assets/stylesheets/expensesconcepts.scss
  65. 13 0
      app/assets/stylesheets/main.scss
  66. 3 0
      app/assets/stylesheets/pos_configs.scss
  67. 3 0
      app/assets/stylesheets/pre_purchases.scss
  68. 3 0
      app/assets/stylesheets/pre_transfers.scss
  69. 3 0
      app/assets/stylesheets/product_wastes.scss
  70. 3 0
      app/assets/stylesheets/products_returns.scss
  71. 4 0
      app/assets/stylesheets/purchases.css
  72. 4 0
      app/assets/stylesheets/purchases_data.css
  73. 60 0
      app/assets/stylesheets/scaffold.css
  74. 73 0
      app/assets/stylesheets/scaffolds.scss
  75. 3 0
      app/assets/stylesheets/sellers.scss
  76. 3 0
      app/assets/stylesheets/sellerscommissions.scss
  77. 3 0
      app/assets/stylesheets/special_prices.scss
  78. 3 0
      app/assets/stylesheets/storers.scss
  79. 3 0
      app/assets/stylesheets/transfers.scss
  80. 3 0
      app/assets/stylesheets/warehouses.scss
  81. 225 0
      app/controllers/application_controller.rb
  82. 197 0
      app/controllers/available_products_controller.rb
  83. 234 0
      app/controllers/cash_outs_controller.rb
  84. 132 0
      app/controllers/cash_registers_controller.rb
  85. 286 0
      app/controllers/cash_registers_moves_controller.rb
  86. 122 0
      app/controllers/categories_controller.rb
  87. 140 0
      app/controllers/commissions_controller.rb
  88. 0 0
      app/controllers/concerns/.keep
  89. 140 0
      app/controllers/customers_controller.rb
  90. 67 0
      app/controllers/dashboard_controller.rb
  91. 160 0
      app/controllers/expenses_controller.rb
  92. 119 0
      app/controllers/expensesconcepts_controller.rb
  93. 76 0
      app/controllers/open_cash_registers_controller.rb
  94. 252 0
      app/controllers/pointsales_controller.rb
  95. 55 0
      app/controllers/pos_configs_controller.rb
  96. 78 0
      app/controllers/pre_purchases_controller.rb
  97. 189 0
      app/controllers/pre_sales_controller.rb
  98. 136 0
      app/controllers/pre_transfers_controller.rb
  99. 111 0
      app/controllers/product_wastes_controller.rb
  100. 0 0
      app/controllers/products_controller.rb

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+# Ignore bundler config.
+/.bundle
+
+# Ignore all logfiles and unnecessary tempfiles.
+/log/*
+log/*
+!/log/.keep
+/tmp
+.DS_Store
+.powrc
+.powenv
+.env
+/vendor/bundle/
+/test/*
+/backup/*
+*.numbers
+*.xlsx
+/planeacion/*
+
+# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
+config/initializers/secret_token.rb
+config/initializers/byebug.rb
+config/secrets.yml
+config/database.yml
+
+/bin/blockly_data/node_modules/
+/bin/blockly_data/repos/
+/coverage
+public/system
+amada
+POS_variantes.bmpr
+POS_variantes.pdf
+/POS.oplx/
+/public.sql
+log

+ 33 - 0
.overcommit.yml

@@ -0,0 +1,33 @@
+# Use this file to configure the Overcommit hooks you wish to use. This will
+# extend the default configuration defined in:
+# https://github.com/brigade/overcommit/blob/master/config/default.yml
+#
+# At the topmost level of this YAML file is a key representing type of hook
+# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
+# customize each hook, such as whether to only run it on certain files (via
+# `include`), whether to only display output if it fails (via `quiet`), etc.
+#
+# For a complete list of hooks, see:
+# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook
+#
+# For a complete list of options that you can use to customize hooks, see:
+# https://github.com/brigade/overcommit#configuration
+#
+# Uncomment the following lines to make the configuration take effect.
+
+# PreCommit:
+#   RuboCop:
+#     enabled: true
+#     on_warn: fail # Treat all warnings as failures
+#
+#  TrailingWhitespace:
+#    enabled: true
+#    exclude:
+#      - '**/db/structure.sql' # Ignore trailing whitespace in generated files
+#
+#PostCheckout:
+#  ALL: # Special hook name that customizes all hooks of this type
+#    quiet: true # Change all post-checkout hooks to only display output on failure
+#
+#  IndexTags:
+#    enabled: true # Generate a tags file with `ctags` each time HEAD changes

+ 261 - 0
.rubocop.yml

@@ -0,0 +1,261 @@
+AllCops:
+  TargetRubyVersion: 2.3
+  Exclude:
+    - "vendor/**/*"
+    - "db/**/*"
+    - "spec/vcr_cassettes/**/*"
+  UseCache: false
+Style/ClassAndModuleChildren:
+  Description: 'Checks style of children classes and modules.'
+  Enabled: false
+Style/ClassVars:
+  Description: 'Avoid the use of class variables.'
+  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'
+  Enabled: false
+Style/CollectionMethods:
+  Description: Preferred collection methods.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size
+  Enabled: true
+  PreferredMethods:
+    collect: map
+    collect!: map!
+    find: detect
+    find_all: select
+    reduce: inject
+Style/DotPosition:
+  Description: Checks the position of the dot in multi-line method calls.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains
+  Enabled: true
+  EnforcedStyle: trailing
+  SupportedStyles:
+  - leading
+  - trailing
+Style/FileName:
+  Description: Use snake_case for source file names.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files
+  Enabled: false
+  Exclude: []
+Style/GuardClause:
+  Description: Check for conditionals that can be replaced with guard clauses
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
+  Enabled: false
+  MinBodyLength: 1
+Style/IfUnlessModifier:
+  Description: Favor modifier if/unless usage when you have a single-line body.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier
+  Enabled: false
+  MaxLineLength: 80
+Style/OptionHash:
+  Description: Don't use option hashes when you can use keyword arguments.
+  Enabled: false
+Style/PercentLiteralDelimiters:
+  Description: Use `%`-literal delimiters consistently
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces
+  Enabled: false
+  PreferredDelimiters:
+    "%": "()"
+    "%i": "()"
+    "%q": "()"
+    "%Q": "()"
+    "%r": "{}"
+    "%s": "()"
+    "%w": "()"
+    "%W": "()"
+    "%x": "()"
+Style/PredicateName:
+  Description: Check the names of predicate methods.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark
+  Enabled: true
+  NamePrefix:
+  - is_
+  - has_
+  - have_
+  NamePrefixBlacklist:
+  - is_
+  Exclude:
+  - spec/**/*
+Style/RaiseArgs:
+  Description: Checks the arguments passed to raise/fail.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages
+  Enabled: false
+  EnforcedStyle: exploded
+  SupportedStyles:
+  - compact
+  - exploded
+Style/SignalException:
+  Description: Checks for proper usage of fail and raise.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method
+  Enabled: false
+  EnforcedStyle: semantic
+  SupportedStyles:
+  - only_raise
+  - only_fail
+  - semantic
+Style/SingleLineBlockParams:
+  Description: Enforces the names of some block params.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks
+  Enabled: false
+  Methods:
+  - reduce:
+    - a
+    - e
+  - inject:
+    - a
+    - e
+Style/SingleLineMethods:
+  Description: Avoid single-line methods.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods
+  Enabled: false
+  AllowIfMethodIsEmpty: true
+Style/StringLiterals:
+  Description: Checks if uses of quotes match the configured preference.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals
+  Enabled: false
+  EnforcedStyle: single_quotes
+  SupportedStyles:
+  - single_quotes
+  - double_quotes
+Style/StringLiteralsInInterpolation:
+  Description: Checks if uses of quotes inside expressions in interpolated strings
+    match the configured preference.
+  Enabled: false
+  EnforcedStyle: single_quotes
+  SupportedStyles:
+  - single_quotes
+  - double_quotes
+Style/TrailingCommaInArguments:
+  Description: 'Checks for trailing comma in argument lists.'
+  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
+  Enabled: false
+  EnforcedStyleForMultiline: no_comma
+  SupportedStyles:
+  - comma
+  - consistent_comma
+  - no_comma
+Style/TrailingCommaInLiteral:
+  Description: 'Checks for trailing comma in array and hash literals.'
+  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
+  Enabled: false
+  EnforcedStyleForMultiline: no_comma
+  SupportedStyles:
+  - comma
+  - consistent_comma
+  - no_comma
+Metrics/LineLength:
+    Max: 100
+    Enabled: false
+Metrics/AbcSize:
+  Description: A calculated magnitude based on number of assignments, branches, and
+    conditions.
+  Enabled: false
+  Max: 15
+Metrics/ClassLength:
+  Description: Avoid classes longer than 100 lines of code.
+  Enabled: false
+  CountComments: false
+  Max: 100
+Metrics/ModuleLength:
+  CountComments: false
+  Max: 100
+  Description: Avoid modules longer than 100 lines of code.
+  Enabled: false
+Metrics/CyclomaticComplexity:
+  Description: A complexity metric that is strongly correlated to the number of test
+    cases needed to validate a method.
+  Enabled: false
+  Max: 6
+Metrics/MethodLength:
+  Description: Avoid methods longer than 10 lines of code.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods
+  Enabled: false
+  CountComments: false
+  Max: 10
+Metrics/ParameterLists:
+  Description: Avoid parameter lists longer than three or four parameters.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params
+  Enabled: false
+  Max: 5
+  CountKeywordArgs: true
+Metrics/PerceivedComplexity:
+  Description: A complexity metric geared towards measuring complexity for a human
+    reader.
+  Enabled: false
+  Max: 7
+Lint/AssignmentInCondition:
+  Description: Don't use assignment in conditions.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition
+  Enabled: false
+  AllowSafeAssignment: true
+Style/InlineComment:
+  Description: Avoid inline comments.
+  Enabled: false
+Style/AccessorMethodName:
+  Description: Check the naming of accessor methods for get_/set_.
+  Enabled: false
+Style/Alias:
+  Description: Use alias_method instead of alias.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method
+  Enabled: false
+Style/Documentation:
+  Description: Document classes and non-namespace modules.
+  Enabled: false
+Style/DoubleNegation:
+  Description: Checks for uses of double negation (!!).
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
+  Enabled: false
+Style/EachWithObject:
+  Description: Prefer `each_with_object` over `inject` or `reduce`.
+  Enabled: false
+Style/EmptyLiteral:
+  Description: Prefer literals to Array.new/Hash.new/String.new.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash
+  Enabled: false
+Style/NumericLiterals:
+  Enabled: false
+Style/ModuleFunction:
+  Description: Checks for usage of `extend self` in modules.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function
+  Enabled: false
+Style/OneLineConditional:
+  Description: Favor the ternary operator(?:) over if/then/else/end constructs.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator
+  Enabled: false
+Style/PerlBackrefs:
+  Description: Avoid Perl-style regex back references.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers
+  Enabled: false
+Style/Send:
+  Description: Prefer `Object#__send__` or `Object#public_send` to `send`, as `send`
+    may overlap with existing methods.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#prefer-public-send
+  Enabled: false
+Style/SpecialGlobalVars:
+  Description: Avoid Perl-style global variables.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms
+  Enabled: false
+Style/VariableInterpolation:
+  Description: Don't interpolate global, instance and class variables directly in
+    strings.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate
+  Enabled: false
+Style/WhenThen:
+  Description: Use when x then ... for one-line cases.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases
+  Enabled: false
+Lint/EachWithObjectArgument:
+  Description: Check for immutable argument given to each_with_object.
+  Enabled: true
+Lint/HandleExceptions:
+  Description: Don't suppress exception.
+  StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
+  Enabled: false
+Lint/LiteralInCondition:
+  Description: Checks of literals used in conditions.
+  Enabled: false
+Lint/LiteralInInterpolation:
+  Description: Checks for literals used in interpolation.
+  Enabled: false
+Lint/UselessAssignment:
+  Description: 'Checks for useless assignment to a local variable.'
+  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+  Enabled: false

+ 1 - 0
.ruby-version

@@ -0,0 +1 @@
+2.2.3

+ 109 - 0
Gemfile

@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+source 'https://rubygems.org'
+
+
+# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
+gem 'rails', '4.2.4'
+# Use PostgreSQL as the database for Active Record
+gem 'pg'
+# Devise users auth
+gem 'devise'
+gem 'devise_security_extension'
+# Navegation for build the menu
+gem 'simple_navigation_renderers'
+gem 'breadcrumbs_on_rails'
+# Abilities for User types /roles
+gem 'cancan'
+# Activity user logs / auditabled
+gem 'audited-activerecord', '~> 4.0'
+
+
+# Use SCSS for stylesheets
+gem 'sass-rails', '~> 5.0'
+# Use Uglifier as compressor for JavaScript assets
+gem 'uglifier', '>= 1.3.0'
+# Use CoffeeScript for .coffee assets and views
+gem 'coffee-rails', '~> 4.1.0'
+# See https://github.com/rails/execjs#readme for more supported runtimes
+# gem 'therubyracer', platforms: :ruby
+
+## Gemas POS
+gem 'paperclip'
+
+## responders para modal
+gem 'responders'
+
+# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
+gem 'jbuilder', '~> 2.0'
+# bundle exec rake doc:rails generates the API under doc/api.
+gem 'sdoc', '~> 0.4.0', group: :doc
+
+# Gema para modificar la forma de mostrar el error resaltado en los input de la forma
+gem 'nokogiri'
+
+### javascripts y css
+# Use jquery as the JavaScript library
+gem 'sprockets-rails', :require => 'sprockets/railtie'
+gem 'jquery-rails'
+gem 'jquery-ui-rails'
+gem 'bootstrap-sass'
+gem 'bootstrap-select-rails'
+gem "bootstrap-switch-rails"
+gem 'momentjs-rails', '>= 2.9.0'
+gem 'bootstrap3-datetimepicker-rails', '~> 4.17.37'
+gem 'bootbox-rails', '~>0.4'
+gem "select2-rails"
+gem 'bootstrap-multiselect-rails'
+gem 'twitter-typeahead-rails'
+gem 'simple-line-icons-rails'
+gem 'jquery-datatables-rails', github: 'rweng/jquery-datatables-rails' # correr rails generate jquery:datatables:install bootstrap3 , rails generate jquery:datatables:install responsive
+gem 'jquery-inputmask-rails', '~> 2.5', '>= 2.5.5'
+gem 'jquery-migrate-rails', '~> 1.2', '>= 1.2.1'
+gem 'jquery-slimscroll-rails', '~> 1.0', '>= 1.0.9'
+gem 'toastr_rails'
+# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
+gem 'turbolinks'
+# Gema para usar autocompletar en inputs
+gem 'rails-jquery-autocomplete'
+gem 'font-awesome-sass', '~> 4.5.0'
+gem 'wicked_pdf'
+gem 'wkhtmltopdf-binary'
+gem 'twitter_bootstrap_wizard_rails'
+gem 'bootstrap-tagsinput-rails'
+gem 'acts-as-taggable-on', '~> 3.4' # then run rake acts_as_taggable_on_engine:install:migrations
+gem 'remotipart', '~> 1.2'
+gem 'will_paginate'
+gem "font-awesome-rails"
+gem 'carrierwave', '>= 1.0.0.rc', '< 2.0'
+gem 'mini_magick'
+# gem 'ajax-datatables-rails', git: 'git://github.com/antillas21/ajax-datatables-rails.git', branch: 'master'
+# gem 'amcharts.rb'
+# Use ActiveModel has_secure_password
+# gem 'bcrypt', '~> 3.1.7'
+
+# Use Unicorn as the app server
+# gem 'unicorn'
+
+# Use Capistrano for deployment
+# gem 'capistrano-rails', group: :development
+
+group :development, :test do
+  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
+  gem 'byebug'
+  # Para generar esquems de base de datos
+  # install: sudo apt-get install graphviz -y
+  # Osx: brew install graphviz
+  gem 'rails-erd'
+  gem 'overcommit'
+  gem 'rubocop', '~> 0.46.0', require: false
+end
+
+group :development do
+  # Access an IRB console on exception pages or by using <%= console %> in views
+  gem 'web-console', '~> 2.0'
+
+  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
+  gem 'spring'
+  gem 'pry-rails'
+end
+

+ 334 - 0
Gemfile.lock

@@ -0,0 +1,334 @@
+GIT
+  remote: git://github.com/rweng/jquery-datatables-rails.git
+  revision: 9eee0a1975b5a22e50bdc73fcca7db7f8a12c143
+  specs:
+    jquery-datatables-rails (3.3.0)
+      actionpack (>= 3.1)
+      jquery-rails
+      railties (>= 3.1)
+      sass-rails
+
+GEM
+  remote: https://rubygems.org/
+  specs:
+    actionmailer (4.2.4)
+      actionpack (= 4.2.4)
+      actionview (= 4.2.4)
+      activejob (= 4.2.4)
+      mail (~> 2.5, >= 2.5.4)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+    actionpack (4.2.4)
+      actionview (= 4.2.4)
+      activesupport (= 4.2.4)
+      rack (~> 1.6)
+      rack-test (~> 0.6.2)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    actionview (4.2.4)
+      activesupport (= 4.2.4)
+      builder (~> 3.1)
+      erubis (~> 2.7.0)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    activejob (4.2.4)
+      activesupport (= 4.2.4)
+      globalid (>= 0.3.0)
+    activemodel (4.2.4)
+      activesupport (= 4.2.4)
+      builder (~> 3.1)
+    activerecord (4.2.4)
+      activemodel (= 4.2.4)
+      activesupport (= 4.2.4)
+      arel (~> 6.0)
+    activesupport (4.2.4)
+      i18n (~> 0.7)
+      json (~> 1.7, >= 1.7.7)
+      minitest (~> 5.1)
+      thread_safe (~> 0.3, >= 0.3.4)
+      tzinfo (~> 1.1)
+    acts-as-taggable-on (3.5.0)
+      activerecord (>= 3.2, < 5)
+    arel (6.0.3)
+    ast (2.3.0)
+    audited (4.2.0)
+      rails-observers (~> 0.1.2)
+    audited-activerecord (4.2.0)
+      activerecord (~> 4.0)
+      audited (= 4.2.0)
+    autoprefixer-rails (6.3.2)
+      execjs
+      json
+    bcrypt (3.1.10)
+    binding_of_caller (0.7.2)
+      debug_inspector (>= 0.0.1)
+    bootbox-rails (0.5.0)
+    bootstrap-multiselect-rails (0.9.9)
+      rails (>= 4.0.0)
+    bootstrap-sass (3.3.6)
+      autoprefixer-rails (>= 5.2.1)
+      sass (>= 3.3.4)
+    bootstrap-select-rails (1.6.3)
+    bootstrap-switch-rails (3.3.3)
+    bootstrap-tagsinput-rails (0.4.2.1)
+      railties (>= 3.1)
+    bootstrap3-datetimepicker-rails (4.17.37)
+      momentjs-rails (>= 2.8.1)
+    breadcrumbs_on_rails (2.3.1)
+    builder (3.2.2)
+    byebug (6.0.2)
+    cancan (1.6.10)
+    childprocess (0.5.9)
+      ffi (~> 1.0, >= 1.0.11)
+    carrierwave (1.0.0.rc)
+      activemodel (>= 4.0.0)
+      activesupport (>= 4.0.0)
+      mime-types (>= 1.16)
+    choice (0.2.0)
+    climate_control (0.0.3)
+      activesupport (>= 3.0)
+    cocaine (0.5.7)
+      climate_control (>= 0.0.3, < 1.0)
+    coderay (1.1.1)
+    coffee-rails (4.1.0)
+      coffee-script (>= 2.2.0)
+      railties (>= 4.0.0, < 5.0)
+    coffee-script (2.4.1)
+      coffee-script-source
+      execjs
+    coffee-script-source (1.9.1.1)
+    debug_inspector (0.0.2)
+    devise (3.5.3)
+      bcrypt (~> 3.0)
+      orm_adapter (~> 0.1)
+      railties (>= 3.2.6, < 5)
+      responders
+      thread_safe (~> 0.1)
+      warden (~> 1.2.3)
+    devise_security_extension (0.10.0)
+      devise (>= 3.0.0, < 4.0)
+      railties (>= 3.2.6, < 5.0)
+    erubis (2.7.0)
+    execjs (2.6.0)
+    ffi (1.9.14)
+    font-awesome-rails (4.7.0.0)
+      railties (>= 3.2, < 5.1)
+    font-awesome-sass (4.5.0)
+      sass (>= 3.2)
+    globalid (0.3.6)
+      activesupport (>= 4.1.0)
+    i18n (0.7.0)
+    iniparse (1.4.2)
+    jbuilder (2.3.2)
+      activesupport (>= 3.0.0, < 5)
+      multi_json (~> 1.2)
+    jquery-inputmask-rails (2.5.5)
+    jquery-migrate-rails (1.2.1)
+    jquery-rails (4.0.5)
+      rails-dom-testing (~> 1.0)
+      railties (>= 4.2.0)
+      thor (>= 0.14, < 2.0)
+    jquery-slimscroll-rails (1.0.9)
+    jquery-ui-rails (5.0.5)
+      railties (>= 3.2.16)
+    json (1.8.3)
+    loofah (2.0.3)
+      nokogiri (>= 1.5.9)
+    mail (2.6.3)
+      mime-types (>= 1.16, < 3)
+    method_source (0.8.2)
+    mime-types (2.6.2)
+    mimemagic (0.3.0)
+    mini_magick (4.5.1)
+    mini_portile (0.6.2)
+    minitest (5.8.1)
+    momentjs-rails (2.11.0)
+      railties (>= 3.1)
+    multi_json (1.11.2)
+    nokogiri (1.6.6.2)
+      mini_portile (~> 0.6.0)
+    orm_adapter (0.5.0)
+    overcommit (0.37.0)
+      childprocess (~> 0.5.8)
+      iniparse (~> 1.4)
+    paperclip (4.3.1)
+      activemodel (>= 3.2.0)
+      activesupport (>= 3.2.0)
+      cocaine (~> 0.5.5)
+      mime-types
+      mimemagic (= 0.3.0)
+    parser (2.3.3.1)
+      ast (~> 2.2)
+    pg (0.18.3)
+    powerpack (0.1.1)
+    pry (0.10.4)
+      coderay (~> 1.1.0)
+      method_source (~> 0.8.1)
+      slop (~> 3.4)
+    pry-rails (0.3.4)
+      pry (>= 0.9.10)
+    rack (1.6.4)
+    rack-test (0.6.3)
+      rack (>= 1.0)
+    rails (4.2.4)
+      actionmailer (= 4.2.4)
+      actionpack (= 4.2.4)
+      actionview (= 4.2.4)
+      activejob (= 4.2.4)
+      activemodel (= 4.2.4)
+      activerecord (= 4.2.4)
+      activesupport (= 4.2.4)
+      bundler (>= 1.3.0, < 2.0)
+      railties (= 4.2.4)
+      sprockets-rails
+    rails-deprecated_sanitizer (1.0.3)
+      activesupport (>= 4.2.0.alpha)
+    rails-dom-testing (1.0.7)
+      activesupport (>= 4.2.0.beta, < 5.0)
+      nokogiri (~> 1.6.0)
+      rails-deprecated_sanitizer (>= 1.0.1)
+    rails-erd (1.4.5)
+      activerecord (>= 3.2)
+      activesupport (>= 3.2)
+      choice (~> 0.2.0)
+      ruby-graphviz (~> 1.2)
+    rails-html-sanitizer (1.0.2)
+      loofah (~> 2.0)
+    rails-jquery-autocomplete (1.0.3)
+      rails (>= 3.2)
+    rails-observers (0.1.2)
+      activemodel (~> 4.0)
+    railties (4.2.4)
+      actionpack (= 4.2.4)
+      activesupport (= 4.2.4)
+      rake (>= 0.8.7)
+      thor (>= 0.18.1, < 2.0)
+    rainbow (2.1.0)
+    rake (10.4.2)
+    rdoc (4.2.0)
+    remotipart (1.2.1)
+    responders (2.1.1)
+      railties (>= 4.2.0, < 5.1)
+    rubocop (0.46.0)
+      parser (>= 2.3.1.1, < 3.0)
+      powerpack (~> 0.1)
+      rainbow (>= 1.99.1, < 3.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (~> 1.0, >= 1.0.1)
+    ruby-graphviz (1.2.2)
+    ruby-progressbar (1.8.1)
+    sass (3.4.21)
+    sass-rails (5.0.4)
+      railties (>= 4.0.0, < 5.0)
+      sass (~> 3.1)
+      sprockets (>= 2.8, < 4.0)
+      sprockets-rails (>= 2.0, < 4.0)
+      tilt (>= 1.1, < 3)
+    sdoc (0.4.1)
+      json (~> 1.7, >= 1.7.7)
+      rdoc (~> 4.0)
+    select2-rails (4.0.1)
+      thor (~> 0.14)
+    simple-line-icons-rails (0.0.1)
+      railties (>= 3.2, < 5.0)
+    simple-navigation (3.14.0)
+      activesupport (>= 2.3.2)
+    simple_navigation_renderers (1.0.2)
+      simple-navigation (~> 3.11)
+    slop (3.6.0)
+    spring (1.4.0)
+    sprockets (3.4.0)
+      rack (> 1, < 3)
+    sprockets-rails (2.3.3)
+      actionpack (>= 3.0)
+      activesupport (>= 3.0)
+      sprockets (>= 2.8, < 4.0)
+    thor (0.19.1)
+    thread_safe (0.3.5)
+    tilt (2.0.2)
+    toastr_rails (2.1.1)
+    turbolinks (2.5.3)
+      coffee-rails
+    twitter-typeahead-rails (0.11.1)
+      actionpack (>= 3.1)
+      jquery-rails
+      railties (>= 3.1)
+    twitter_bootstrap_wizard_rails (0.1.1)
+      railties
+    tzinfo (1.2.2)
+      thread_safe (~> 0.1)
+    uglifier (2.7.2)
+      execjs (>= 0.3.0)
+      json (>= 1.8.0)
+    unicode-display_width (1.1.2)
+    warden (1.2.4)
+      rack (>= 1.0)
+    web-console (2.2.1)
+      activemodel (>= 4.0)
+      binding_of_caller (>= 0.7.2)
+      railties (>= 4.0)
+      sprockets-rails (>= 2.0, < 4.0)
+    wicked_pdf (1.0.6)
+    will_paginate (3.1.5)
+    wkhtmltopdf-binary (0.12.3)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  acts-as-taggable-on (~> 3.4)
+  audited-activerecord (~> 4.0)
+  bootbox-rails (~> 0.4)
+  bootstrap-multiselect-rails
+  bootstrap-sass
+  bootstrap-select-rails
+  bootstrap-switch-rails
+  bootstrap-tagsinput-rails
+  bootstrap3-datetimepicker-rails (~> 4.17.37)
+  breadcrumbs_on_rails
+  byebug
+  cancan
+  carrierwave (>= 1.0.0.rc, < 2.0)
+  coffee-rails (~> 4.1.0)
+  devise
+  devise_security_extension
+  font-awesome-rails
+  font-awesome-sass (~> 4.5.0)
+  jbuilder (~> 2.0)
+  jquery-datatables-rails!
+  jquery-inputmask-rails (~> 2.5, >= 2.5.5)
+  jquery-migrate-rails (~> 1.2, >= 1.2.1)
+  jquery-rails
+  jquery-slimscroll-rails (~> 1.0, >= 1.0.9)
+  jquery-ui-rails
+  mini_magick
+  momentjs-rails (>= 2.9.0)
+  nokogiri
+  overcommit
+  paperclip
+  pg
+  pry-rails
+  rails (= 4.2.4)
+  rails-erd
+  rails-jquery-autocomplete
+  remotipart (~> 1.2)
+  responders
+  rubocop (~> 0.46.0)
+  sass-rails (~> 5.0)
+  sdoc (~> 0.4.0)
+  select2-rails
+  simple-line-icons-rails
+  simple_navigation_renderers
+  spring
+  sprockets-rails
+  toastr_rails
+  turbolinks
+  twitter-typeahead-rails
+  twitter_bootstrap_wizard_rails
+  uglifier (>= 1.3.0)
+  web-console (~> 2.0)
+  wicked_pdf
+  will_paginate
+  wkhtmltopdf-binary
+
+BUNDLED WITH
+   1.13.6

+ 28 - 0
README.rdoc

@@ -0,0 +1,28 @@
+== README
+
+This README would normally document whatever steps are necessary to get the
+application up and running.
+
+Things you may want to cover:
+
+* Ruby version
+
+* System dependencies
+
+* Configuration
+
+* Database creation
+
+* Database initialization
+
+* How to run the test suite
+
+* Services (job queues, cache servers, search engines, etc.)
+
+* Deployment instructions
+
+* ...
+
+
+Please feel free to use a different markup language if you do not plan to run
+<tt>rake doc:app</tt>.

+ 6 - 0
Rakefile

@@ -0,0 +1,6 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+
+Rails.application.load_tasks

BIN
app/assets/fonts/font-awesome/fontawesome-webfont.eot


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 655 - 0
app/assets/fonts/font-awesome/fontawesome-webfont.svg


BIN
app/assets/fonts/font-awesome/fontawesome-webfont.ttf


BIN
app/assets/fonts/font-awesome/fontawesome-webfont.woff


BIN
app/assets/fonts/font-awesome/fontawesome-webfont.woff2


+ 0 - 0
app/assets/images/.keep


BIN
app/assets/images/avatar.png


BIN
app/assets/images/logos/logo-default.png


BIN
app/assets/images/no-image.png


BIN
app/assets/images/profile.png


BIN
app/assets/images/sprite.png


+ 4 - 0
app/assets/javascripts/amcharts/ajax.js

@@ -0,0 +1,4 @@
+//= require_self
+//= require_tree ./ajax
+
+AmCharts.RB.Ajax = {};

+ 70 - 0
app/assets/javascripts/amcharts/ajax/request.js

@@ -0,0 +1,70 @@
+//= require amcharts/util
+//= require amcharts/ajax/response
+//= require_self
+
+AmCharts.RB.Ajax.Request = AmCharts.RB.Util.Class.create({
+  response: null,
+
+  initialize: function(url, params, method, on_state_change)
+  {
+    this.transport = this.get_transport();
+
+    this.url = url;
+    this.method = method ? method.toUpperCase() : 'GET';
+    this.params = AmCharts.isString(params) ? params : AmCharts.RB.Util.to_query_string(params);
+
+    if (this.method != 'GET' && this.method != 'POST') {
+      // simulate other verbs over post
+      this.params += (this.params ? '&' : '') + "_method=" + this.method;
+      this.method = 'POST';
+    }
+
+    if (this.params && this.method === 'GET') {
+      // when GET, append parameters to URL
+      this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + this.params;
+    }
+
+    this.transport.open(this.method.toUpperCase(), this.url, true);
+    this.response = new AmCharts.RB.Ajax.Response(this.transport);
+
+    if (on_state_change && AmCharts.RB.Util.is_function(on_state_change)) {
+      this.transport.onreadystatechange = on_state_change;
+    }
+
+    this.set_request_headers();
+    this.transport.send(this.method === 'POST' ? this.params : null);
+  },
+
+  get_transport: function()
+  {
+    return AmCharts.RB.Util.try_these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  set_request_headers: function()
+  {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'POST')
+    {
+      headers['Content-type'] = "application/x-www-form-urlencoded; charset=UTF-8";
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+        headers['Connection'] = 'close';
+    }
+
+    for (var name in headers) {
+      this.transport.setRequestHeader(name, headers[name]);
+    }
+  }
+});

+ 26 - 0
app/assets/javascripts/amcharts/ajax/response.js

@@ -0,0 +1,26 @@
+//= require json2
+
+AmCharts.RB.Ajax.Response = function(transport) {
+  var $transport = transport;
+
+  return {
+    status: function() { return $transport.status },
+    status_text: function() { return $transport.statusText },
+    ready_state: function() { return $transport.readyState },
+
+    get_header: function(name)
+    {
+      try {
+        return $transport.getResponseHeader(name) || null;
+      } catch (e) { return null; }
+    },
+
+    get_json: function()
+    {
+      try {
+        return JSON.parse($transport.responseText);
+      } catch(e) { return null; }
+    }
+  }
+};
+

+ 11 - 0
app/assets/javascripts/amcharts/all.js

@@ -0,0 +1,11 @@
+/*
+ *= require amcharts/amcharts
+ *= require amcharts/amstock
+ *= require amcharts/funnel
+ *= require amcharts/gauge
+ *= require amcharts/pie
+ *= require amcharts/radar
+ *= require amcharts/serial
+ *= require amcharts/xy
+ *= require amcharts/themes/light
+*/

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 381 - 0
app/assets/javascripts/amcharts/amcharts.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 98 - 0
app/assets/javascripts/amcharts/amstock.js


+ 67 - 0
app/assets/javascripts/amcharts/chart.js

@@ -0,0 +1,67 @@
+//= require amcharts/ext/object_keys
+
+AmCharts.RB.Chart = AmCharts.RB.Util.Class.create({
+  initialize: function(chart)
+  {
+    this.chart = chart;
+  },
+
+  load_data: function(data)
+  {
+    this.chart.dataProvider = data;
+
+    if (this.pie()) {
+      if (AmCharts.RB.Util.is_empty(this.chart.titleField)) this.chart.titleField = this.title_field();
+      if (AmCharts.RB.Util.is_empty(this.chart.valueField)) this.chart.valueField = this.value_field();
+    }
+    else
+    {
+      if (AmCharts.RB.Util.is_empty(this.chart.categoryField)) this.chart.categoryField = this.category_field();
+    }
+
+
+    this.chart.validateData();
+    this.chart.animateAgain();
+  },
+
+  category_field: function()
+  {
+    if (this.chart.dataProvider.length == 0) return '';
+    return Object.keys(this.chart.dataProvider[0])[0];
+  },
+
+  value_field: function()
+  {
+    if (this.chart.dataProvider.length == 0) return '';
+    return Object.keys(this.chart.dataProvider[0])[1];
+  },
+
+  title_field: function()
+  {
+    if (this.chart.dataProvider.length == 0) return '';
+    return Object.keys(this.chart.dataProvider[0])[0];
+  },
+
+  failed: function(message)
+  {
+    var blanket = AmCharts.RB.Helpers.get_blanket(this.chart.container.div),
+      blanket_inner = blanket.childNodes[0].childNodes[0].childNodes[0];
+
+    blanket.style.display = '';
+    var error_div = document.createElement("DIV");
+    error_div.className = 'chart-loading-error';
+    error_div.appendChild(document.createTextNode(message));
+    blanket_inner.innerHTML = "";
+    blanket_inner.appendChild(error_div);
+  },
+
+  pie: function()
+  {
+    return this.chart.type === "pie";
+  },
+
+  serial: function()
+  {
+    return this.chart.type === "serial";
+  }
+});

+ 24 - 0
app/assets/javascripts/amcharts/ext/get_elements_by_class_name.js

@@ -0,0 +1,24 @@
+// Add indexOf and getElementsByClassName methods
+// See: http://stackoverflow.com/questions/13261506/getelementsbyclassname-in-ie8
+(function() {
+  var indexOf = [].indexOf || function(prop) {
+    for (var i = 0; i < this.length; i++) {
+      if (this[i] === prop) return i;
+    }
+    return -1;
+  };
+
+  window.getElementsByClassName = function(className, context) {
+    if (context.getElementsByClassName) return context.getElementsByClassName(className);
+    var elems = document.querySelectorAll ? context.querySelectorAll("." + className) : (function() {
+      var all = context.getElementsByTagName("*"),
+        elements = [],
+        i = 0;
+      for (; i < all.length; i++) {
+        if (all[i].className && (" " + all[i].className + " ").indexOf(" " + className + " ") > -1 && indexOf.call(elements,all[i]) === -1) elements.push(all[i]);
+      }
+      return elements;
+    })();
+    return elems;
+  };
+})();

+ 41 - 0
app/assets/javascripts/amcharts/ext/object_keys.js

@@ -0,0 +1,41 @@
+// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
+if (!Object.keys) {
+  Object.keys = (function () {
+    'use strict';
+    var hasOwnProperty = Object.prototype.hasOwnProperty,
+      hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
+      dontEnums = [
+        'toString',
+        'toLocaleString',
+        'valueOf',
+        'hasOwnProperty',
+        'isPrototypeOf',
+        'propertyIsEnumerable',
+        'constructor'
+      ],
+      dontEnumsLength = dontEnums.length;
+
+    return function (obj) {
+      if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
+        throw new TypeError('Object.keys called on non-object');
+      }
+
+      var result = [], prop, i;
+
+      for (prop in obj) {
+        if (hasOwnProperty.call(obj, prop)) {
+          result.push(prop);
+        }
+      }
+
+      if (hasDontEnumBug) {
+        for (i = 0; i < dontEnumsLength; i++) {
+          if (hasOwnProperty.call(obj, dontEnums[i])) {
+            result.push(dontEnums[i]);
+          }
+        }
+      }
+      return result;
+    };
+  }());
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 17 - 0
app/assets/javascripts/amcharts/funnel.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 20 - 0
app/assets/javascripts/amcharts/gauge.js


+ 131 - 0
app/assets/javascripts/amcharts/helpers.js

@@ -0,0 +1,131 @@
+AmCharts.RB.Helpers = {
+  create_unit: function(num, unit)
+  {
+    // If the number doesn't end with a unit already, add the given one
+    return num.toString().match(/\d$/) ? num + unit : num;
+  },
+
+  add_container_if_needed: function(id, width, height)
+  {
+    var container = document.getElementById(id);
+
+    if (!container) {
+      container = document.createElement("DIV");
+      container.className = 'chart';
+      container.id = id;
+      if (width) container.style.width = this.create_unit(width, 'px');
+      container.style.height = this.create_unit(height, 'px');
+
+      // Add the container before the last script
+      var scripts = document.getElementsByTagName('script');
+      var this_script = scripts[scripts.length - 1];
+      this_script.parentNode.insertBefore(container, this_script);
+    }
+
+    return container;
+  },
+
+  add_blanket: function(container)
+  {
+    var wrapper = document.createElement("DIV"),
+      blanket = document.createElement("DIV"),
+      blanket_container = document.createElement("DIV"),
+      blanket_outer = document.createElement("DIV"),
+      blanket_inner = document.createElement("DIV");
+
+    wrapper.className = 'chart-wrapper';
+    wrapper.id = container.id + "_wrapper";
+    wrapper.style.width = container.style.width;
+    wrapper.style.minHeight = container.style.height;
+
+    container.style.width = '100%';
+    blanket.className = 'chart-blanket';
+    blanket.style.display = 'none';
+
+    container.parentNode.insertBefore(wrapper, container);
+
+    blanket_inner.className = 'chart-blanket-inner';
+
+    blanket_outer.appendChild(blanket_inner);
+    blanket_container.appendChild(blanket_outer);
+    blanket.appendChild(blanket_container);
+
+    wrapper.appendChild(blanket);
+    wrapper.appendChild(container);
+  },
+
+  get_wrapper: function(container)
+  {
+    var wrapper = container;
+    while (wrapper && (" " + wrapper.className + " ").indexOf(" chart-wrapper ") < 0) {
+      wrapper = wrapper.parentElement;
+    }
+
+    return wrapper;
+  },
+
+  get_blanket: function(container)
+  {
+    var wrapper = this.get_wrapper(container);
+    return getElementsByClassName('chart-blanket', wrapper)[0];
+  },
+
+  add_loading_indicator: function(container, message, image_path) {
+    var loading_div = document.createElement("DIV"),
+        loading_image = document.createElement("IMG"),
+        loading = document.createTextNode(message),
+        blanket = this.get_blanket(container),
+        blanket_inner = blanket.childNodes[0].childNodes[0].childNodes[0];
+
+    loading_div.className = 'chart-loading';
+    loading_image.className = 'chart-loading-image';
+    loading_image.src = image_path;
+
+    loading_div.appendChild(loading);
+
+    blanket.style.display = '';
+    blanket_inner.innerHTML = "";
+    blanket_inner.appendChild(loading_div);
+    blanket_inner.appendChild(loading_image);
+  },
+
+  hide_loading_indicator: function(chart) {
+    // If data is updated but still empty, keep the indicator up
+    if (!chart.dataProvider.length) return;
+
+    var container = chart.container.div,
+        blanket = AmCharts.RB.Helpers.get_blanket(container);
+
+    if (blanket) blanket.style.display = 'none';
+  },
+
+  // If the chart is provided by a remote provider which is loaded immediately (not defered),
+  // and the provider has loaded before the chart has, reload the chart with the data
+  load_from_immediate_provider: function(chart) {
+    if (chart.remoteProvider && chart.remoteProvider.loaded() && !chart.dataProvider.length)
+    {
+      new AmCharts.RB.Chart(chart).load_data(chart.remoteProvider.data);
+    }
+  },
+
+  add_legend_div: function(id, main_div) {
+    var legend = document.getElementById(id);
+
+    if (!legend) {
+      legend = document.createElement("DIV");
+      legend.className = 'chart-legend';
+      legend.id = id;
+      legend.style.width = main_div.getWidth() + 'px';
+
+      var wrapper = main_div.parentNode;
+      if (main_div.nextSibling) {
+        wrapper.insertBefore(legend, main_div.nextSibling);
+      }
+      else {
+        wrapper.appendChild(legend);
+      }
+    }
+
+    return legend;
+  }
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 10 - 0
app/assets/javascripts/amcharts/pie.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 10 - 0
app/assets/javascripts/amcharts/radar.js


+ 59 - 0
app/assets/javascripts/amcharts/remote_json_provider.js

@@ -0,0 +1,59 @@
+//= require ./ajax
+
+AmCharts.RB.RemoteJSONProvider = AmCharts.RB.Util.Class.create(function()
+{
+  function state_changed()
+  {
+    var response = this.request.response;
+
+    if (response.ready_state() == 4)
+    {
+      if (response.status() == 200)
+      {
+        this.completed = true;
+        this.started = false;
+
+        this.data = response.get_json();
+        if (this.chart) this.chart.load_data(this.data);
+      }
+      else if (this.chart)
+      {
+        this.chart.failed("Error loading chart data: " + this.url);
+      }
+    }
+  }
+
+  return {
+    request: null,
+
+    initialize: function(chart, url, params, method)
+    {
+      this.url = url;
+      this.completed = this.started = false
+      this.params = params;
+      this.method = method || 'GET';
+
+      if (chart) this.chart = new AmCharts.RB.Chart(chart);
+    },
+
+    load: function(defer)
+    {
+      this.started = true;
+
+      setTimeout(function(){
+        this.request = new AmCharts.RB.Ajax.Request(this.url, this.params, this.method, state_changed.bind(this));
+      }.bind(this), defer || 0)
+    },
+
+    loaded: function()
+    {
+      return this.completed;
+    },
+
+    loading: function()
+    {
+      return !this.loaded() && this.started;
+    }
+  }
+}());
+

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 57 - 0
app/assets/javascripts/amcharts/serial.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
app/assets/javascripts/amcharts/themes/light.js


+ 71 - 0
app/assets/javascripts/amcharts/util.js

@@ -0,0 +1,71 @@
+AmCharts.RB.Util = {
+  Class: {
+    create: function(block) {
+      function klass() {
+        this.initialize.apply(this, arguments);
+      }
+
+      klass.prototype = block || {};
+      if (!klass.prototype.initialize) klass.prototype.initialize = function() {}
+      klass.prototype.constructor = klass;
+      return klass;
+    }
+  },
+
+  try_these: function()
+  {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++)
+    {
+      var lambda = arguments[i];
+
+      try
+      {
+        returnValue = lambda();
+        break;
+      }
+      catch (e) { }
+    }
+
+    return returnValue;
+  },
+
+  to_query_string: function(obj, prefix)
+  {
+    var str = [];
+
+    if (AmCharts.ifArray(obj))
+    {
+      for (var i = 0; i < obj.length; i++)
+      {
+        var k = prefix ? prefix + "[]" : "[]", v = obj[i];
+        str.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
+      }
+    }
+    else {
+      for (var p in obj) {
+        if (!obj.hasOwnProperty(p)) continue;
+        var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
+
+        str.push(typeof v == "object" ?
+          AmCharts.RB.Util.to_query_string(v, k) :
+          encodeURIComponent(k) + "=" + encodeURIComponent(v));
+      }
+  }
+
+    return str.join("&");
+  },
+
+  is_function: function(object)
+  {
+    return Object.prototype.toString.call(object) === "[object Function]";
+  },
+
+  is_empty: function(object)
+  {
+    if (object === null || object === undefined) return true;
+    if (object.length !== undefined) return object.length == 0;
+    return undefined;
+  }
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 17 - 0
app/assets/javascripts/amcharts/xy.js


+ 69 - 0
app/assets/javascripts/application.js

@@ -0,0 +1,69 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// compiled file.
+//
+// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
+// about supported directives.
+//
+//= require jquery
+//= require jquery_ujs
+//= require bootstrap-sprockets
+//= require bootstrap/alert
+//= require bootstrap/dropdown
+//= require bootstrap-select
+//= require bootstrap-switch
+//= require bootstrap-fileinput
+//= require moment
+//= require bootstrap-datetimepicker
+//= require bootstrap-datepicker
+//= require select2
+//= require dataTables/jquery.dataTables
+//= require dataTables/extras/dataTables.responsive
+//= require dataTables/extras/dataTables.tableTools
+//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
+//= require jquery.inputmask
+//= require jquery.inputmask.extensions
+//= require jquery.inputmask.numeric.extensions
+//= require jquery.inputmask.date.extensions
+//= require jquery-migrate-min
+//= require bootbox
+//= require autocomplete-rails
+//= require turbolinks
+//= require bootstrap-multiselect
+//= require twitter/typeahead.min
+//= require handlebars.min.js
+//= require toastr
+//= require datatable.js
+//= require amcharts/all.js
+//= require bootstrap-tagsinput
+//
+//= require jquery.blockui.min.js
+//= require jquery.multi-select.js
+//= require jquery.quicksearch.js
+//= require uniform/jquery.uniform.js
+//= require jquery.bootstrap.wizard
+//= require prettify
+//
+//= require app.js
+//= require demo.js
+//= require layout.js
+//= require components-date-time-pickers.js
+//= require components-select2.js
+//= require excanvas.js
+//= require respond.js
+//= require config.js
+
+//= require backstretch/jquery.backstretch.min.js
+//= require plugins/login-5.js
+//= require jquery-barcodeListener.js
+//= require jquery.remotipart
+//= require accounting.min.js
+
+
+
+

+ 3 - 0
app/assets/javascripts/cash_outs.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/cash_registers.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/commissions.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 304 - 0
app/assets/javascripts/datatable.js

@@ -0,0 +1,304 @@
+/***
+Wrapper/Helper Class for datagrid based on jQuery Datatable Plugin
+***/
+var Datatable = function() {
+
+    var tableOptions; // main options
+    var dataTable; // datatable object
+    var table; // actual table jquery object
+    var tableContainer; // actual table container object
+    var tableWrapper; // actual table wrapper jquery object
+    var tableInitialized = false;
+    var ajaxParams = {}; // set filter mode
+    var the;
+
+    var countSelectedRecords = function() {
+        var selected = $('tbody > tr > td:nth-child(1) input[type="checkbox"]:checked', table).size();
+        var text = tableOptions.dataTable.language.metronicGroupActions;
+        if (selected > 0) {
+            $('.table-group-actions > span', tableWrapper).text(text.replace("_TOTAL_", selected));
+        } else {
+            $('.table-group-actions > span', tableWrapper).text("");
+        }
+    };
+
+    return {
+
+        //main function to initiate the module
+        init: function(options) {
+
+            if (!$().dataTable) {
+                return;
+            }
+
+            the = this;
+
+            // default settings
+            options = $.extend(true, {
+                src: "", // actual table  
+                filterApplyAction: "filter",
+                filterCancelAction: "filter_cancel",
+                resetGroupActionInputOnSuccess: true,
+                loadingMessage: 'Loading...',
+                dataTable: {
+                    "dom": "<'row'<'col-md-8 col-sm-12'pli><'col-md-4 col-sm-12'<'table-group-actions pull-right'>>r><'table-responsive't><'row'<'col-md-8 col-sm-12'pli><'col-md-4 col-sm-12'>>", // datatable layout
+                    "pageLength": 10, // default records per page
+                    "language": { // language settings
+                        // metronic spesific
+                        "metronicGroupActions": "_TOTAL_ records selected:  ",
+                        "metronicAjaxRequestGeneralError": "Could not complete request. Please check your internet connection",
+
+                        // data tables spesific
+                        "lengthMenu": "<span class='seperator'>|</span>View _MENU_ records",
+                        "info": "<span class='seperator'>|</span>Found total _TOTAL_ records",
+                        "infoEmpty": "No records found to show",
+                        "emptyTable": "No data available in table",
+                        "zeroRecords": "No matching records found",
+                        "paginate": {
+                            "previous": "Prev",
+                            "next": "Next",
+                            "last": "Last",
+                            "first": "First",
+                            "page": "Page",
+                            "pageOf": "of"
+                        }
+                    },
+
+                    "orderCellsTop": true,
+                    "columnDefs": [{ // define columns sorting options(by default all columns are sortable extept the first checkbox column)
+                        'orderable': false,
+                        'targets': [0]
+                    }],
+
+                    "pagingType": "bootstrap_extended", // pagination type(bootstrap, bootstrap_full_number or bootstrap_extended)
+                    "autoWidth": false, // disable fixed width and enable fluid table
+                    "processing": false, // enable/disable display message box on record load
+                    "serverSide": true, // enable/disable server side ajax loading
+
+                    "ajax": { // define ajax settings
+                        "url": "", // ajax URL
+                        "type": "POST", // request type
+                        "timeout": 20000,
+                        "data": function(data) { // add request parameters before submit
+                            $.each(ajaxParams, function(key, value) {
+                                data[key] = value;
+                            });
+                            App.blockUI({
+                                message: tableOptions.loadingMessage,
+                                target: tableContainer,
+                                overlayColor: 'none',
+                                cenrerY: true,
+                                boxed: true
+                            });
+                        },
+                        "dataSrc": function(res) { // Manipulate the data returned from the server
+                            if (res.customActionMessage) {
+                                App.alert({
+                                    type: (res.customActionStatus == 'OK' ? 'success' : 'danger'),
+                                    icon: (res.customActionStatus == 'OK' ? 'check' : 'warning'),
+                                    message: res.customActionMessage,
+                                    container: tableWrapper,
+                                    place: 'prepend'
+                                });
+                            }
+
+                            if (res.customActionStatus) {
+                                if (tableOptions.resetGroupActionInputOnSuccess) {
+                                    $('.table-group-action-input', tableWrapper).val("");
+                                }
+                            }
+
+                            if ($('.group-checkable', table).size() === 1) {
+                                $('.group-checkable', table).attr("checked", false);
+                                $.uniform.update($('.group-checkable', table));
+                            }
+
+                            if (tableOptions.onSuccess) {
+                                tableOptions.onSuccess.call(undefined, the, res);
+                            }
+
+                            App.unblockUI(tableContainer);
+
+                            return res.data;
+                        },
+                        "error": function() { // handle general connection errors
+                            if (tableOptions.onError) {
+                                tableOptions.onError.call(undefined, the);
+                            }
+
+                            App.alert({
+                                type: 'danger',
+                                icon: 'warning',
+                                message: tableOptions.dataTable.language.metronicAjaxRequestGeneralError,
+                                container: tableWrapper,
+                                place: 'prepend'
+                            });
+
+                            App.unblockUI(tableContainer);
+                        }
+                    },
+
+                    "drawCallback": function(oSettings) { // run some code on table redraw
+                        if (tableInitialized === false) { // check if table has been initialized
+                            tableInitialized = true; // set table initialized
+                            table.show(); // display table
+                        }
+                        App.initUniform($('input[type="checkbox"]', table)); // reinitialize uniform checkboxes on each table reload
+                        countSelectedRecords(); // reset selected records indicator
+
+                        // callback for ajax data load
+                        if (tableOptions.onDataLoad) {
+                            tableOptions.onDataLoad.call(undefined, the);
+                        }
+                    }
+                }
+            }, options);
+
+            tableOptions = options;
+
+            // create table's jquery object
+            table = $(options.src);
+            tableContainer = table.parents(".table-container");
+
+            // apply the special class that used to restyle the default datatable
+            var tmp = $.fn.dataTableExt.oStdClasses;
+
+            $.fn.dataTableExt.oStdClasses.sWrapper = $.fn.dataTableExt.oStdClasses.sWrapper + " dataTables_extended_wrapper";
+            $.fn.dataTableExt.oStdClasses.sFilterInput = "form-control input-xs input-sm input-inline";
+            $.fn.dataTableExt.oStdClasses.sLengthSelect = "form-control input-xs input-sm input-inline";
+
+            // initialize a datatable
+            dataTable = table.DataTable(options.dataTable);
+
+            // revert back to default
+            $.fn.dataTableExt.oStdClasses.sWrapper = tmp.sWrapper;
+            $.fn.dataTableExt.oStdClasses.sFilterInput = tmp.sFilterInput;
+            $.fn.dataTableExt.oStdClasses.sLengthSelect = tmp.sLengthSelect;
+
+            // get table wrapper
+            tableWrapper = table.parents('.dataTables_wrapper');
+
+            // build table group actions panel
+            if ($('.table-actions-wrapper', tableContainer).size() === 1) {
+                $('.table-group-actions', tableWrapper).html($('.table-actions-wrapper', tableContainer).html()); // place the panel inside the wrapper
+                $('.table-actions-wrapper', tableContainer).remove(); // remove the template container
+            }
+            // handle group checkboxes check/uncheck
+            $('.group-checkable', table).change(function() {
+                var set = table.find('tbody > tr > td:nth-child(1) input[type="checkbox"]');
+                var checked = $(this).prop("checked");
+                $(set).each(function() {
+                    $(this).prop("checked", checked);
+                });
+                $.uniform.update(set);
+                countSelectedRecords();
+            });
+
+            // handle row's checkbox click
+            table.on('change', 'tbody > tr > td:nth-child(1) input[type="checkbox"]', function() {
+                countSelectedRecords();
+            });
+
+            // handle filter submit button click
+            table.on('click', '.filter-submit', function(e) {
+                e.preventDefault();
+                the.submitFilter();
+            });
+
+            // handle filter cancel button click
+            table.on('click', '.filter-cancel', function(e) {
+                e.preventDefault();
+                the.resetFilter();
+            });
+        },
+
+        submitFilter: function() {
+            the.setAjaxParam("action", tableOptions.filterApplyAction);
+
+            // get all typeable inputs
+            $('textarea.form-filter, select.form-filter, input.form-filter:not([type="radio"],[type="checkbox"])', table).each(function() {
+                the.setAjaxParam($(this).attr("name"), $(this).val());
+            });
+
+            // get all checkboxes
+            $('input.form-filter[type="checkbox"]:checked', table).each(function() {
+                the.addAjaxParam($(this).attr("name"), $(this).val());
+            });
+
+            // get all radio buttons
+            $('input.form-filter[type="radio"]:checked', table).each(function() {
+                the.setAjaxParam($(this).attr("name"), $(this).val());
+            });
+
+            dataTable.ajax.reload();
+        },
+
+        resetFilter: function() {
+            $('textarea.form-filter, select.form-filter, input.form-filter', table).each(function() {
+                $(this).val("");
+            });
+            $('input.form-filter[type="checkbox"]', table).each(function() {
+                $(this).attr("checked", false);
+            });
+            the.clearAjaxParams();
+            the.addAjaxParam("action", tableOptions.filterCancelAction);
+            dataTable.ajax.reload();
+        },
+
+        getSelectedRowsCount: function() {
+            return $('tbody > tr > td:nth-child(1) input[type="checkbox"]:checked', table).size();
+        },
+
+        getSelectedRows: function() {
+            var rows = [];
+            $('tbody > tr > td:nth-child(1) input[type="checkbox"]:checked', table).each(function() {
+                rows.push($(this).val());
+            });
+
+            return rows;
+        },
+
+        setAjaxParam: function(name, value) {
+            ajaxParams[name] = value;
+        },
+
+        addAjaxParam: function(name, value) {
+            if (!ajaxParams[name]) {
+                ajaxParams[name] = [];
+            }
+
+            skip = false;
+            for (var i = 0; i < (ajaxParams[name]).length; i++) { // check for duplicates
+                if (ajaxParams[name][i] === value) {
+                    skip = true;
+                }
+            }
+
+            if (skip === false) {
+                ajaxParams[name].push(value);
+            }
+        },
+
+        clearAjaxParams: function(name, value) {
+            ajaxParams = {};
+        },
+
+        getDataTable: function() {
+            return dataTable;
+        },
+
+        getTableWrapper: function() {
+            return tableWrapper;
+        },
+
+        gettableContainer: function() {
+            return tableContainer;
+        },
+
+        getTable: function() {
+            return table;
+        }
+
+    };
+
+};

+ 3 - 0
app/assets/javascripts/expensesconcepts.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 107 - 0
app/assets/javascripts/jquery-barcodeListener.js

@@ -0,0 +1,107 @@
+/**
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the new BSD license.
+ *
+ * @author      David Zeller <me@zellerda.com>
+ * @license     http://www.opensource.org/licenses/BSD-3-Clause New BSD license
+ * @version     1.0
+ */
+(function($){
+
+    $.barcodeListener = function(context, options){
+
+        var $defaults = {
+            support: [8, 12, 13]
+        };
+
+        var $this = this;
+        $this.element = $(context);
+        $this.timeout = 0;
+        $this.code = '';
+        $this.settings = {};
+
+        $this.init = function(){
+            $this.settings = $.extend({}, $defaults, options);
+            $this.element.on('keypress', function(e){
+                $this.listen(e);
+            })
+        };
+
+        $this.listen = function(e){
+            var $char = $this.validateKey(e.which);
+            if($char === 13){
+                $this.validate();
+            } else if($char !== false) {
+                if($this.code == ''){
+                    setTimeout($this.clear(), 1000);
+                }
+                $this.add($char);
+            }
+        };
+
+        $this.validate = function(){
+            var $tmp = $this.code;
+            if($this.settings.support.indexOf($tmp.length) > -1){
+                var $d = new Date(),
+                    $interval = $d.getTime() - $this.timeout;
+                $this.clear();
+                if($interval < 1000){
+                    $this.element.trigger('barcode.valid', [$tmp]);
+                }
+            } else {
+                $this.clear();
+            }
+        };
+
+        $this.clear = function(){
+            $this.code = '';
+            $this.timeout = 0;
+        };
+
+        $this.validateKey = function(keycode){
+            if(keycode == 13 || (keycode >= 48 && keycode <= 57)){
+                if(keycode == 13){
+                    return keycode;
+                } else {
+                    return String.fromCharCode(keycode);
+                }
+            } else {
+                return false;
+            }
+        };
+
+        $this.add = function(char){
+            if($this.timeout === 0){
+                var $d = new Date();
+                $this.timeout = $d.getTime();
+            }
+            $this.code += char;
+        };
+
+        $this.init();
+    };
+
+    $.fn.barcodeListener = function(options) {
+
+        return this.each(function(){
+            if(undefined == $(this).data('barcodeListener')){
+                var plugin = new $.barcodeListener(this, options);
+                $(this).data('barcodeListener', plugin);
+            }
+        });
+
+    }
+
+})(jQuery);

+ 489 - 0
app/assets/javascripts/json2.js

@@ -0,0 +1,489 @@
+/*
+ json2.js
+ 2014-02-04
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value       any JavaScript value, usually an object or array.
+
+ replacer    an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space       an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear()   + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate())      + 'T' +
+ f(this.getUTCHours())     + ':' +
+ f(this.getUTCMinutes())   + ':' +
+ f(this.getUTCSeconds())   + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+ */
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+ */
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (typeof JSON !== 'object') {
+  JSON = {};
+}
+
+(function () {
+  'use strict';
+
+  function f(n) {
+    // Format integers to have at least two digits.
+    return n < 10 ? '0' + n : n;
+  }
+
+  if (typeof Date.prototype.toJSON !== 'function') {
+
+    Date.prototype.toJSON = function () {
+
+      return isFinite(this.valueOf())
+        ? this.getUTCFullYear()     + '-' +
+        f(this.getUTCMonth() + 1) + '-' +
+        f(this.getUTCDate())      + 'T' +
+        f(this.getUTCHours())     + ':' +
+        f(this.getUTCMinutes())   + ':' +
+        f(this.getUTCSeconds())   + 'Z'
+        : null;
+    };
+
+    String.prototype.toJSON      =
+      Number.prototype.toJSON  =
+        Boolean.prototype.toJSON = function () {
+          return this.valueOf();
+        };
+  }
+
+  var cx,
+    escapable,
+    gap,
+    indent,
+    meta,
+    rep;
+
+
+  function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+    escapable.lastIndex = 0;
+    return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+      var c = meta[a];
+      return typeof c === 'string'
+        ? c
+        : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+    }) + '"' : '"' + string + '"';
+  }
+
+
+  function str(key, holder) {
+
+// Produce a string from holder[key].
+
+    var i,          // The loop counter.
+      k,          // The member key.
+      v,          // The member value.
+      length,
+      mind = gap,
+      partial,
+      value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+    if (value && typeof value === 'object' &&
+      typeof value.toJSON === 'function') {
+      value = value.toJSON(key);
+    }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+    if (typeof rep === 'function') {
+      value = rep.call(holder, key, value);
+    }
+
+// What happens next depends on the value's type.
+
+    switch (typeof value) {
+      case 'string':
+        return quote(value);
+
+      case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+        return isFinite(value) ? String(value) : 'null';
+
+      case 'boolean':
+      case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+        return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+      case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+        if (!value) {
+          return 'null';
+        }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+        gap += indent;
+        partial = [];
+
+// Is the value an array?
+
+        if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+          length = value.length;
+          for (i = 0; i < length; i += 1) {
+            partial[i] = str(i, value) || 'null';
+          }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+          v = partial.length === 0
+            ? '[]'
+            : gap
+            ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+            : '[' + partial.join(',') + ']';
+          gap = mind;
+          return v;
+        }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+        if (rep && typeof rep === 'object') {
+          length = rep.length;
+          for (i = 0; i < length; i += 1) {
+            if (typeof rep[i] === 'string') {
+              k = rep[i];
+              v = str(k, value);
+              if (v) {
+                partial.push(quote(k) + (gap ? ': ' : ':') + v);
+              }
+            }
+          }
+        } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+          for (k in value) {
+            if (Object.prototype.hasOwnProperty.call(value, k)) {
+              v = str(k, value);
+              if (v) {
+                partial.push(quote(k) + (gap ? ': ' : ':') + v);
+              }
+            }
+          }
+        }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+        v = partial.length === 0
+          ? '{}'
+          : gap
+          ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+          : '{' + partial.join(',') + '}';
+        gap = mind;
+        return v;
+    }
+  }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+  if (typeof JSON.stringify !== 'function') {
+    escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+    meta = {    // table of character substitutions
+      '\b': '\\b',
+      '\t': '\\t',
+      '\n': '\\n',
+      '\f': '\\f',
+      '\r': '\\r',
+      '"' : '\\"',
+      '\\': '\\\\'
+    };
+    JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+      var i;
+      gap = '';
+      indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+      if (typeof space === 'number') {
+        for (i = 0; i < space; i += 1) {
+          indent += ' ';
+        }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+      } else if (typeof space === 'string') {
+        indent = space;
+      }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+      rep = replacer;
+      if (replacer && typeof replacer !== 'function' &&
+        (typeof replacer !== 'object' ||
+          typeof replacer.length !== 'number')) {
+        throw new Error('JSON.stringify');
+      }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+      return str('', {'': value});
+    };
+  }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+  if (typeof JSON.parse !== 'function') {
+    cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+    JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+      var j;
+
+      function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+        var k, v, value = holder[key];
+        if (value && typeof value === 'object') {
+          for (k in value) {
+            if (Object.prototype.hasOwnProperty.call(value, k)) {
+              v = walk(value, k);
+              if (v !== undefined) {
+                value[k] = v;
+              } else {
+                delete value[k];
+              }
+            }
+          }
+        }
+        return reviver.call(holder, key, value);
+      }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+      text = String(text);
+      cx.lastIndex = 0;
+      if (cx.test(text)) {
+        text = text.replace(cx, function (a) {
+          return '\\u' +
+            ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+        });
+      }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+      if (/^[\],:{}\s]*$/
+        .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+          .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+          .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+        j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+        return typeof reviver === 'function'
+          ? walk({'': j}, '')
+          : j;
+      }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+      throw new SyntaxError('JSON.parse');
+    };
+  }
+}());

+ 27 - 0
app/assets/javascripts/modals.js.coffee

@@ -0,0 +1,27 @@
+$ ->
+  modal_holder_selector = '#modal-holder'
+  modal_selector = '.modal'
+
+  $(document).on 'click', 'a[data-modal]', ->
+    location = $(this).attr('href')
+    #Load modal dialog from server
+    $.get location, (data)->
+      $(modal_holder_selector).html(data).
+      find(modal_selector).modal()
+    false
+
+  $(document).on 'ajax:success',
+    'form[data-modal]', (event, data, status, xhr)->
+      url = xhr.getResponseHeader('Location')
+      if url
+        # Redirect to url
+        window.location = url
+      else
+        # Remove old modal backdrop
+        $('.modal-backdrop').remove()
+
+        # Replace old modal with new one
+        $(modal_holder_selector).html(data).
+        find(modal_selector).modal()
+
+      false

+ 3 - 0
app/assets/javascripts/pos_configs.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/pre_purchases.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/pre_transfers.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/product_wastes.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/products_returns.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/sellers.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/sellerscommissions.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/special_prices.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/storers.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/transfers.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/javascripts/warehouses.coffee

@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/

+ 36 - 0
app/assets/stylesheets/application.css

@@ -0,0 +1,36 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any styles
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
+ * file per style scope.
+ *
+ *= require main.scss
+ *= require select2
+ *= require select2-bootstrap
+ *= require simple-line-icons
+ *= require dataTables/extras/dataTables.responsive
+ *= require dataTables/extras/dataTables.tableTools
+ *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
+ *= require multi-select.css
+ *= require uniform/uniform.default.css
+ *= require bootstrap-fileinput.css
+ *= require bootstrap-multiselect
+ *= require global/profile.css
+ *= require global/components-rounded.css
+ *= require global/plugins.css
+ *= require global/yellow-crusta.css
+ *= require typeahead.css
+ *= require toastr
+ *= require bootstrap-datetimepicker
+ *= require prettify
+ *= require bootstrap-tagsinput
+ *= require font-awesome
+ *= require custom
+ */
+

+ 3 - 0
app/assets/stylesheets/cash_outs.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the CashOuts controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/cash_registers.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the cash_registers controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/commissions.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the commissions controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 6 - 0
app/assets/stylesheets/custom.css

@@ -0,0 +1,6 @@
+/* Copyright (C) 2016 Spiral Media Labs - Todos los derechos reservados */
+/* here you can put your own css to customize and override the theme */
+/* Static info custom */
+
+.static-info hr {
+  margin: 0 0 0 0 !important; }

+ 3 - 0
app/assets/stylesheets/expensesconcepts.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the expensesconcepts controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 13 - 0
app/assets/stylesheets/main.scss

@@ -0,0 +1,13 @@
+// "bootstrap-sprockets" must be imported before "bootstrap" and "bootstrap/variables"
+@import "bootstrap-sprockets";
+@import "bootstrap";
+
+/* for bootstrap3 */
+@import "bootstrap3-switch";
+@import 'bootstrap-datetimepicker';
+
+@import "simple-line-icons";
+
+@import "font-awesome-sprockets";
+@import "font-awesome";
+

+ 3 - 0
app/assets/stylesheets/pos_configs.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the posConfigs controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/pre_purchases.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the PrePurchases controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/pre_transfers.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the pre_transfers controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/product_wastes.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the product_wastes controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/products_returns.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the ProductsReturns controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 4 - 0
app/assets/stylesheets/purchases.css

@@ -0,0 +1,4 @@
+/*
+  Place all the styles related to the matching controller here.
+  They will automatically be included in application.css.
+*/

+ 4 - 0
app/assets/stylesheets/purchases_data.css

@@ -0,0 +1,4 @@
+/*
+  Place all the styles related to the matching controller here.
+  They will automatically be included in application.css.
+*/

+ 60 - 0
app/assets/stylesheets/scaffold.css

@@ -0,0 +1,60 @@
+body { background-color: #fff; color: #333; }
+
+body, p, ol, ul, td {
+  font-family: verdana, arial, helvetica, sans-serif;
+  font-size:   13px;
+  line-height: 18px;
+}
+
+pre {
+  background-color: #eee;
+  padding: 10px;
+  font-size: 11px;
+}
+
+a { color: #000; }
+a:visited { color: #666; }
+a:hover { color: #fff; background-color:#000; }
+
+div.field, div.actions {
+  margin-bottom: 10px;
+}
+
+#notice {
+  color: green;
+}
+
+.field_with_errors {
+  padding: 2px;
+  background-color: red;
+  display: table;
+}
+
+#error_explanation {
+  width: 450px;
+  border: 2px solid red;
+  padding: 7px;
+  padding-bottom: 0;
+  margin-bottom: 20px;
+  background-color: #f0f0f0;
+}
+
+#error_explanation h2 {
+  text-align: left;
+  font-weight: bold;
+  padding: 5px 5px 5px 15px;
+  font-size: 12px;
+  margin: -7px;
+  margin-bottom: 0px;
+  background-color: #c00;
+  color: #fff;
+}
+
+#error_explanation ul li {
+  font-size: 12px;
+  list-style: square;
+}
+
+.chart a {
+  display:none !important;
+}

+ 73 - 0
app/assets/stylesheets/scaffolds.scss

@@ -0,0 +1,73 @@
+body {
+  background-color: #fff;
+  color: #333;
+  font-family: verdana, arial, helvetica, sans-serif;
+  font-size: 13px;
+  line-height: 18px;
+}
+
+p, ol, ul, td {
+  font-family: verdana, arial, helvetica, sans-serif;
+  font-size: 13px;
+  line-height: 18px;
+}
+
+pre {
+  background-color: #eee;
+  padding: 10px;
+  font-size: 11px;
+}
+
+a {
+  color: #000;
+
+  &:visited {
+    color: #666;
+  }
+
+  &:hover {
+    color: #fff;
+    background-color: #000;
+  }
+}
+
+div {
+  &.field, &.actions {
+    margin-bottom: 10px;
+  }
+}
+
+#notice {
+  color: green;
+}
+
+.field_with_errors {
+  padding: 2px;
+  background-color: red;
+  display: table;
+}
+
+#error_explanation {
+  width: 450px;
+  border: 2px solid red;
+  padding: 7px;
+  padding-bottom: 0;
+  margin-bottom: 20px;
+  background-color: #f0f0f0;
+
+  h2 {
+    text-align: left;
+    font-weight: bold;
+    padding: 5px 5px 5px 15px;
+    font-size: 12px;
+    margin: -7px;
+    margin-bottom: 0px;
+    background-color: #c00;
+    color: #fff;
+  }
+
+  ul li {
+    font-size: 12px;
+    list-style: square;
+  }
+}

+ 3 - 0
app/assets/stylesheets/sellers.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the sellers controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/sellerscommissions.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the sellerscommissions controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/special_prices.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the specialPrices controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/storers.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the storers controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/transfers.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the transfers controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/warehouses.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the warehouses controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 225 - 0
app/controllers/application_controller.rb

@@ -0,0 +1,225 @@
+class ApplicationController < ActionController::Base
+	# Prevent CSRF attacks by raising an exception.
+	# For APIs, you may want to use :null_session instead.
+	
+	before_filter do
+		resource = controller_path.singularize.gsub('/', '_').to_sym
+		method = "#{resource}_params"
+		params[resource] &&= send(method) if respond_to?(method, true)
+	end
+	
+	before_filter :set_pos_config
+	around_filter :user_time_zone, :if => :set_pos_config
+
+	protect_from_forgery with: :exception
+
+	##--- Breadcrum_rails
+	add_breadcrumb I18n.t("breadcrumbs.dashboard"), :root_path
+
+	##--- Restricción para autentificación 
+	before_action :authenticate_user!
+
+	##--- Notes boxes
+	add_flash_types :success, :warning, :danger, :info
+
+	##--- Parametros permitidos para los usuarios
+	before_action :configure_permitted_parameters, if: :devise_controller?
+
+	##--- Redireccionamiento para los permisos a modulos 
+	rescue_from CanCan::AccessDenied do |exception|
+		redirect_to root_url, :alert => exception.message
+	end
+
+	##--- Funciones personalizadas
+	def getcounties  
+		render :json => SpmxCounty.where("state_id = ?", params[:state_id]) 
+	end
+
+	def find
+		query = params[:query]
+		if query.include? ':'
+			# buscar con atributos
+			product_name = query[0, query.index(':') -1]
+			attribute =  query[query.index(':') +1, query.length]
+		else
+			product_name = query
+		end	
+
+		render json: (query.include? ':') ? Product.name_sku_barcode_attribute_like(product_name, attribute).limit(30).to_json(:methods => [:small_img, :display_attributes ]) : Product.name_sku_barcode_like(params[:query]).limit(30).to_json(:methods => [:small_img, :display_attributes ])
+	end
+	#para special_prices
+	def find_sp
+		query = params[:query]
+		product_name = query
+
+		render json: Product.name_sku_barcode_like_sp(product_name).limit(30).to_json(:methods => [:small_img ])
+	end
+
+	def find_from_stock
+		query = params[:query]
+		if query.include? ':'
+			# buscar con atributos
+			product_name = query[0, query.index(':') -1]
+			attribute =  query[query.index(':') +1, query.length]
+		else
+			product_name = query
+		end		
+
+		if current_user.usertype == 'S'
+			render json: (query.include? ':') ? Warehouse.find(current_user.warehouse_id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes]) : Warehouse.find(current_user.warehouse_id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes])
+		else
+			render json: (query.include? ':') ? Pointsale.find(current_user.pointsale_id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes]) : Pointsale.find(current_user.pointsale_id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes])			
+		end		
+	end
+
+	def find_from_stock_by_pointsale
+		id = params[:pointsale_id][2, params[:pointsale_id].length]
+		
+		query = params[:query]
+		if query.include? ':'
+			# buscar con atributos
+			product_name = query[0, query.index(':') -1]
+			attribute =  query[query.index(':') +1, query.length]
+		else
+			product_name = query
+		end	
+
+		if params[:pointsale_id].first == 'P'
+			render json: (query.include? ':') ? Pointsale.find(id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes]) : Pointsale.find(id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes])		
+		else
+			render json: (query.include? ':') ? Warehouse.find(id).products.name_sku_barcode_attribute_like(product_name, attribute).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes]) : Warehouse.find(id).products.name_sku_barcode_like(params[:query]).where("stock > 0").limit(30).to_json(:methods => [:small_img, :display_attributes])		
+		end
+	end
+
+	def get_subcategories  
+		render :json => params[:category_id] != '0' ? Category.activos.where("parent_id = ?", params[:category_id])  : Category.activos.where('parent_id != 0')		
+	end
+
+	def set_pos_config
+	  @pos_config = PosConfig.first
+	end
+
+	def user_time_zone(&block)
+	  Time.use_zone(@pos_config.time_zone, &block)
+	end
+
+	  #eliminar pre_sales que ya estaban guardadas
+  	def delete_pre_sales
+    	PreSale.where(user_id: current_user.id).destroy_all 
+	    respond_to do |format|
+	        format.json { head :no_content }
+	    end
+  	end
+  	
+  	#eliminar pre_purchases que ya estaban guardadas
+  	def delete_pre_purchases
+		PrePurchase.where(user_id: current_user.id).destroy_all 
+	    # render head :no_content 
+	    respond_to do |format|
+	        format.json { head :no_content }
+	    end    
+  	end
+
+  	#eliminar pre_purchases que ya estaban guardadas
+  	def delete_pre_transfers
+  		respond_to do |format|
+			pre_transfers = PreTransfer.where(user_id: current_user.id)
+			pre_transfers.each do |pre| 
+				if pre.destroy
+			      	if pre.origin_is_pointsale == 1
+			        	stock = AvailableProduct.find_by(:pointsale_id =>  pre.origin_id, 
+			          		:product_id => pre.product_id)          
+			      	else
+				        stock = WarehouseStock.find_by(:warehouse_id => pre.origin_id,
+			          		:product_id => pre.product_id)
+			      	end					
+					stock.stock += pre.quantity
+	        		stock.save					
+				end	        	
+	    	end
+	    	format.json { head :ok }
+	    end 
+  	end
+
+  	def get_max_product_id 
+  		render :json => Product.maximum(:id).to_i.next
+  	end
+
+  	def get_max_purchaseid_by_pointsale 
+  		prefix = Pointsale.find(params[:pointsale_id]).prefix
+  		next_id = Purchase.where(:pointsale_id => params[:pointsale_id]).count.next
+
+  		render :json => "#{prefix}-C-#{next_id}"
+  	end
+
+  	def get_max_purchaseid_by_warehouse 
+  		prefix = Warehouse.find(params[:warehouse_id]).prefix
+  		next_id = Purchase.where(:warehouse_id => params[:warehouse_id]).count.next
+
+  		render :json => "#{prefix}-C-#{next_id}"
+  	end  	
+
+  	def get_next_sale_code 
+  		pointsale = OpenCashRegister.find(params[:open_cash_register_id]).cash_register.pointsale
+  		next_id = pointsale.sales.count.next
+  		render :json => "#{pointsale.prefix}-V-#{next_id}"
+  	end
+  	
+  	def get_next_expense_code
+	 	if current_user.usertype == 'A'
+	 		next_id = Expense.where("expense_code ilike ?", '%ADM%').count.next
+	  		render :json => "ADM-E-#{next_id}"  	 
+	 	else
+	  		pointsale = OpenCashRegister.find(params[:open_cash_register_id]).cash_register.pointsale
+	  		next_id = pointsale.expenses.count.next
+	  		render :json => "#{pointsale.prefix}-E-#{next_id}"  	 		
+	 	end		
+  	end
+
+  	def products_by_category_pointsale
+  		products = Array.new
+  		products_by_line = Array.new
+  		category_id = params[:category_id]
+		id = params[:pointsale_id][2, params[:pointsale_id].length]
+
+  		categories = Category.find(category_id).self_and_descendents
+
+  		categories.each do |category|
+	  		products_by_line += category.products 
+  		end
+
+
+  		if params[:pointsale_id].first == 'P'
+	  		Pointsale.find(id).products.each do |p| 
+	  			if products_by_line.include?(p)
+	  				products << p
+	  			end
+	  		end  			
+  		else
+	  		Warehouse.find(id).products.each do |p| 
+	  			if products_by_line.include?(p)
+	  				products << p
+	  			end
+	  		end  	  			
+  		end
+  		render :json => products
+  	end
+
+	protected
+
+		##--- Definir los parametros definidos para los usuarios en las diferentes rutas
+		def configure_permitted_parameters
+			# devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
+
+			###-- https://github.com/plataformatec/devise#strong-parameters   
+			###-- https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address
+			devise_parameter_sanitizer.for(:sign_up) { |u| u.permit( :userid, :first_name, :last_name, :email, :password, :remember_me) }
+			# devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation, :current_password) }
+		end
+
+		def respond_modal_with(*args, &blk)
+			options = args.extract_options!
+			options[:responder] = ModalResponder
+			respond_with *args, options, &blk
+		end
+end

+ 197 - 0
app/controllers/available_products_controller.rb

@@ -0,0 +1,197 @@
+class AvailableProductsController < ApplicationController
+
+  before_action :set_available_product, only: [:edit_price, :update_price]
+  autocomplete :available_product, :name, :full => true
+
+  def stock
+    @showcolumns = "minMax"
+    # se utiliza para mandarle al datatable el numero de columnas y en que orden se deben de acomodar
+    @column_definition = [{ "data": "0"}, { "data": "1" }, { "data": "2" }, { "data": "3" }, { "data": "4" }, { "data": "5" }, { "data": "6" }, { "data": "7" }].to_json
+
+    respond_to do |format|
+      format.html
+      format.json { render json: StocksDatatable.new(view_context, current_user, @showcolumns) }
+    end
+  end
+
+  def initial_stock
+    @showcolumns = "initial"
+    # se utiliza para mandarle al datatable el numero de columnas y en que orden se deben de acomodar
+    @column_definition = [{ "data": "0"}, { "data": "1" }, { "data": "2" }, { "data": "3" }, { "data": "4" }, { "data": "5" }, { "data": "6" }].to_json
+
+    respond_to do |format|
+      format.html
+      format.json { render json: StocksDatatable.new(view_context, current_user, @showcolumns) }
+    end
+  end
+
+  def stock_by_pointsale
+    add_breadcrumb "Existencias", :stock_by_pointsale_path
+    if current_user.usertype == "S"
+      @selected = "W-#{current_user.warehouse_id}"
+    else
+      @selected = "P-#{current_user.pointsale_id}"
+    end
+    respond_to do |format|
+      format.html
+      format.json { render json: StockByPointsaleDatatable.new(view_context, current_user, @selected) }
+    end
+
+  end
+
+  def edit_price
+    @product = @available_product.product
+  end
+
+  def update_price
+    @product = @available_product.product
+    message = "El precio del venta para el producto #{@available_product.product.name} se actualizo a $#{params[:available_product][:price_sale]} en punto de venta #{@available_product.pointsale.name}."
+
+    respond_to do |format|
+      if @available_product.update_attributes params[:available_product]
+        @available_product.audit_comment = message
+        format.html { redirect_to products_url, success: message }
+        format.json { head :no_content }
+        format.js {  flash[:success] = message }
+      else
+        format.js { render :edit_price }
+        format.json { render json: @available_product.errors,  status: :unprocessable_entity }
+      end
+    end
+  end
+
+
+  #POST /pointsales/5/available_products
+  def create
+    @available_product = AvailableProduct.new(available_product_params)
+
+    respond_to do |format|
+      if @available_products.save
+        format.html { redirect_to pointsale_available_product_url, notice: 'Se agregó un producto disponible para el punto de venta.' + Pointsale.find(pointsale_id).select("name") }
+      else
+        format.html { render pointsale_available_product_path(@available_product.pointsale_id) }
+      end
+    end
+  end
+
+
+  def updateStock
+    min = params[:stock_min]
+    max = params[:stock_max]
+
+    respond_to do |format|
+      if current_user.usertype == 'S'
+        WarehouseStock.where(id: params[:ids]).update_all(:stock_min => min, :stock_max => max)
+      else
+        AvailableProduct.where(id: params[:ids]).update_all(:stock_min => min, :stock_max => max)
+      end
+      format.json { head :ok }
+    end
+  end
+
+  def initialStock
+    respond_to do |format|
+      if current_user.usertype == 'S'
+        WarehouseStock.where(id: params[:ids]).update_all(:stock => params[:stock])
+      else
+        AvailableProduct.where(id: params[:ids]).update_all(:stock => params[:stock])
+      end
+      format.json { head :ok }
+    end
+  end
+
+  def print_stock
+    respond_to do |format|
+      location = params[:location]
+      category = params[:category]
+      sub_category =  params[:sub_category]
+      search =  params[:search]
+      size = params[:size].to_i
+      location_id = params[:location][2, params[:location].length] if !location.blank?
+
+      if ( current_user.usertype == 'S' && location.blank? ) || ( !location.blank? && location.first == 'W' )
+        stock = WarehouseStock.activos.includes(:warehouse, :categories).where(:warehouse_id => (location_id.blank? ? current_user.warehouse_id : location_id)).where('stock > 0')
+      elsif ( current_user.usertype != 'S' && location.blank? ) || ( !location.blank? && location.first == 'P' )
+        stock = AvailableProduct.activos.includes(:pointsale, :categories).where(:pointsale_id => (location_id.blank? ? current_user.pointsale_id : location_id) ).where('stock > 0')
+      end
+
+      if sub_category.present?
+        stock = stock.joins(:categories).where('categories.id = ?', sub_category)
+      elsif category.present?
+        subs_ids = Category.activos.where(:parent_id => category).pluck(:id)
+        stock = stock.joins(:categories).where('categories.id IN (?)', subs_ids)
+      end
+
+      if size != 0
+        stock = stock.limit(size)
+      end
+
+      unless search.blank?
+        stock = stock.where("products.sku ilike :search or products.name ilike :search", search: "%#{search}%")
+      end
+
+      if location.first == 'W'
+        location = Warehouse.find(location_id)
+      elsif location.first == 'P'
+        location = Pointsale.find(location_id)
+      end
+
+      format.pdf do
+        render pdf: "existencias",
+          template: "available_products/stock_by_pointsale.pdf.erb",
+          layout: "pdf_base.pdf.erb",
+          locals: { :stock => stock, :location => location, :category => category, :sub_category => sub_category, :search => search },
+          show_as_html: params.key?('debug'),
+          page_size: 'Letter'
+      end
+    end
+  end
+
+  def total_products_by_pointsale
+      location = params[:location]
+      category = params[:category]
+      sub_category =  params[:sub_category]
+      location_id = params[:location][2, params[:location].length] if !location.blank?
+
+      if location.first == 'P'
+        stock = AvailableProduct.activos.where(:pointsale_id => location_id )
+      elsif location.first == 'W'
+        stock = WarehouseStock.activos.where(:warehouse_id => location_id )
+      end
+
+      if sub_category.present?
+        stock = stock.joins(:categories).where('categories.id = ?', sub_category)
+      elsif category.present?
+        subs_ids = Category.activos.where(:parent_id => category).pluck(:id)
+        stock = stock.joins(:categories).where('categories.id IN (?)', subs_ids)
+      end
+
+      total_prods = stock.sum(:stock)
+      render :json => total_prods
+  end
+
+  # DELETE //pointsales/5/available_products/1
+  def destroy
+    @available_product.destroy
+    # respond_to do |format|
+    #   format.html { redirect_to foods_url, notice: 'Food was successfully destroyed.' }
+    #   format.json { head :no_content }
+    # end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def get_products
+      @products = Product.activos_children
+    end
+    # Use callbacks to share common setup or constraints between actions.
+    def set_available_product
+      @available_product = AvailableProduct.find(params[:available_product_id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def available_product_params
+      params.require(:available_product).permit(:pointsale_id, :product_id, :stock_min, :stock_max, :stock, :ids, :price_sale)
+    end
+
+end

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 234 - 0
app/controllers/cash_outs_controller.rb


+ 132 - 0
app/controllers/cash_registers_controller.rb

@@ -0,0 +1,132 @@
+class CashRegistersController < ApplicationController
+  ##--- Abilities
+  load_and_authorize_resource
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :cash_registers_path
+  add_breadcrumb "Nueva Caja registradora " , :new_cash_register_path, only: :new
+  add_breadcrumb "Detalle de la Caja registradora "  , :cash_register_path, only: :show
+  add_breadcrumb "Editar Caja registradora "  , :edit_cash_register_path, only: :edit
+
+  before_action :set_cash_register, only: [:show, :edit, :update, :destroy]
+
+  #before_action :get_pointsale, only: [:index]
+
+  # GET /cash_registers
+  # GET /cash_registers.json
+  def index
+    if current_user.usertype == 'A'
+      @cash_registers = CashRegister.vigentes.includes(:pointsale)
+    else
+      @cash_registers = CashRegister.includes(:pointsale).where(:pointsale_id => current_user.pointsale_id ).vigentes
+    end
+  end
+
+  # GET /cash_registers/1
+  # GET /cash_registers/1.json
+  def show
+  end
+
+  # GET /cash_registers/new
+  def new
+    @cash_register = CashRegister.new
+  end
+
+  # GET /cash_registers/1/edit
+  def edit
+  end
+
+  # POST /cash_registers
+  # POST /cash_registers.json
+  def create
+    @cash_register = CashRegister.new(cash_register_params)
+    @cash_register.pointsale_id = @current_user.pointsale_id
+    respond_to do |format|
+      if @cash_register.save
+          format.html { redirect_to cash_registers_url, success: "La caja " + @cash_register.name + " fue registrada." }
+        format.json { render :show, status: :created, location: @cash_register }
+      else
+        format.html { render :new }
+        format.json { render json: @cash_register.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /cash_registers/1
+  # PATCH/PUT /cash_registers/1.json
+  def update
+    respond_to do |format|
+      cash_register_opened = OpenCashRegister.where(:cash_register_id => @cash_register.id, :status => 0).any?
+
+      if cash_register_opened == true && params[:cash_register][:status] == 'inactive'
+        @cash_register.errors.add(:status, "No se puede desactivar una caja abierta.")
+        format.html { render :edit }
+        format.json { render json: @cash_register.errors, status: :unprocessable_entity }
+      else
+        @cash_register.update(cash_register_params)
+        format.html { redirect_to cash_registers_url,   success: "La caja " + @cash_register.name + " fue modificada." }
+        format.json { render :show, status: :ok, location: @cash_register }
+      end
+    end
+  end
+
+  def update_status
+      cash_register = CashRegister.find(params[:cash_register_id])
+      puts cash_register.active?
+      if cash_register.active?
+        cash_register.status = 2
+      elsif cash_register.inactive?
+        cash_register.status = 1
+      end
+      respond_to do |format|
+        if cash_register.save(:validate => false)
+            format.html { redirect_to cash_registers_url, warning: "La caja registradora " + cash_register.name + " fue "+ (cash_register.active? ? "activada" : "desactivada")+"." }
+          # format.json { render :show, status: :ok, location: @pointsale }
+          format.json { head :no_content }
+        else
+          format.html { redirect_to cash_registers_url }
+          format.json { render json: @cash_register.errors, status: :unprocessable_entity }
+        end
+      end
+  end
+
+  # DELETE /cash_registers/1
+  # DELETE /cash_registers/1.json
+  def destroy
+    #@cash_register.destroy
+    respond_to do |format|
+      cash_register_opened = OpenCashRegister.where(:cash_register_id => @cash_register.id, :status => 0).any?
+      if cash_register_opened == true
+        @cash_register.errors.add(:status, "No se puede eliminar una caja abierta.")
+        format.html { render :edit }
+        format.json { render json: @cash_register.errors, status: :unprocessable_entity }
+      else
+        if @cash_register.main == 'yes'
+          @cash_register.errors.add(:base, "No se puede eliminar la caja principal.")
+          format.html { redirect_to cash_registers_url,  warning: "La caja " + @cash_register.name + " no se puede eliminar por ser la principal." }
+          format.json { render json: @cash_register.errors, status: :unprocessable_entity }
+        else
+          @cash_register.update_attributes(:status => 0)
+          format.html { redirect_to cash_registers_url,  warning: "La caja " + @cash_register.name + " fue eliminada." }
+          format.json { head :no_content }
+        end
+      end
+    end
+  end
+
+  def get_cash_fund
+    last_cash_out = CashRegister.find(params[:cash_register_id]).cash_outs.last
+    render :json => last_cash_out.present? ? last_cash_out.cash_fund : 0
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_cash_register
+      @cash_register = CashRegister.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def cash_register_params
+      params.require(:cash_register).permit(:name, :description, :pointsale_id, :status)
+    end
+end

+ 286 - 0
app/controllers/cash_registers_moves_controller.rb

@@ -0,0 +1,286 @@
+class CashRegistersMovesController < ApplicationController
+  before_action :set_cash_registers_move, only: [:show, :edit, :update, :destroy]
+
+
+  # GET /cash_registers_moves/new
+  def new
+    @cash_registers_move = CashRegistersMove.new
+
+    if params[:sale].present?
+      @sale = Sale.find(params[:sale])
+      @credito = Credit.where(:customer_id => @sale.customer_id, :status => "0").sum(:rest)
+      @cash_registers_move.open_cash_register_id = @sale.open_cash_register_id
+    elsif params[:products_return].present?
+      @products_return = ProductsReturn.find(params[:products_return])
+    end
+
+    @cash_id = PaymentMethod.find_by(:isCash => 1).id
+
+    if @sale.present?
+      if @sale.saletype == 'reserved'
+        if @sale.parcial?
+          # esto quiere decir que la venta ya tiene un anticipo, se debe mostrar modal
+          # para abonar al apartado o liquidarlo
+          @payments = CashRegistersMove.where(:sale_id => @sale.id, :status => 1)
+          render 'liquidate_reserved_sale'
+        else
+          # registrar apartado
+          @min_quantity_reserve = ((@pos_config.reserve_sale_percent / 100) * @sale.total).round
+          @cash_registers_move.quantity = @min_quantity_reserve
+          render 'new_sale_reserve'
+        end
+      else
+        # Nueva venta
+        render 'new'
+      end
+    elsif @products_return.present?
+      @open_cash_register_id = current_user.pointsale.get_open_cash_register.id
+      render 'products_return_payment'
+    end
+  end
+
+  # POST /cash_registers_moves
+  # POST /cash_registers_moves.json
+  def create
+    @cash_registers_move = CashRegistersMove.new(cash_registers_move_params)
+    @sale = Sale.find(params[:cash_registers_move][:sale_id]) if params[:cash_registers_move][:sale_id].present?
+    @purchase = Purchase.find(params[:cash_registers_move][:purchase_id]) if params[:cash_registers_move][:purchase_id].present?
+    credit = Credit.find_by_sale_id(@sale.id) if params[:cash_registers_move][:sale_id].present?
+    @products_return = ProductsReturn.find(params[:cash_registers_move][:products_return_id]) if params[:cash_registers_move][:products_return_id].present?
+
+    respond_to do |format|
+      if @sale.present?
+        # calcular cambio cuando aplica.
+        @cash_registers_move.calculate_quantities
+
+        @cash_registers_move.move_type = :ingreso
+
+        if @sale.cash?
+          @sale.update_attributes(:status => :paid)
+          @cash_registers_move.concept = :sale
+        elsif @sale.reserved?
+          @cash_registers_move.status = :inactive
+          @cash_registers_move.concept = :reserved_payment
+          @cash_registers_move.open_cash_register_id = Pointsale.find(current_user.pointsale_id).open_cash_registers.where("open_cash_registers.status = 0").last.id
+        end
+
+        if credit.present?
+          save_when_is_credit(credit)
+          @cash_registers_move.open_cash_register_id = Pointsale.find(current_user.pointsale_id).open_cash_registers.where("open_cash_registers.status = 0").last.id
+        end
+
+        message = "movimiento de efectivo por venta con folio #{@cash_registers_move.sale.sale_code}"
+        @cash_registers_move.audit_comment = message
+
+        if @cash_registers_move.save
+          format.js { flash[:success] = message }
+        else
+          format.js
+          format.json { render json: @cash_registers_move.errors, status: :unprocessable_entity }
+        end
+      elsif @purchase.present?
+        @cash_registers_move.move_type = :egreso
+        @cash_registers_move.concept = :purchase
+        @cash_registers_move.status = :inactive
+
+        message = "movimiento de efectivo por compra con folio #{@cash_registers_move.purchase.purchase_code}"
+        @cash_registers_move.audit_comment = message
+
+        if @cash_registers_move.save
+          format.html { redirect_to purchases_path, success: 'Movimiento de efectivo realizado correctamente.' }
+        else
+          format.html { render :new }
+          format.json { render json: @cash_registers_move.errors, status: :unprocessable_entity }
+        end
+      elsif @products_return.present?
+        # calcular cambio cuando aplica.
+        @cash_registers_move.calculate_quantities
+        @cash_registers_move.move_type = :ingreso
+        @cash_registers_move.status = :inactive
+
+        message = "movimiento de efectivo por devolución con folio #{@cash_registers_move.products_return.return_code}"
+        @cash_registers_move.audit_comment = message
+        if @cash_registers_move.save
+          format.js { flash[:success] = message }
+        else
+          format.js
+          format.json { render json: @cash_registers_move.errors, status: :unprocessable_entity }
+        end
+      end
+    end
+  end
+
+
+  # DELETE /cash_registers_moves/1
+  # DELETE /cash_registers_moves/1.json
+  def destroy
+    @cash_registers_move.destroy
+    respond_to do |format|
+      format.json { head :no_content }
+      format.js { head :ok }
+    end
+  end
+
+  def confirm_payments
+    respond_to do |format|
+      @sale = Sale.find(params[:sale_id]) if params[:sale_id].present?
+      @products_return = ProductsReturn.find(params[:products_return_id]) if params[:products_return_id].present?
+
+      if @sale.present?
+        if CashRegistersMove.where(:sale_id => @sale.id).update_all(:status => 1)
+          @sale.update_attributes(:status => :paid)
+          message = "Venta registrada correctamente."
+        end
+      elsif @products_return.present?
+        CashRegistersMove.where(:products_return_id => @products_return.id).update_all(:status => 1)
+        message = "Devolución registrada correctamente."
+      end
+
+      format.js { flash[:success] = message }
+    end
+  end
+
+  #funcion que se manda llamar cuando se clickea en 'Abonar' al crear apartado
+  def confirm_reserve
+    respond_to do |format|
+      @sale = Sale.find(params[:sale_id])
+      if CashRegistersMove.where(:sale_id => @sale.id).update_all(:status => 1)
+        @sale.update_attributes(:status => :parcial)
+        format.js { flash[:success] = "Apartado agregado al cliente #{@sale.customer.nick_name} correctamente." }
+      end
+    end
+  end
+
+  def add_payments_to_reserve
+    respond_to do |format|
+      @sale = Sale.find(params[:sale_id])
+      @new_moves_array = Array.new
+      @new_moves = CashRegistersMove.where(:sale_id => @sale.id, :status => 0)
+      @new_moves.each do |move|
+        @new_moves_array << move.id
+      end
+      if @new_moves.update_all(:status => 1)
+        # if @sale.reserve_debt == 0
+        if @sale.reserve_debt <= 0
+          @sale.update_attributes(:status => :paid)
+        end
+        format.js { flash[:success] = "Abono al apartado #{@sale.sale_code} agregado correctamente." }
+      end
+    end
+  end
+
+  def delete_credit_payment
+    respond_to do |format|
+      payment = CreditPayment.find(params[:credit_payment_id])
+      cashmoves = CashRegistersMove.where(:credit_payment_id => payment.id)
+
+      openregister = OpenCashRegister.find_by(:id => cashmoves.first.open_cash_register_id)
+      if openregister.open?
+        payment.update_attributes(:status => :cancelled)
+        #
+        credit = Credit.find(params[:credit])
+        sale = Sale.find(credit.sale_id)
+        sum = payment.quantity + credit.rest
+        if(credit.total < sum)
+          credit.update_attributes(:rest => credit.total)
+          sale.update_attributes(:status => :notpaid)
+        else
+          credit.update_attributes(:rest => sum)
+          if sale.total = credit.rest
+            sale.update_attributes(:status => :notpaid)
+          end
+        end
+        cashmoves.each do |move|
+          new_cash_move = move.dup
+          new_cash_move.move_type = :egreso
+          new_cash_move.save
+        end
+      format.html { redirect_to customer_sales_path(:customer_id => payment.customer_id), notice: "Movimiento de efectivo realizado correctamente." }
+      format.js { flash[:success] = "Movimiento de efectivo realizado correctamente." }
+      else
+        format.html { redirect_to customer_sales_path(:customer_id => payment.customer_id), notice: "No se pudo realizar el movimiento." }
+        format.js { flash[:success] = "NO se pudo realizar el movimiento." }
+      end
+    end
+  end
+
+  def add_quick_payment
+    # type 1 es abono a credito, 2 es abono a apartado
+    @type = params[:type].to_i
+    if @type == 1
+      debtors_ids = Credit.debtors
+      @customers = Customer.find(debtors_ids.map { |m| m[0] })
+    else
+      @sales = Pointsale.find(current_user.pointsale_id).sales.where(:saletype => 2, :status => 3).select(:customer_id).distinct
+      @customers = Customer.find(@sales.map { |m| m.customer_id })
+    end
+  end
+
+  def create_quick_debt_payment
+    @customer = Customer.find(params[:customer_id])
+    @sale = @customer.sales.where('saletype = 0 and status != 1 and status != 2').order('date_sale DESC').last
+    redirect_to new_cash_registers_move_path(:sale => @sale, :customer => @customer)
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_cash_registers_move
+      @cash_registers_move = CashRegistersMove.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def cash_registers_move_params
+      params.require(:cash_registers_move).permit(:payment_method_id, :quantity, :open_cash_register_id, :sale_id, :cardnumber, :ticket, :received, :change, :products_return_id)
+    end
+
+    def save_when_is_credit(credit)
+      abono = @cash_registers_move.quantity
+      @credit_payment = CreditPayment.new
+      @credit_payment.credit_id = credit.id
+      @credit_payment.customer_id = credit.customer_id
+      @credit_payment.pointsale_id = credit.pointsale_id
+      @credit_payment.status = :active
+      @credit_payment.date_payment = Date.today
+      @credit_payment.user_id = current_user.id
+
+      if @credit_payment.save
+        @cash_registers_move.credit_payment_id = @credit_payment.id
+        @cash_registers_move.concept = :credit_payment
+        @debt = Credit.where(:customer_id => credit.customer_id).sum(:rest)
+        if abono >= credit.rest #si se paga más del adeudo de este crédito
+          abono = abono - credit.rest
+
+          # @debt = credit.rest
+          credit.update_attributes(:rest => 0)
+          @sale.update_attributes(:status => :paid)
+
+          while abono > 0 do
+            nextcredit = Credit.where("customer_id = #{credit.customer_id} and rest > 0").order("sale_id ASC")
+            if nextcredit.first.present?
+              if abono <= nextcredit.first.rest
+                nextcredit.first.update_attributes(:rest => nextcredit.first.rest - abono)
+                sale = Sale.find_by_id(nextcredit.first.sale_id)
+                sale.update_attributes(:status => :parcial)
+                abono = abono - nextcredit.first.rest
+              else
+                abono = abono - nextcredit.first.rest
+                # @debt = nextcredit.first.rest
+                nextcredit.first.update_attributes(:rest => 0)
+                sale = Sale.find_by_id(nextcredit.sale_id)
+                sale.update_attributes(:status => :paid)
+              end
+            else
+              abono = 0 #si ya no hay deuda
+            end
+          end
+
+        else
+          @cash_registers_move.quantity = abono
+          abono = credit.rest - abono
+          credit.update_attributes(:rest => abono)
+          @sale.update_attributes(:status => :parcial)
+        end
+      end
+    end
+end
+

+ 122 - 0
app/controllers/categories_controller.rb

@@ -0,0 +1,122 @@
+class CategoriesController < ApplicationController
+  ##--- Abilities
+  load_and_authorize_resource
+
+	##--- Breadcrum_rails
+	add_breadcrumb I18n.t("breadcrumbs." + controller_name), :categories_path
+	add_breadcrumb "Nueva Línea de producto " , :new_category_path, only: :new
+	add_breadcrumb "Detalle de la Línea de producto"  , :category_path, only: :show
+	add_breadcrumb "Editar Línea de producto"  , :edit_category_path, only: :edit
+
+	before_action :set_category, only: [:show, :edit, :update, :destroy]
+	before_action :get_filters, only: [:index, :edit, :new]
+
+	# GET /categories
+	# GET /categories.json
+  def index
+    @categories = Category.vigentes.includes(:children, :products).where("parent_id = 0").order("category ASC")
+  end
+
+  # GET /categories/1
+  # GET /categories/1.json
+  def show
+  end
+
+  # GET /categories/new
+  def new
+    @category = Category.new
+  end
+
+  # GET /categories/1/edit
+  def edit
+  end
+
+  # POST /categories
+  # POST /categories.json
+  def create
+    @category = Category.new(category_params)
+    @notice_title = 'Registro creado';
+    @category.audit_comment = "La línea de producto  " + @category.category + " fue creada."
+    respond_to do |format|
+
+      if @category.save
+        format.html { redirect_to categories_url, success: "La línea de producto  " + @category.category + " fue creada." }
+        format.json { render :show, status: :created, location: @category }
+      else
+        format.html { render :new }
+        format.json { render json: @category.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /categories/1
+  # PATCH/PUT /categories/1.json
+  def update
+    respond_to do |format|
+      @category.audit_comment = 'La línea de producto ' + params[:category][:category] + ' fue modificada.'
+      if @category.update(category_params)
+        format.html { redirect_to categories_url, success: 'La línea de producto ' + @category.category + ' fue modificada.' }
+        format.json { render :show, status: :ok, location: @category }
+      else
+        format.html { render :edit }
+        format.json { render json: @category.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  def update_status
+    category = Category.find(params[:category_id])
+    puts category.active?
+    if category.active?
+      category.status = 2
+    elsif category.inactive?
+      category.status = 1
+    end
+    respond_to do |format|
+      if category.save(:validate => false)
+          format.html { redirect_to categories_url, warning: "La línea de producto " + category.category + " fue "+ (category.active? ? "activada" : "desactivada")+"." }
+        # format.json { render :show, status: :ok, location: @category }
+        format.json { head :no_content }
+      else
+        format.html { redirect_to categories_url }
+        format.json { render json: @category.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+  # DELETE /categories/1
+  # DELETE /categories/1.json
+  def destroy
+    # Borrar la relacion entre la categoria a "eliminar" y los productos que pertenecen a ella
+    @category.products.clear
+    @category.audit_comment = "La línea de producto " + @category.category + " fue eliminada."
+    respond_to do |format|
+      if @category.update_attributes(:status => 0)
+        format.html { redirect_to categories_url, warning: "La línea de producto " + @category.category + " fue eliminada." }
+        format.json { head :no_content }
+       else
+         format.html { render :edit }
+         format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+       end
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_category
+      @category = Category.find(params[:id])
+    end
+
+		def get_filters
+			if params[:current_page].blank?
+				@current_page = 1
+			else
+				@current_page = params[:current_page]
+			end
+      @filter = params[:filter]
+		end
+
+		# Never trust parameters from the scary internet, only allow the white list through.
+		def category_params
+			params.require(:category).permit(:category, :description, :parent_id, :status)
+		end
+end

+ 140 - 0
app/controllers/commissions_controller.rb

@@ -0,0 +1,140 @@
+class CommissionsController < ApplicationController
+  ##--- Abilities
+  load_and_authorize_resource
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :commissions_path, only: :show
+
+  before_action :set_commission, only: [:show, :edit, :update, :destroy]
+  before_action :get_filters, only: [:index, :show, :edit, :new]
+
+  # GET /commissions
+  # GET /commissions.json
+  def index
+    if current_user.usertype == 'A'
+      @commissions = Commission.all.includes(:pointsale, :user)
+    else
+      @commissions = Commission.includes(:pointsale, :user).where(:pointsale_id => current_user.pointsale_id)
+    end
+  end
+
+  # GET /commissions/1
+  # GET /commissions/1.json
+  def show
+    sellers_commissions_ids = SalesSellercommission.where('sellerscommission_id in (?)', @commission.sellerscommissions.pluck(:id)).pluck(:sale_id)
+    @sales = Sale.where('sales.id in (?)', sellers_commissions_ids).joins(:seller).order('sellers.name')
+  end
+
+  # GET /commissions/new
+  def new
+    @sellers = Seller.where('id IN (?)' , params[:sellers_ids])
+    @initial_date = params[:initial_date]
+    @final_date = params[:final_date]
+    commission_total = 0
+    @sellers.each do |seller|
+      commission_total += seller.get_sales_by_period(@initial_date, @final_date, 'commission').sum(:total)
+    end
+    @commission = Commission.new
+    @commission.sellerscommissions.new
+    @commission.commission_total = commission_total
+    @commission.pointsale_id = params[:pointsale_id]
+    @commission.initial_date = @initial_date
+    @commission.final_date = @final_date
+  end
+
+  # GET /commissions/1/edit
+  def edit
+  end
+
+  # POST /commissions
+  # POST /commissions.json
+  def create
+    @commission = Commission.new(commission_params)
+    @commission.user_id = current_user.id
+    respond_to do |format|
+      @commission.audit_comment = "Comisiones del #{initial_date} al #{final_date} generadas"
+      if @commission.save
+        @commission.sellerscommissions.each do |seller_commission|
+          sales_by_period = seller_commission.seller.get_sales_by_period(@commission.initial_date, @commission.final_date, 'commission').pluck(:id)
+          sales_by_period.each do |sale |
+            sales_sellercom = SalesSellercommission.new
+            sales_sellercom.sellerscommission_id = seller_commission.id
+            sales_sellercom.sale_id = sale
+            sales_sellercom.save
+          end
+        end
+        format.html { redirect_to commissions_path, notice: 'Pago de comisión registrado con éxito.' }
+        format.json { render :show, status: :created, location: @commission }
+      else
+        format.html { render :new }
+        format.json { render json: @commission.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /commissions/1
+  # PATCH/PUT /commissions/1.json
+  def update
+    respond_to do |format|
+      if @commission.update(commission_params)
+        format.html { redirect_to @commission, notice: 'Commission was successfully updated.' }
+        format.json { render :show, status: :ok, location: @commission }
+      else
+        format.html { render :edit }
+        format.json { render json: @commission.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /commissions/1
+  # DELETE /commissions/1.json
+  def destroy
+    @commission.destroy
+    respond_to do |format|
+      format.html { redirect_to commissions_url, notice: 'Commission was successfully destroyed.' }
+      format.json { head :no_content }
+    end
+  end
+
+  def sellers_for_commissions
+  end
+
+  def generate_commissions
+
+  end
+
+  def find_sellers_by_date
+    initial_date = DateTime.parse(params[:initial_date])
+    final_date = DateTime.parse(params[:final_date])
+
+    sales = Pointsale.find(params[:pointsale_id]).sales.where(:date_sale => initial_date..final_date).pluck(:id)
+    commissions_paid = SalesSellercommission.where('sale_id IN (?)', sales).pluck(:sale_id)
+
+    to_pay = sales - commissions_paid
+
+    sellers = Sale.where('sales.id IN (?)', to_pay).joins(:seller).distinct(:seller_id).select(:seller_id, :name)
+    render :json => sellers
+  end
+
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_commission
+      @commission = Commission.find(params[:id])
+    end
+
+    def get_filters
+      if params[:current_page].blank?
+        @current_page = 1
+      else
+        @current_page = params[:current_page]
+      end
+      @filter = params[:filter]
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def commission_params
+      params.require(:commission).permit(:initial_date, :final_date, :commission_total, :pointsale_id, :user_id, sellerscommissions_attributes: [:seller_id, :num_sales, :num_products, :sales_total, :commission_calculated, :commission_paid, :commission_percent])
+    end
+
+end

+ 0 - 0
app/controllers/concerns/.keep


+ 140 - 0
app/controllers/customers_controller.rb

@@ -0,0 +1,140 @@
+class CustomersController < ApplicationController
+  ##--- Abilities
+  load_and_authorize_resource
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :customers_path, only: [:index, :new, :show, :edit]
+  add_breadcrumb "Clientes deudores", :debtors_path, only: [:debtors, :customer_sales]
+  add_breadcrumb "Detalle de ventas de clientes deudores", :customer_sales_path, only: :customer_sales
+  add_breadcrumb "Nuevo " + I18n.t("breadcrumbs." + controller_name).singularize, :new_customer_path, only: :new
+  add_breadcrumb "Detalle del " + I18n.t("breadcrumbs." + controller_name).singularize , :customer_path, only: :show
+  add_breadcrumb "Editar " + I18n.t("breadcrumbs." + controller_name).singularize , :edit_customer_path, only: :edit
+
+  before_action :set_customer, only: [:show, :edit, :update, :destroy]
+  before_action :get_filters, only: [:index, :show, :edit, :new, :debtors, :customer_sales]
+
+  # GET /customers
+  # GET /customers.json
+  def index
+    @customers = Customer.includes(:billing_information).vigentes.where.not(id: 1)
+  end
+
+  def debtors
+    @customers = Customer.includes(:sales, :credits).vigentes
+  end
+
+  def customer_sales
+    @custom = Customer.find(params[:customer_id])
+    @sales = Sale.where(customer_id: @custom.id, saletype: 0).order('created_at desc')
+    @credit = Credit.where(customer_id: @custom.id)
+  end
+
+  # apartados por cliente
+  def reserved_sales_by_customer
+    respond_to do |format|
+      @customer = Customer.find(params[:customer_id])
+      @sales = Sale.where(customer_id: @customer.id, saletype: 2, status: 3)
+      format.js
+    end
+  end
+
+  # GET /customers/1
+  # GET /customers/1.json
+  def show
+    @sales = Sale.where(customer_id: @customer.id, saletype: 0).order('created_at desc')
+    @credit = Credit.where(customer_id: @customer.id)
+  end
+
+  # GET /customers/new
+  def new
+    @customer = Customer.new
+    @customer.credit = false
+  end
+
+  # GET /customers/1/edit
+  def edit
+  end
+
+  # POST /customers
+  # POST /customers.json
+  def create
+    @customer = Customer.new(customer_params)
+    @customer.audit_comment = "El cliente " + @customer.nick_name + " fue registrado."
+    respond_to do |format|
+      if @customer.save
+        format.html { redirect_to customers_url, success: "El cliente " + @customer.nick_name + " fue registrado." }
+        format.json { render :show, status: :created, location: @customer }
+      else
+        format.html { render :new }
+        format.json { render json: @customer.errors, status: :unprocessable_entity }
+      end
+      format.js
+    end
+  end
+
+  # PATCH/PUT /customers/1
+  # PATCH/PUT /customers/1.json
+  def update
+    respond_to do |format|
+      @customer.audit_comment = "El cliente " + params[:customer][:nick_name] + " fue registrado."
+      if @customer.update(customer_params)
+        format.html { redirect_to customers_url, success: "El cliente " + @customer.nick_name + " fue modificado." }
+        format.json { render :show, status: :ok, location: @customer }
+      else
+        format.html { render :edit }
+        format.json { render json: @customer.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  def update_status
+    customer = Customer.find(params[:customer_id])
+    if customer.active?
+      customer.status = 2
+    elsif customer.inactive?
+      customer.status = 1
+    end
+    respond_to do |format|
+      if customer.save(validate: false)
+        format.html { redirect_to customers_url, warning: "El cliente " + customer.nick_name + " fue "+ (customer.active? ? "activado" : "desactivado") + "." }
+        format.json { head :no_content }
+      else
+        format.html { redirect_to customers_url }
+        format.json { render json: @customer.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /customers/1
+  # DELETE /customers/1.json
+  def destroy
+    respond_to do |format|
+      if @customer.update_attributes(status: :erased)
+        @customer.audit_comment = "El cliente " + @customer.nick_name + " fue dado de baja."
+        format.html { redirect_to customers_url, warning: "El cliente " + @customer.nick_name + " fue dado de baja." }
+        format.json { head :no_content }
+      end
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_customer
+      @customer = Customer.find(params[:id])
+    end
+
+  def get_filters
+    if params[:current_page].blank?
+      @current_page = 1
+    else
+      @current_page = params[:current_page]
+    end
+    @filter = params[:filter]
+  end
+
+  # Never trust parameters from the scary internet, only allow the white list through.
+  def customer_params
+    params.require(:customer).permit(:nick_name, :phone, :email, :credit, :credit_limit, :time_limit, :notes, :status, billing_information_attributes:[ :id, :name, :rfc, :address, :num_ext, :num_int, :zipcode, :state_id, :county_id, :city, :suburb], contact_attributes:[ :id, :name, :last_name, :phone, :email ] )
+
+  end
+end

+ 67 - 0
app/controllers/dashboard_controller.rb

@@ -0,0 +1,67 @@
+class DashboardController < ApplicationController
+  def index
+    @open_cash = false
+  	if current_user.usertype == 'A'
+      # obtener ingresos por punto de venta
+      @incomings = ActionController::Base.helpers.sanitize(CashRegistersMove.incomings_per_period('day'))
+      # obtener ranking de productos y prepararlo para mandarlo al view
+      @product_ranking = Array.new
+      most_selled_products = Product.most_selled_products('week', current_user)
+      most_selled_products.each do |product, quantity|
+        obj = {:product => product, :quantity => quantity}
+       @product_ranking << obj
+      end
+      @product_ranking =  ActionController::Base.helpers.sanitize(@product_ranking.to_json)
+
+  	elsif current_user.usertype == 'G'
+      # obtener ranking de vendedores y prepararlo para mandarlo al view
+      @sellers_ranking = Array.new
+      sellers = Seller.get_ranking('week', current_user.pointsale)
+      sellers.each do |seller, total|
+        obj = {:seller => seller, :total => total}
+        @sellers_ranking << obj
+      end
+      @sellers_ranking = ActionController::Base.helpers.sanitize(@sellers_ranking.to_json)
+      # obtener ranking de productos y prepararlo para mandarlo al view
+      @product_ranking = Array.new
+      most_selled_products = Product.most_selled_products('week', current_user)
+      most_selled_products.each do |product, quantity|
+        obj = {:product => product, :quantity => quantity}
+       @product_ranking << obj
+      end
+      @product_ranking =  ActionController::Base.helpers.sanitize(@product_ranking.to_json)
+    elsif current_user.usertype == 'C'
+      opened_cash_user = OpenCashRegister.where(:user_id => current_user.id, :status => 0).any?
+      @open_cash = true if opened_cash_user == false
+    end  
+  end
+
+  def get_chart_data_for_dashboard
+    if params[:chart] == 'incomings'
+      puts "incomings"
+      incomings = CashRegistersMove.incomings_per_period(params[:period])
+      @incomings = ActionController::Base.helpers.sanitize(incomings)
+      render :text =>  @incomings     
+    elsif params[:chart] == 'products'
+      puts "products"
+      @product_ranking = Array.new
+      most_selled_products = Product.most_selled_products(params[:period], current_user)
+      most_selled_products.each do |product, quantity|
+        obj = {:product => product, :quantity => quantity}
+       @product_ranking << obj
+      end
+      @product_ranking =  ActionController::Base.helpers.sanitize(@product_ranking.to_json)  
+      render :text =>  @product_ranking    
+    elsif params[:chart] == 'sellers'
+      puts "sellers"
+      @sellers_ranking = Array.new
+      sellers = Seller.get_ranking(params[:period], current_user.pointsale)
+      sellers.each do |seller, total|
+        obj = {:seller => seller, :total => total}
+        @sellers_ranking << obj
+      end
+      @sellers_ranking = ActionController::Base.helpers.sanitize(@sellers_ranking.to_json) 
+      render :text =>  @sellers_ranking        
+    end
+  end
+end

+ 160 - 0
app/controllers/expenses_controller.rb

@@ -0,0 +1,160 @@
+class ExpensesController < ApplicationController
+  ##--- Abilities
+  load_and_authorize_resource
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :expenses_path
+  add_breadcrumb "Nuevo egreso" , :new_expense_path, only: :new
+  add_breadcrumb "Detalle del egreso" , :expense_path, only: :show
+  add_breadcrumb "Editar egreso" , :edit_expense_path, only: :edit
+
+  before_action :set_expense, only: [:show, :edit, :update, :destroy]
+  before_action :set_data, only: [:new, :create, :edit, :update]
+  # GET /expenses
+  # GET /expenses.json
+  def index
+    case current_user.usertype
+      when "A"
+        @from_pointsale = Expense.where.not('open_cash_register_id' => nil).includes(:expensesconcept).order(" id DESC ")
+        @general_expenses = Expense.where(:open_cash_register_id => nil).includes(:expensesconcept).order(" id DESC ")
+      when "G"
+        @expenses = Pointsale.find(current_user.pointsale_id).expenses.includes(:expensesconcept, :open_cash_register).order(" id DESC ")
+      when "C"
+        @expenses = Expense.where(:open_cash_register_id => current_user.get_open_cash_register.id).includes(:expensesconcept).order(" id DESC ")
+    end
+  end
+
+  # GET /expenses/1
+  # GET /expenses/1.json
+  def show
+  end
+
+  # GET /expenses/new
+  def new
+    @expense = Expense.new
+    @concept_purchase_payment = Expensesconcept.find_by(:expense_from_purchase => 1)
+    if current_user.usertype == 'C'
+      @is_cashier = true
+      @expense.open_cash_register_id = current_user.get_open_cash_register.id
+    end
+  end
+
+  # GET /expenses/1/edit
+  def edit
+  end
+
+  # POST /expenses
+  # POST /expenses.json
+  def create
+    respond_to do |format|
+      @expense = Expense.new(expense_params)
+      @concept_purchase_payment = Expensesconcept.find_by(:expense_from_purchase => 1)
+      @expense.status = :active
+
+      if current_user.usertype == "A"
+        @expense.skip_open_cash_validation =  true
+        @expense.skip_open_cash_has_money =  true
+      else
+        @expense.expense_date = Date.current
+      end
+
+      @expense.audit_comment = "Egreso por #{@expense.quantity} registrado"
+      if @expense.save
+        # gasto pago a venta
+        if @concept_purchase_payment.id == @expense.expensesconcept_id
+          if params[:purchases].present?
+            purchase = Purchase.find(params[:purchases])
+            purchase.update_attributes(:status => "paid")
+          end
+        end
+        #movimiento de efectivo
+        if current_user.usertype != "A"
+          move = CashRegistersMove.new
+          move.skip_received_validation = true
+          move.expense_id = @expense.id
+          move.open_cash_register_id = @expense.open_cash_register_id
+          move.quantity = @expense.quantity
+          move.move_type = :egreso
+          move.concept = :expense
+          move.payment_method_id = PaymentMethod.find_by(:isCash => 1).id
+          # si es pago a compra ponle el ID
+          if purchase.present?
+            move.purchase_id = purchase.id
+          end
+          move.save
+        end
+        format.html { redirect_to expenses_path, notice: 'El egreso fue creado correctamente.' }
+        format.json { render :show, status: :created, location: @expense }
+      else
+        format.html { render :new }
+        format.json { render json: @expense.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /expenses/1
+  # PATCH/PUT /expenses/1.json
+  def update
+    respond_to do |format|
+      if @expense.update(expense_params)
+        format.html { redirect_to @expense, notice: 'Expense was successfully updated.' }
+        format.json { render :show, status: :ok, location: @expense }
+      else
+        format.html { render :edit }
+        format.json { render json: @expense.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /expenses/1
+  # DELETE /expenses/1.json
+  def destroy
+    respond_to do |format|
+      if current_user.usertype == 'A'
+        @expense.skip_open_cash_validation  =  true
+      end
+
+      @expense.audit_comment = "Egreso #{@expense.expense_code} cancelado."
+      if @expense.update_attributes(:status => 0)
+        CashRegistersMove.where(:expense_id => @expense.id).each do |move|
+          new_cash_move = move.dup
+          new_cash_move.move_type = :ingreso
+          new_cash_move.skip_received_validation = true
+          new_cash_move.save
+        end
+        format.html { redirect_to expenses_url, warning: 'El Gasto fue cancelado correctamente.' }
+        format.json { head :no_content }
+      else
+        format.html { redirect_to expenses_url, warning: 'Error, intentar nuevamente.' }
+        format.json { head :no_content }
+      end
+    end
+  end
+
+  private
+    def set_data
+      @is_cashier = false
+      if current_user.usertype != "A"
+        @expenses_concepts = Array.new
+        Expensesconcept.where("status = 1 and allpoints = 'true'").each do |expense|
+          @expenses_concepts << expense
+        end
+
+        Pointsale.find(current_user.pointsale_id).expensesconcepts.activos.each do |expense|
+          @expenses_concepts << expense
+        end
+        @expenses_concepts.uniq!
+      else
+        @expenses_concepts = Expensesconcept.activos
+      end
+    end
+    # Use callbacks to share common setup or constraints between actions.
+    def set_expense
+      @expense = Expense.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def expense_params
+      params.require(:expense).permit(:expensesconcept_id, :open_cash_register_id, :cash_registers_move_id, :quantity, :status, :purchases, :observations, :expense_date, :expense_code)
+    end
+end

+ 119 - 0
app/controllers/expensesconcepts_controller.rb

@@ -0,0 +1,119 @@
+class ExpensesconceptsController < ApplicationController
+	##--- Abilities
+	load_and_authorize_resource
+
+	##--- Breadcrum_rails
+	add_breadcrumb I18n.t("breadcrumbs." + controller_name), :expensesconcepts_path
+	add_breadcrumb "Nuevo Concepto de egreso" , :new_expensesconcept_path, only: :new
+	add_breadcrumb "Detalle del Concepto de egreso" , :expensesconcept_path, only: :show
+	add_breadcrumb "Editar Concepto de egreso" , :edit_expensesconcept_path, only: :edit
+
+	before_action :set_expensesconcept, only: [:show, :edit, :update, :destroy]
+	before_action :get_filters, only: [:index, :show, :edit, :new]
+	# before_action :get_pointsales, only: [:create, :update]
+
+	# GET /expensesconcepts
+	# GET /expensesconcepts.json
+	def index
+    @concept_purchase_payment = Expensesconcept.find_by(:expense_from_purchase => 1)
+    if current_user.usertype == 'A'
+      @expensesconcepts = Expensesconcept.vigentes
+    else
+      @expensesconcepts = Array.new
+      Expensesconcept.where("status = 1 and allpoints = 'true'").each do |expense|
+        @expensesconcepts << expense
+      end
+
+      Pointsale.find(current_user.pointsale_id).expensesconcepts.activos.each do |expense|
+        @expensesconcepts << expense
+      end
+      @expensesconcepts.uniq!
+    end
+  end
+
+
+  # GET /expensesconcepts/new
+  def new
+    @expensesconcept = Expensesconcept.new
+  end
+
+  def create
+    @expensesconcept = Expensesconcept.new(expensesconcept_params)
+		respond_to do |format|
+			@expensesconcept.audit_comment = "Concepto de egreso #{@expensesconcept.name} creado."
+			if @expensesconcept.save
+				# @expensesconcept.pointsales = @pointsales
+				format.html { redirect_to expensesconcepts_url, success: 'El concepto ' + @expensesconcept.name + ' fue creado.' }
+				format.json { render :show, status: :created, location: @expensesconcept }
+			else
+				format.html { render :new }
+				format.json { render json: @expensesconcept.errors, status: :unprocessable_entity }
+			end
+		end
+	end
+
+	# PATCH/PUT /expensesconcepts/1
+	# PATCH/PUT /expensesconcepts/1.json
+	def update
+		respond_to do |format|
+			@expensesconcept.audit_comment = "Concepto de egreso #{@expensesconcept.name} modificado."
+			if @expensesconcept.update(expensesconcept_params)
+
+				# @expensesconcept.pointsales = @pointsales
+				format.html { redirect_to expensesconcepts_url, success: 'El concepto ' + @expensesconcept.name + ' fue modificado.' }
+					format.json { render :show, status: :ok, location: @expensesconcept }
+			else
+				format.html { render :edit }
+				format.json { render json: @expensesconcept.errors, status: :unprocessable_entity }
+			end
+		end
+	end
+
+	# DELETE /expensesconcepts/1
+	# DELETE /expensesconcepts/1.json
+	def destroy
+		#@expensesconcept.destroy
+		respond_to do |format|
+			@expensesconcept.audit_comment = "Concepto de egreso #{@expensesconcept.name} eliminado."
+			if @expensesconcept.update_attributes(:status => 0)
+						format.html { redirect_to expensesconcepts_url, warning: "El concepto " + @expensesconcept.name + " fue eliminado." }
+						format.json { head :no_content }
+			else
+				format.html { render :edit }
+				format.json { render json: @expensesconcept.errors, status: :unprocessable_entity }
+			end
+		end
+	end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_expensesconcept
+      @expensesconcept = Expensesconcept.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def expensesconcept_params
+      params.require(:expensesconcept).permit(:name, :description, :allpoints, :status, :pointsale_ids => [])
+    end
+
+    def get_filters
+			if params[:current_page].blank?
+				@current_page = 1
+			else
+				@current_page = params[:current_page]
+			end
+			@filter = params[:filter]
+		end
+
+    def get_pointsales
+        unless params[:expensesconcept][:pointsale_ids].nil? && params[:expensesconcept][:pointsale_ids].empty?
+            ## Issue arreglo primer elemento en blanco ...
+            params[:expensesconcept][:pointsale_ids] = params[:expensesconcept][:pointsale_ids].delete_if{ |x| x.empty? }
+            @pointsales = params[:expensesconcept][:pointsale_ids].map do |k|
+                Pointsale.find(k)
+            end
+        else
+            @pointsales = []
+        end
+    end
+end

+ 76 - 0
app/controllers/open_cash_registers_controller.rb

@@ -0,0 +1,76 @@
+class OpenCashRegistersController < ApplicationController
+  before_action :set_open_cash_register, only: [:show, :edit, :update, :destroy]
+
+  # GET /open_cash_registers
+  # GET /open_cash_registers.json
+  def index
+    @open_cash_registers = OpenCashRegister.all
+  end
+
+  # GET /open_cash_registers/1
+  # GET /open_cash_registers/1.json
+  def show
+  end
+
+  # GET /open_cash_registers/new
+  def new
+
+  end
+
+  # GET /open_cash_registers/1/edit
+  def edit
+  end
+
+  # POST /open_cash_registers
+  # POST /open_cash_registers.json
+  def create
+    @open_cash_register = OpenCashRegister.new(open_cash_register_params)
+    @open_cash_register.status = "open"
+    @open_cash_register.user_id = current_user.id
+    respond_to do |format|
+      @open_cash_register.audit_comment = "Caja #{@open_cash_register.cash_register.name} abierta con #{@open_cash_register.initial_cash}"
+      if @open_cash_register.save
+        format.json { head :no_content }
+        format.js
+      else
+        format.html { render :new }
+        format.json { render json: @open_cash_register.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /open_cash_registers/1
+  # PATCH/PUT /open_cash_registers/1.json
+  def update
+    respond_to do |format|
+      if @open_cash_register.update(open_cash_register_params)
+        format.html { redirect_to @open_cash_register, notice: 'Open cash register was successfully updated.' }
+        format.json { render :show, status: :ok, location: @open_cash_register }
+      else
+        format.html { render :edit }
+        format.json { render json: @open_cash_register.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /open_cash_registers/1
+  # DELETE /open_cash_registers/1.json
+  def destroy
+    @open_cash_register.destroy
+    respond_to do |format|
+      format.html { redirect_to open_cash_registers_url, notice: 'Open cash register was successfully destroyed.' }
+      format.json { head :no_content }
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_open_cash_register
+      @open_cash_register = OpenCashRegister.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def open_cash_register_params
+      params.require(:open_cash_register).permit(:cash_register_id, :initial_cash, :final_cash, :status)
+    end
+end

+ 252 - 0
app/controllers/pointsales_controller.rb

@@ -0,0 +1,252 @@
+class PointsalesController < ApplicationController
+	##--- Abilities
+	load_and_authorize_resource
+
+	##--- Breadcrum_rails
+	add_breadcrumb I18n.t("breadcrumbs." + controller_name), :pointsales_path
+	add_breadcrumb "Nuevo Punto de venta" , :new_pointsale_path, only: :new
+	add_breadcrumb "Detalle del Punto de venta" , :pointsale_path, only: :show
+
+	before_action :set_pointsale, only: [:show, :edit, :update, :destroy, :update_products]
+	before_action :get_filters, only: [:index, :show, :edit, :new]
+	# before_action :get_available_products, only: [:edit, :update]
+
+	# GET /pointsales
+	# GET /pointsales.json
+	def index
+		@pointsales = Pointsale.vigentes
+	end
+
+	# GET /pointsales/1
+	# GET /pointsales/1.json
+	def show
+	end
+
+	# GET /pointsales/new
+	def new
+		@pointsale = Pointsale.new
+		# @pointsale.users.new
+	end
+
+	# GET /pointsales/1/edit
+	def edit
+		@todo = params[:todo]
+		add_breadcrumb ( @todo.present? ? "Actualizar productos del punto de venta " : "Editar Punto de venta") , :edit_pointsale_path, only: :edit
+	  	respond_to do |format|
+	    	format.html
+	    	format.json { render json: AvailableProductsDatatable.new(view_context, @pointsale) }
+	  	end
+	end
+
+	# POST /pointsales
+	# POST /pointsales.json
+	def create
+		@pointsale = Pointsale.new(pointsale_params)
+
+		@pointsale.skip_name_validation =  false
+		@pointsale.skip_products_validation =  false
+		@pointsale.users.first.skip_validations_from_pointsale = true
+		@pointsale.audit_comment = "El punto de venta " + @pointsale.name + " fue creado."
+		@pointsale.users.first.usertype = 'G'
+		@pointsale.users.first.status = 1
+		@pointsale.prefix.upcase!
+		respond_to do |format|
+			if @pointsale.save
+				@pointsale.users.first.pointsale_id = @pointsale.id
+				main_cash_register = CashRegister.new
+				main_cash_register.name = @pointsale.name + " Principal"
+				main_cash_register.description = "Caja registradora principal"
+				main_cash_register.pointsale_id = @pointsale.id
+				main_cash_register.main = 'yes'
+				main_cash_register.status = 'active'
+				main_cash_register.save
+
+				format.html { redirect_to pointsales_url, success: "El punto de venta " + @pointsale.name + " fue creado." }
+				format.json { render :show, status: :created, location: @pointsale }
+			else
+				format.html { render :new }
+				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+			end
+		end
+	end
+
+	# PATCH/PUT /pointsales/1
+	# PATCH/PUT /pointsales/1.json
+	def update
+		respond_to do |format|
+			if @pointsale.update(pointsale_params)
+				@pointsale.audit_comment = "El punto de venta #{@pointsale.name} fue modificado."
+				format.html { redirect_to pointsales_url, success: "El punto de venta #{@pointsale.name} fue modificado." }
+				format.json { render :show, status: :ok, location: @pointsale }
+			else
+				format.html { render :edit }
+				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+			end
+		end
+	end
+
+	def update_status
+	    pointsale = Pointsale.find(params[:pointsale_id])
+	    puts pointsale.active?
+    	if pointsale.active?
+    		pointsale.status = 2
+    	elsif pointsale.inactive?
+    		pointsale.status = 1
+    	end
+	    respond_to do |format|
+	    	if pointsale.save(:validate => false)
+	    		pointsale.users.each do |user|
+	    			if !user.erased?
+	    				user.status = pointsale.status
+	    				user.save(:validate => false)
+	    			end
+	    		end
+	        	format.html { redirect_to pointsales_url, warning: "El punto de venta " + pointsale.name + " fue "+ (pointsale.active? ? "activado" : "desactivado")+"." }
+				# format.json { render :show, status: :ok, location: @pointsale }
+				format.json { head :no_content }
+			else
+				format.html { redirect_to pointsales_url }
+				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+			end
+	    end
+	end
+
+	def transfer_stock
+		respond_to do |format|
+			destiny = params[:destiny][2, params[:destiny].length]
+
+			availables = AvailableProduct.where('id in (?)', params[:all_ids])
+
+			AvailableProduct.find(params[:ids_to_transfer]).each do |available|
+				if params[:destiny].first == 'P'
+					# es a punto de venta
+					stockProduct = AvailableProduct.find_by(:product_id => available.product_id, :pointsale_id => destiny)
+					if stockProduct.present?
+						stockProduct.stock += available.stock
+					else
+						stockProduct = available.dup
+						stockProduct.pointsale_id = destiny
+					end
+				else
+					# es a almacen
+					stockProduct = WarehouseStock.find_by(:product_id => available.product_id, :warehouse_id => destiny)
+					if stockProduct.present?
+						stockProduct.stock += available.stock
+					else
+						stockProduct = WarehouseStock.new
+						stockProduct.warehouse_id = destiny
+						stockProduct.product_id = available.product_id
+						stockProduct.stock_min = available.stock_min
+						stockProduct.stock_max = available.stock_max
+						stockProduct.stock = available.stock
+					end
+				end
+				stockProduct.save
+				available.destroy
+		 	end
+
+		 	availables_with_zero = params[:all_ids] - params[:ids_to_transfer]
+		 	if availables_with_zero.length > 0
+		 		AvailableProduct.where(id: availables_with_zero).delete_all
+		 	end
+		 	format.json { head :ok }
+		end
+	end
+
+  	def assign_or_delete_products
+  		@todo = params[:todo]
+  		if @todo == 'delete'
+  			@products = AvailableProduct.find(params[:ids])
+  		elsif @todo == 'assign'
+  			@products = Product.find(params[:ids])
+  		end
+
+	    @pointsale = Pointsale.find(params[:pointsale_id])
+  	end
+
+  	def assign_products_to_pointsale
+  		respond_to do |format|
+		    @products = JSON.parse params[:products]
+		    @products.each do | product |
+		    	available = AvailableProduct.new
+		    	available.product_id = product['id']
+		    	available.pointsale_id = params[:pointsale_id]
+		    	available.stock_min = 0
+		    	available.stock_max = 0
+		    	available.stock = product['stock']
+		    	available.save
+		    end
+		    format.json { head :ok }
+  		end
+  	end
+
+  	def delete_products_from_pointsale
+  		respond_to do |format|
+  			@ids = params[:ids]
+			@to_delete_with_stock = AvailableProduct.where("id in (?) and stock > 0", @ids)
+			@ids_array = Array.new
+			@ids.each do |id|
+				@ids_array << id
+			end
+			@ids_array = @ids_array.to_s
+
+			if @to_delete_with_stock.blank?
+				AvailableProduct.where(id: @ids).delete_all
+			end
+			format.js
+  		end
+  	end
+
+	# DELETE /pointsales/1
+	# DELETE /pointsales/1.json
+	def destroy
+		respond_to do |format|
+			@pointsale.skip_name_validation =  true
+			@pointsale.skip_products_validation =  true
+			@pointsale.audit_comment = "El punto de venta " + @pointsale.name + " fue eliminado."
+			if @pointsale.update_attributes(:status => 0)
+				CashRegister.where(:pointsale_id => @pointsale.id).update_all(:status => 'erased')
+				format.html { redirect_to pointsales_url, warning: "El punto de venta " + @pointsale.name + " fue eliminado." }
+				format.json { head :no_content }
+			else
+				format.html { render :edit }
+				format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+			end
+		end
+	end
+
+	# GET /pointsales/1/update_products
+	# def update_products
+	#   respond_to do |format|
+	#     if @pointsale.save
+	#       #format.html { redirect_to @pointsale, notice: 'El punto de venta fue creado.' }
+	#       format.html { redirect_to pointsales_url, success: "El punto de venta " + @pointsale.name + " fue creado." }
+	#       format.json { render :show, status: :created, location: @pointsale }
+	#     else
+	#       format.html { render :update_products }
+	#       format.json { render json: @pointsale.errors, status: :unprocessable_entity }
+	#     end
+	#   end
+	# end
+
+
+	private
+		# Use callbacks to share common setup or constraints between actions.
+		def set_pointsale
+			@pointsale = Pointsale.find(params[:id])
+		end
+
+		def get_filters
+			if params[:current_page].blank?
+				@current_page = 1
+			else
+				@current_page = params[:current_page]
+			end
+			@filter = params[:filter]
+		end
+
+		# Never trust parameters from the scary internet, only allow the white list through.
+		def pointsale_params
+			params.require(:pointsale).permit(:name, :address, :notes, :status, :prefix, :img_pointsale, :img_pointsale_cache, :ticket_footer, :product_ids => [], users_attributes:[:userid, :first_name, :last_name, :password, :email, :password_confirmation])
+		end
+end

+ 55 - 0
app/controllers/pos_configs_controller.rb

@@ -0,0 +1,55 @@
+class PosConfigsController < ApplicationController
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :pos_configs_path
+
+  before_action :set_pos_config, only: [:show, :edit, :update, :destroy]
+
+  # GET /pos_configs
+  # GET /pos_configs.json
+  def index
+    @pos_config = PosConfig.first.blank? ? PosConfig.new : PosConfig.first
+  end
+
+  # POST /pos_configs
+  # POST /pos_configs.json
+  def create
+    @pos_config = PosConfig.new(pos_config_params)
+    respond_to do |format|
+      if @pos_config.save
+        format.html { redirect_to root_path, notice: 'Configuración de parametros generales guardada correctamente.' }
+        format.json { render :show, status: :created, location: @pos_config }
+      else
+        format.html { render action: "index" }
+        format.json { render json: @pos_config.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /pos_configs/1
+  # PATCH/PUT /pos_configs/1.json
+  def update
+    respond_to do |format|
+      @pos_config.audit_comment = "Se actualizó parametros generales"
+      if @pos_config.update(pos_config_params)
+        format.html { redirect_to root_path, notice: 'Configuración de parametros generales guardada correctamente.' }
+        format.json { render :show, status: :ok, location: @pos_config }
+      else
+        format.html { render action: "index" }
+        format.json { render json: @pos_config.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_pos_config
+      @pos_config = PosConfig.first
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def pos_config_params
+      params[:pos_config]
+        params.require(:pos_config).permit(:cancel_partial_payment, :refund_sale, :days_cancel_sale, :days_cancel_purchase, :tax_percent, :time_zone, :gain_margin, :ticket_description, :reserve_sale_percent, :days_cancel_reserved, :commission_percent, :ticket_footer, :ticket_img, :ticket_img_cache, :haggle_in_sale_percent)
+    end
+end

+ 78 - 0
app/controllers/pre_purchases_controller.rb

@@ -0,0 +1,78 @@
+class PrePurchasesController < ApplicationController
+  before_action :set_pre_purchase, only: [:show, :edit, :update, :destroy]
+
+
+  # GET /pre_purchases/new
+  def new
+  end
+
+  # POST /pre_purchases
+  # POST /pre_purchases.json
+  def create
+    respond_to do |format|
+      @pre_purchase = PrePurchase.new(pre_purchase_params)
+      @pre_purchase.user_id = current_user.id
+      @pre_purchase.get_totals
+      if @pre_purchase.save
+        format.json { head :no_content }
+        format.js
+      else
+        format.json { render json: @pre_purchase.errors.values, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /pre_purchases/1
+  # PATCH/PUT /pre_purchases/1.json
+  def update
+    @pre_purchase.quantity = params[:pre_purchase][:quantity].to_i
+    @pre_purchase.get_totals
+    respond_to do |format|
+      if @pre_purchase.update(pre_purchase_params)
+        format.json { head :ok }
+      end
+    end
+  end
+
+  # DELETE /pre_purchases/1
+  # DELETE /pre_purchases/1.json
+  def destroy
+    @pre_purchase.destroy
+    respond_to do |format|
+      if @pre_purchase.destroy
+        format.json { head :ok }
+      end
+    end
+  end
+
+  def add_pre_purchase_by_barcode
+    respond_to do |format|
+      @product = Product.find_by(:barcode => params[:barcode])
+      if @product.blank?
+        format.js { render :action => "create" }
+      else
+        @pre_purchase = PrePurchase.new
+        @pre_purchase.supplier_id = params[:supplier_id]
+        @pre_purchase.user_id = current_user.id
+        @pre_purchase.pointsale_id = params[:pointsale_id]
+        @pre_purchase.warehouse_id = params[:warehouse_id]
+        @pre_purchase.product_id = @product.id
+        @pre_purchase.quantity = 1
+        @pre_purchase.get_totals
+        @pre_purchase.save
+        format.js { render :action => "create" }
+      end
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_pre_purchase
+      @pre_purchase = PrePurchase.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def pre_purchase_params
+      params.require(:pre_purchase).permit(:supplier_id, :pointsale_id, :warehouse_id, :product_id, :quantity, :amount, :tax, :total, :barcode, :price_base, :exchange)
+    end
+end

+ 189 - 0
app/controllers/pre_sales_controller.rb

@@ -0,0 +1,189 @@
+class PreSalesController < ApplicationController
+  before_action :set_pre_sale, only: [:show, :edit, :update, :destroy]
+
+  # GET /pre_sales/new
+  def new
+    # @pre_sale = PreSale.new
+  end
+
+  # POST /pre_sales
+  # POST /pre_sales.json
+  def create
+    total_in_pre_sales = 0
+    @pre_sale = PreSale.new(pre_sale_params)
+    @pre_sale.user_id = current_user.id
+
+    # Sumar los totales de los presales en caso que exista.
+    PreSale.where(:user_id => @pre_sale.user_id).each do |pre|
+      total_in_pre_sales = total_in_pre_sales + pre.total
+    end
+
+    #precio unitario
+    @pre_sale.unit_price = @pre_sale.product.get_price_sale(OpenCashRegister.find(@pre_sale.open_cash_register_id).pointsale.id)
+
+    @pre_sale.get_totals
+
+    total_in_pre_sales = total_in_pre_sales + @pre_sale.total
+
+    respond_to do |format|
+      if has_enough_stock?(@pre_sale)
+        #cuando la venta es a credito checar que el credito permita esta nueva venta
+        if @pre_sale.sale_type == "credit"
+          debiting = @pre_sale.customer.get_debiting
+          if @pre_sale.customer.sale_approved?(debiting + total_in_pre_sales)
+            if @pre_sale.save
+              format.js
+            else
+              format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+            end
+          else
+            @pre_sale.errors.add(:customer_id, "El cliente ya no tiene credito disponible.")
+            format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+          end
+        elsif @pre_sale.sale_type == "cash"
+          if  @pre_sale.save
+            format.js
+          else
+            format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+          end
+        elsif @pre_sale.sale_type == "reserved"
+          if  @pre_sale.save
+            format.js
+          else
+            format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+          end
+        end
+      else
+        @pre_sale.errors.add(:base, "Stock insuficiente del producto #{@pre_sale.product.name}")
+        format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+        format.js
+      end
+    end
+  end
+
+  # PATCH/PUT /pre_sales/1
+  # PATCH/PUT /pre_sales/1.json
+  def update
+    respond_to do |format|
+      @pre_sale.quantity = params[:pre_sale][:quantity].to_i
+      availableProduct = AvailableProduct.find_by(:pointsale_id => current_user.pointsale_id,
+        :product_id => @pre_sale.product_id)
+
+      same_product_quantity = 0
+      PreSale.where(:user_id => current_user.id, :product_id => @pre_sale.product_id).where.not(:id => @pre_sale.id).each do |pre|
+        same_product_quantity += pre.quantity
+      end
+
+      if availableProduct.stock >= (same_product_quantity +  @pre_sale.quantity)
+        @pre_sale.get_totals
+
+        if @pre_sale.update(pre_sale_params)
+          format.json { render :json => @pre_sale }
+        end
+      else
+        @pre_sale.errors.add(:base, "Stock insuficiente del producto #{@pre_sale.product.name}")
+        format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /pre_sales/1
+  # DELETE /pre_sales/1.json
+  def destroy
+    respond_to do |format|
+      if @pre_sale.destroy
+        format.json { head :ok }
+      end
+    end
+  end
+
+  def add_pre_sale_by_barcode
+    total_in_pre_sales = 0
+    pointsale = OpenCashRegister.get_pointsale(params[:open_cash_register_id], "open_cash_register")
+    @product = pointsale.products.find_by("barcode = ? and stock > 0", params[:barcode])
+
+    if @product.blank?
+      format.js { render :action => "create" }
+    else
+      # ir sumando todos los presales que tiene, para ver si lo abarca el credito
+      PreSale.where(:user_id => current_user.id).each do |pre|
+        total_in_pre_sales = total_in_pre_sales + pre.total
+      end
+
+      @pre_sale = PreSale.new
+      @pre_sale.customer_id = params[:customer_id]
+      @pre_sale.user_id = current_user.id
+      @pre_sale.open_cash_register_id = params[:open_cash_register_id]
+      @pre_sale.product_id = @product.id
+      @pre_sale.sale_type = params[:sale_type]
+
+      #precio unitario
+      @pre_sale.unit_price = @pre_sale.product.get_price_sale(OpenCashRegister.find(@pre_sale.open_cash_register_id).pointsale.id)
+
+      @pre_sale.get_totals
+
+      total_in_pre_sales = total_in_pre_sales + @pre_sale.total
+
+      #cuando la venta es a credito checar que el credito permita esta nueva venta
+      respond_to do |format|
+        if has_enough_stock?(@pre_sale)
+          if @pre_sale.sale_type == "credit"
+            debiting = @pre_sale.customer.get_debiting
+            if @pre_sale.customer.sale_approved?(debiting + total_in_pre_sales)
+              if @pre_sale.save
+                format.js { render :action => "create" }
+              end
+            else
+              @pre_sale.errors.add(:customer_id, "El cliente ya no tiene credito disponible.")
+              format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+              format.js { render :action => "create" }
+            end
+          elsif @pre_sale.sale_type == "cash"
+            if  @pre_sale.save
+              format.js { render :action => "create" }
+            else
+              format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+            end
+          elsif @pre_sale.sale_type == "reserved"
+            if  @pre_sale.save
+              format.js
+            else
+              format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+            end
+          end
+        else
+          @pre_sale.errors.add(:base, "Stock insuficiente del producto #{@pre_sale.product.name}")
+          format.json { render json: @pre_sale.errors.values, status: :unprocessable_entity }
+        end
+      end
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_pre_sale
+      @pre_sale = PreSale.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def pre_sale_params
+      params.require(:pre_sale).permit(:customer_id, :user_id, :open_cash_register_id, :sale_type, :product_id, :quantity, :amount, :tax, :discount, :total, :barcode, :haggle)
+    end
+
+    def has_enough_stock?(pre_sale)
+      same_product_quantity = 0
+      PreSale.where(:user_id => current_user.id, :product_id => pre_sale.product_id).each do |pre|
+        same_product_quantity += pre.quantity
+      end
+
+      availableProduct = AvailableProduct.find_by(:pointsale_id => current_user.pointsale_id,
+        :product_id => pre_sale.product_id)
+
+      if availableProduct.stock >= (same_product_quantity +  pre_sale.quantity)
+        return true
+      else
+        return false
+      end
+
+    end
+end

+ 136 - 0
app/controllers/pre_transfers_controller.rb

@@ -0,0 +1,136 @@
+class PreTransfersController < ApplicationController
+  before_action :set_pre_transfer, only: [:show, :edit, :update, :destroy]
+
+  # GET /pre_transfers/new
+  def new
+    @pre_transfer = PreTransfer.new
+  end
+
+  # POST /pre_transfers
+  # POST /pre_transfers.json
+  def create
+    @pre_transfer = PreTransfer.new(pre_transfer_params)
+    @pre_transfer.user_id = current_user.id
+    @pre_transfer.origin_is_pointsale = params[:pre_transfer][:origin_id].first == 'P' ? 1 : 0
+    @pre_transfer.destiny_is_pointsale = params[:pre_transfer][:destiny_id].first == 'P' ? 1 : 0
+    @pre_transfer.origin_id = params[:pre_transfer][:origin_id][2, params[:pre_transfer][:origin_id].length]
+    @pre_transfer.destiny_id = params[:pre_transfer][:destiny_id][2, params[:pre_transfer][:destiny_id].length]
+    respond_to do |format|
+      if @pre_transfer.save
+        if @pre_transfer.origin_is_pointsale == 1
+          stock = AvailableProduct.find_by(:pointsale_id =>  @pre_transfer.origin_id,
+            :product_id => @pre_transfer.product_id)
+        else
+          stock = WarehouseStock.find_by(:warehouse_id => @pre_transfer.origin_id,
+            :product_id => @pre_transfer.product_id)
+        end
+        stock.stock = stock.stock -= @pre_transfer.quantity
+        stock.save
+        format.js
+      else
+        format.json { render json: @pre_transfer.errors.values, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /pre_transfers/1
+  # PATCH/PUT /pre_transfers/1.json
+  def update
+    respond_to do |format|
+      if @pre_transfer.origin_is_pointsale == 1
+        stock = AvailableProduct.find_by(:pointsale_id =>  @pre_transfer.origin_id,
+          :product_id => @pre_transfer.product_id)
+      else
+        stock = WarehouseStock.find_by(:warehouse_id => @pre_transfer.origin_id,
+          :product_id => @pre_transfer.product_id)
+      end
+      #regresarle al stock la cantidad a traspasar que estaba anteriormente
+      stock.stock += @pre_transfer.quantity
+      new_quantity = params[:pre_transfer][:quantity].to_i
+      stock.stock = stock.stock -= new_quantity
+      if stock.stock >= 0
+        if @pre_transfer.update(pre_transfer_params)
+          stock.save
+          format.json { head :ok }
+        else
+          format.json { render json: @pre_transfer.errors, status: :unprocessable_entity }
+        end
+      else
+        @pre_transfer.errors.add(:product, "El stock actual del producto es insuficiente.")
+        format.json { render json: @pre_transfer.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /pre_transfers/1
+  # DELETE /pre_transfers/1.json
+  def destroy
+    respond_to do |format|
+      if @pre_transfer.origin_is_pointsale == 1
+        stock = AvailableProduct.find_by(:pointsale_id =>  @pre_transfer.origin_id,
+          :product_id => @pre_transfer.product_id)
+      else
+        stock = WarehouseStock.find_by(:warehouse_id => @pre_transfer.origin_id,
+          :product_id => @pre_transfer.product_id)
+      end
+      if @pre_transfer.destroy
+        stock.stock += @pre_transfer.quantity
+        stock.save
+        format.json { head :ok }
+      end
+    end
+  end
+
+  def add_pre_transfer_by_barcode
+    respond_to do |format|
+      @pre_transfer = PreTransfer.new
+      @pre_transfer.user_id = current_user.id
+
+      #dependiendo del prefijo que traiga W o P determinar de que tipo es el origen.
+      @pre_transfer.origin_is_pointsale = params[:origin_id].first == 'P' ? 1 : 0
+      @pre_transfer.destiny_is_pointsale = params[:destiny_id].first == 'P' ? 1 : 0
+      @pre_transfer.origin_id = params[:origin_id][2, params[:origin_id].length]
+      @pre_transfer.destiny_id = params[:destiny_id][2, params[:destiny_id].length]
+
+      if @pre_transfer.origin_is_pointsale == 1
+        origin = Pointsale.find(@pre_transfer.origin_id)
+        product = origin.products.find_by("barcode = ? and stock > 0", params[:barcode])
+      else
+        origin = Warehouse.find(@pre_transfer.origin_id)
+        product = origin.products.find_by("barcode = ? and stock > 0", params[:barcode])
+      end
+
+      if product.blank?
+        @error = "No existe producto escaneado o no tiene stock"
+      else
+        @pre_transfer.product_id = product.id
+        @pre_transfer.quantity = 1
+        if @pre_transfer.save
+
+          if @pre_transfer.origin_is_pointsale == 1
+            stock = AvailableProduct.find_by(:pointsale_id =>  @pre_transfer.origin_id,
+              :product_id => product.id)
+          else
+            stock = WarehouseStock.find_by(:warehouse_id => @pre_transfer.origin_id,
+            :product_id => product.id)
+          end
+
+          stock.stock = stock.stock -= @pre_transfer.quantity
+          stock.save
+        end
+      end
+      format.js { render :action => "create" }
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_pre_transfer
+      @pre_transfer = PreTransfer.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def pre_transfer_params
+      params.require(:pre_transfer).permit(:origin_id, :destiny_id, :user_id, :product_id, :quantity)
+    end
+end

+ 111 - 0
app/controllers/product_wastes_controller.rb

@@ -0,0 +1,111 @@
+class ProductWastesController < ApplicationController
+  ##--- Abilities
+  load_and_authorize_resource
+
+  before_action :set_product_waste, only: [:show, :edit, :update, :destroy]
+
+  ##--- Breadcrum_rails
+  add_breadcrumb I18n.t("breadcrumbs." + controller_name), :product_wastes_path
+  add_breadcrumb "Nueva merma de productos" , :new_product_waste_path, only: :new
+
+  before_action :set_pos_config, only: [:show, :edit, :update, :destroy]
+
+  # GET /product_wastes
+  # GET /product_wastes.json
+  def index
+    if current_user.usertype == 'A'
+      @product_wastes = ProductWaste.includes(:product, :user, :pointsale, :warehouse).activos
+    elsif current_user.usertype == 'G'
+      @product_wastes = ProductWaste.includes(:product, :user).where(:pointsale_id => current_user.pointsale_id).activos
+    elsif current_user.usertype == 'S'
+      @product_wastes = ProductWaste.includes(:product, :user).where(:warehouse_id => current_user.warehouse_id).activos
+    end
+  end
+
+  # GET /product_wastes/new
+  def new
+  end
+
+
+  # POST /product_wastes
+  # POST /product_wastes.json
+  def create
+    respond_to do |format|
+      @product_waste = ProductWaste.new(product_waste_params)
+      @product_waste.user_id = current_user.id
+      @product_waste.status = :active
+
+      if current_user.usertype == 'S'
+        @product_waste.warehouse_id = current_user.warehouse_id
+        @stock = WarehouseStock.find_by(:warehouse_id => @product_waste.warehouse_id,
+        :product_id => @product_waste.product_id)
+      else
+        @product_waste.pointsale_id = current_user.pointsale_id
+        @stock = AvailableProduct.find_by(:pointsale_id => @product_waste.pointsale_id,
+        :product_id => @product_waste.product_id)
+      end
+      if @stock.stock >= @product_waste.quantity
+        @product_waste.audit_comment = "Merma del producto #{@product_waste.product.name}  creada"
+        if @product_waste.save
+          @stock.stock -= @product_waste.quantity
+          @stock.save
+          format.js
+        else
+          format.json { render json: @product_waste.errors.values, status: :unprocessable_entity }
+        end
+      else
+        @product_waste.errors.add(:base, "Stock insuficiente, solo se cuenta con #{@stock.stock} unidades del producto #{@product_waste.product.name}")
+        format.json { render json: @product_waste.errors.values, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # PATCH/PUT /product_wastes/1
+  # PATCH/PUT /product_wastes/1.json
+  def update
+    respond_to do |format|
+      if @product_waste.update(product_waste_params)
+        format.html { redirect_to @product_waste, notice: 'Product waste was successfully updated.' }
+        format.json { render :show, status: :ok, location: @product_waste }
+      else
+        format.html { render :edit }
+        format.json { render json: @product_waste.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /product_wastes/1
+  # DELETE /product_wastes/1.json
+  def destroy
+    respond_to do |format|
+      @product_waste.audit_comment = "Merma del producto #{@product_waste.product.name}  eliminada."
+      if @product_waste.update_attributes(:status => :inactive)
+        if current_user.usertype == "S"
+          @stock = WarehouseStock.find_by(:warehouse_id => @product_waste.warehouse_id,
+          :product_id => @product_waste.product_id)
+        else
+          @stock = AvailableProduct.find_by(:pointsale_id => @product_waste.pointsale_id,
+          :product_id => @product_waste.product_id)
+        end
+        @stock.stock = @stock.stock + @product_waste.quantity
+        @stock.save
+
+        format.html { redirect_to product_wastes_url, warning: 'Merma de producto eliminada.' }
+        format.json { head :ok }
+      else
+        format.json { render json: @product_waste.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  private
+    # Use callbacks to share common setup or constraints between actions.
+    def set_product_waste
+      @product_waste = ProductWaste.find(params[:id])
+    end
+
+    # Never trust parameters from the scary internet, only allow the white list through.
+    def product_waste_params
+      params.require(:product_waste).permit(:warehouse_id, :pointsale_id, :product_id, :quantity, :reason, :status)
+    end
+end

+ 0 - 0
app/controllers/products_controller.rb


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác