From e88fbb9d8e648cff1ece3085e38119ce03144cfb Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Thu, 28 Feb 2013 14:04:16 +0100 Subject: [PATCH] Add a simple estimate when the stock is reaching zero... --- LICENSE | 26 +------ README.md | 10 ++- Rakefile | 2 +- Versionfile | 2 +- app/mailers/spree/stock_mailer.rb | 74 +++++++++++++++++++ .../stock_mailer/stock_report_email.text.erb | 21 ++++++ .../install/install_generator.rb | 12 +-- lib/sysmocom_stock.rb | 3 +- lib/sysmocom_stock/engine.rb | 4 +- script/rails | 2 +- sysmocom_stock.gemspec | 20 ++--- 11 files changed, 126 insertions(+), 50 deletions(-) create mode 100644 app/mailers/spree/stock_mailer.rb create mode 100644 app/views/spree/stock_mailer/stock_report_email.text.erb diff --git a/LICENSE b/LICENSE index 9822124..c51a41d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,26 +1,4 @@ -Copyright (c) 2013 [name of plugin creator] +Copyright (c) 2013 s.f.m.c. GmbH All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name Spree nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - -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. +AGPLv3 The code is AGPLv3 diff --git a/README.md b/README.md index 6467a89..98aee43 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -SpreeSysmocomStock +SysmocomStock ================== -Introduction goes here. +Help with estimating when the stock will turn empty. More varpoware +than actual functions but it could take lead-time of a product into +account.. propose the quantity.. Example ======= -Example goes here. +TODO Testing ------- @@ -18,4 +20,4 @@ Be sure to bundle your dependencies and then create a dummy test app for the spe $ bundle exec rake test_app $ bundle exec rspec spec -Copyright (c) 2013 [name of extension creator], released under the New BSD License +Copyright (c) 2013 sysmocom s.f.m.c. GmbH, released under AGPLv3 or later diff --git a/Rakefile b/Rakefile index 3a321bf..2c1ac86 100644 --- a/Rakefile +++ b/Rakefile @@ -10,6 +10,6 @@ task :default => [:spec] desc 'Generates a dummy app for testing' task :test_app do - ENV['LIB_NAME'] = 'spree_sysmocom_stock' + ENV['LIB_NAME'] = 'sysmocom_stock' Rake::Task['common:test_app'].invoke end diff --git a/Versionfile b/Versionfile index 5db83aa..a537bf4 100644 --- a/Versionfile +++ b/Versionfile @@ -3,7 +3,7 @@ # Examples # -# '1.2.x' => { :branch => 'master' } +'1.2.x' => { :branch => 'master' } # '1.1.x' => { :branch => '1-1-stable' } # '1.0.x' => { :branch => '1-0-stable' } # '0.70.x' => { :branch => '0-70-stable' } diff --git a/app/mailers/spree/stock_mailer.rb b/app/mailers/spree/stock_mailer.rb new file mode 100644 index 0000000..f8f9286 --- /dev/null +++ b/app/mailers/spree/stock_mailer.rb @@ -0,0 +1,74 @@ +module Spree + class StockMailer < ActionMailer::Base + helper 'spree/base' + + def find_empty_products() + Spree::Product.where(:count_on_hand => 0).order("name").select {|prod| + prod.deleted_at.nil? } + end + + def find_empty_variants() + Spree::Variant.where(:count_on_hand => 0).order("sku").select {|var| + var.deleted_at.nil? and not var.is_master? + } + end + + def generate_estimate(var) + shipped_week = Spree::InventoryUnit.find(:all, :conditions => { + :state => 'shipped', + :variant_id => var.id, + :updated_at => @last_week.midnight..@today.end_of_day, + }).size() + shipped_month = Spree::InventoryUnit.find(:all, :conditions => { + :state => 'shipped', + :variant_id => var.id, + :updated_at => @last_month.midnight..@today.end_of_day, + }).size() + shipped_year = Spree::InventoryUnit.find(:all, :conditions => { + :state => 'shipped', + :variant_id => var.id, + :updated_at => @last_year.midnight..@today.end_of_day, + }).size() + + # Normalize to units per day... with a shared secret about the range + norm_week = shipped_week / 7.0 + norm_month = shipped_month / 30.0 + norm_year = shipped_year / 365.0 + + # A weighted mean with magic numbers pulled out of thing air. + mean = ((0.6 * norm_week) + (0.25 * norm_month) + (0.15 * norm_year)) / 1.0 + if var.sku.empty? + name = "Prd " + var.product.sku + else + name = "Sku " + var.sku + end + + {'weighted_mean' => mean, 'variant' => var, 'product' => var.product, 'name' => name} + end + + def generate_forecast() + @today = Date.today + @last_week = @today - 7 + @last_month = @today - 30 + @last_year = @today - 365 + + forecast = [] + vars = Spree::Variant.where("deleted_at IS NULL").order("sku").select {|var| + var.product.deleted_at.nil? and var.count_on_hand > 0 } + + vars.each {|var| + forecast.push(generate_estimate(var)) + } + + forecast + end + + def stock_report_email() + @empty_products = find_empty_products() + @empty_variants = find_empty_variants() + @forecast = generate_forecast() + mail(:to => 'webshop@sysmocom.de', + :subject => 'Stock report of the week') + end + end +end diff --git a/app/views/spree/stock_mailer/stock_report_email.text.erb b/app/views/spree/stock_mailer/stock_report_email.text.erb new file mode 100644 index 0000000..9e75e53 --- /dev/null +++ b/app/views/spree/stock_mailer/stock_report_email.text.erb @@ -0,0 +1,21 @@ +Dear Shop-Owner, + +below is the current stock report and estimates on when the products +will run low. + +Estimates: +<% @forecast.each do |dict| %> +<%= dict['name'] %> => <%= dict['weighted_mean'].round(2) %> days until sold out. +<% end %> + + +Empty Products: +<% @empty_products.each do |product| %> +<%= product.name %> +<% end %> + + +Empty Variants: +<% @empty_variants.each do |variant| %> +<%= variant.name %> +<% end %> diff --git a/lib/generators/sysmocom_stock/install/install_generator.rb b/lib/generators/sysmocom_stock/install/install_generator.rb index 4fcf31d..6d5c886 100644 --- a/lib/generators/sysmocom_stock/install/install_generator.rb +++ b/lib/generators/sysmocom_stock/install/install_generator.rb @@ -1,19 +1,19 @@ -module SpreeSysmocomStock +module SysmocomStock module Generators class InstallGenerator < Rails::Generators::Base def add_javascripts - append_file 'app/assets/javascripts/store/all.js', "//= require store/spree_sysmocom_stock\n" - append_file 'app/assets/javascripts/admin/all.js', "//= require admin/spree_sysmocom_stock\n" + append_file 'app/assets/javascripts/store/all.js', "//= require store/sysmocom_stock\n" + append_file 'app/assets/javascripts/admin/all.js', "//= require admin/sysmocom_stock\n" end def add_stylesheets - inject_into_file 'app/assets/stylesheets/store/all.css', " *= require store/spree_sysmocom_stock\n", :before => /\*\//, :verbose => true - inject_into_file 'app/assets/stylesheets/admin/all.css', " *= require admin/spree_sysmocom_stock\n", :before => /\*\//, :verbose => true + inject_into_file 'app/assets/stylesheets/store/all.css', " *= require store/sysmocom_stock\n", :before => /\*\//, :verbose => true + inject_into_file 'app/assets/stylesheets/admin/all.css', " *= require admin/sysmocom_stock\n", :before => /\*\//, :verbose => true end def add_migrations - run 'bundle exec rake railties:install:migrations FROM=spree_sysmocom_stock' + run 'bundle exec rake railties:install:migrations FROM=sysmocom_stock' end def run_migrations diff --git a/lib/sysmocom_stock.rb b/lib/sysmocom_stock.rb index 49a0268..793b60f 100644 --- a/lib/sysmocom_stock.rb +++ b/lib/sysmocom_stock.rb @@ -1,2 +1,3 @@ require 'spree_core' -require 'spree_sysmocom_stock/engine' +require 'sysmocom_stock/engine' + diff --git a/lib/sysmocom_stock/engine.rb b/lib/sysmocom_stock/engine.rb index cd4bce7..6c921ea 100644 --- a/lib/sysmocom_stock/engine.rb +++ b/lib/sysmocom_stock/engine.rb @@ -1,8 +1,8 @@ -module SpreeSysmocomStock +module SysmocomStock class Engine < Rails::Engine require 'spree/core' isolate_namespace Spree - engine_name 'spree_sysmocom_stock' + engine_name 'sysmocom_stock' config.autoload_paths += %W(#{config.root}/lib) diff --git a/script/rails b/script/rails index 7399ce6..a215a30 100644 --- a/script/rails +++ b/script/rails @@ -1,7 +1,7 @@ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. ENGINE_ROOT = File.expand_path('../..', __FILE__) -ENGINE_PATH = File.expand_path('../../lib/spree_sysmocom_stock/engine', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/sysmocom_stock/engine', __FILE__) require 'rails/all' require 'rails/engine/commands' diff --git a/sysmocom_stock.gemspec b/sysmocom_stock.gemspec index d184a9d..03014c2 100644 --- a/sysmocom_stock.gemspec +++ b/sysmocom_stock.gemspec @@ -1,22 +1,22 @@ # encoding: UTF-8 Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY - s.name = 'spree_sysmocom_stock' - s.version = '1.2.4.beta' - s.summary = 'TODO: Add gem summary here' - s.description = 'TODO: Add (optional) gem description here' + s.name = 'sysmocom_stock' + s.version = '0.0.1' + s.summary = 'sysmocom s.f.m.c. Stock Management' + s.description = 'Help estimating empty stock, turnover' s.required_ruby_version = '>= 1.8.7' - # s.author = 'You' - # s.email = 'you@example.com' - # s.homepage = 'http://www.spreecommerce.com' + s.author = 'Holger Hans Peter Freyther' + #s.email = 'you@example.com' + s.homepage = 'http://www.sysmocom.de' - #s.files = `git ls-files`.split("\n") - #s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.require_path = 'lib' s.requirements << 'none' - s.add_dependency 'spree_core', '~> 1.2.4.beta' + s.add_dependency 'spree_core', '>= 1.2.0' s.add_development_dependency 'capybara', '1.0.1' s.add_development_dependency 'factory_girl', '~> 2.6.4'