diff --git a/back-office/app/models/item.rb b/back-office/app/models/item.rb index a59816b..f25908e 100644 --- a/back-office/app/models/item.rb +++ b/back-office/app/models/item.rb @@ -4,6 +4,7 @@ class Item < ActiveRecord::Base belongs_to :sub_category belongs_to :supplier belongs_to :brand + belongs_to :delivery_time_estimation has_and_belongs_to_many :item_groups, :join_table => 'item_item_groups' validates_presence_of :name, :description, :list_price, :current_input_price, :tags, :unit_id, :code, :sub_category_id, :weight, :supplier_id @@ -12,13 +13,13 @@ class Item < ActiveRecord::Base # todo build a front end in backoffice (rails) def self.items_to_order return Item.find_by_sql(%Q{ - select s.name as supplier, i.name as item, sum(iic.count) amount, i.current_input_price - from carts c - join item_in_carts iic on iic.cart_id = c.id - join items i on i.id = iic.item_id - join suppliers s on i.supplier_id = s.id - where c.confirmed and not (c.packed or c.delivered) - group by s.name, i.name, i.current_input_price + select s.name as supplier, i.name as item, sum(iic.count) amount, i.current_input_price + from carts c + join item_in_carts iic on iic.cart_id = c.id + join items i on i.id = iic.item_id + join suppliers s on i.supplier_id = s.id + where c.confirmed and not (c.packed or c.delivered) + group by s.name, i.name, i.current_input_price order by s.name, amount desc; }) end diff --git a/back-office/lib/tasks/ribica.rake b/back-office/lib/tasks/ribica.rake new file mode 100644 index 0000000..7732c74 --- /dev/null +++ b/back-office/lib/tasks/ribica.rake @@ -0,0 +1,309 @@ +require 'csv' + +def get_column_lookup + columns = [ + :name, + :name_on_ribica, + :picture, + :brand, + :code, + :current_input_price, + :list_price, + :units_in_pack, + :description, + :stock, + :on_display, + :tags, + :traits, + :weight, + :delivery_time_estimation, + + :name_unit, + :short_name_unit, + :description_suffix_unit, + :number_of_pieces_suffix_unit, + + :name_sub_category, + :order_sub_category, + + :name_supplier, + :address_supplier, + :postal_code_supplier, + :town_supplier, + :phone_supplier, + :contact_person_supplier, + :email_supplier, + :note_supplier + ] + + columns.map.with_index { |x, i| [x, i] }.to_h +end + +def lookup_delivery_time_estimation(row, lookup) + dte = DeliveryTimeEstimation.where( + duration_in_days: row[lookup[:delivery_time_estimation]] + ).take + + if dte.nil? + dte = DeliveryTimeEstimation.new + dte.duration_in_days = row[lookup[:delivery_time_estimation]] + dte.save! + end + + if dte.duration_in_days == 0 + raise "Delivery time estimation input is missing or incorrect: #{dte.delivery_time_estimation}" + end + + return dte +end + +def lookup_unit(row, lookup) + unit = Unit.where( + name: row[lookup[:name_unit]], + short_name: row[lookup[:short_name_unit]], + description_suffix: row[lookup[:description_suffix_unit]], + number_of_pieces_suffix: row[lookup[:number_of_pieces_suffix_unit]]).take + + if unit.nil? + new_unit = Unit.new + new_unit.name = row[lookup[:name_unit]] + new_unit.short_name = "kom" #row[lookup[:short_name_unit]] + new_unit.description_suffix = row[lookup[:description_suffix_unit]] + new_unit.number_of_pieces_suffix = row[lookup[:number_of_pieces_suffix_unit]] + new_unit.save! + new_unit + else + unit + end +end + +def lookup_brand(row, lookup) + brand = Brand.find_by name: row[lookup[:brand]] + if brand.nil? + new_brand = Brand.new + new_brand.name = row[lookup[:brand]] + new_brand.save! + new_brand + else + brand + end +end + +def lookup_supplier(row, lookup) + supplier = Supplier.find_by name: row[lookup[:name_supplier]] + if supplier.nil? + new_supplier = Supplier.new + new_supplier.name = row[lookup[:name_supplier]] + new_supplier.address = row[lookup[:address_supplier]] + new_supplier.postal_code = row[lookup[:postal_code_supplier]] + new_supplier.town = row[lookup[:town_supplier]] + new_supplier.phone = row[lookup[:phone_supplier]] + new_supplier.contact_person = row[lookup[:contact_person_supplier]] + new_supplier.email = row[lookup[:email_supplier]] + new_supplier.note = row[lookup[:note_supplier]] + new_supplier.save! + new_supplier + else + supplier + end +end + +def resolve_subcategory(str) + raise "Subcategory is missing!" if str.nil? + + parts = str.split(">").map{ |s| s.strip } + subcategory = parts[2] + category = parts[1] + section = parts[0] + + if subcategory.to_s == '' || category.to_s == '' || section.to_s == '' + raise "Invalid subcategory : #{str}" + end + + subcategory = subcategory.downcase + category = category.downcase + section = section.downcase + + sc = SubCategory.eager_load(category: :section) + .where("lower(sub_categories.name) = '#{subcategory}' and lower(categories.name) = '#{category}' and lower(sections.name) = '#{section}'") + .take + + return sc unless sc.nil? + + section_in_db = Section.where("lower(name) = '#{section}'").take + if section_in_db.nil? + section_in_db = Section.new + section_in_db.name = section.capitalize + section_in_db.save! + end + + + cat_in_db = + Category + .eager_load(:section) + .where("lower(categories.name) = '#{category}' and lower(sections.name) = '#{section}'") + .take + + if cat_in_db.nil? + cat_in_db = Category.new + cat_in_db.name = category.capitalize + cat_in_db.section_id = section_in_db.id + cat_in_db.save! + end + + sub_cat_in_db = + SubCategory + .eager_load(:category) + .where("lower(sub_categories.name) = '#{subcategory}' and lower(categories.name) = '#{category}'") + .take + + if sub_cat_in_db.nil? + sub_cat_in_db = SubCategory.new + sub_cat_in_db.name = subcategory.capitalize + sub_cat_in_db.category_id = cat_in_db.id + sub_cat_in_db.save! + end + + sub_cat_in_db + #item.sub_category.order = row[lookup[:order_sub_category]] +end + +def handle_multimedia(item, row, index, logger, lookup) + multimedia = row[lookup[:picture]] + return if multimedia.to_s == "" + + parts = multimedia.split(";").map{ |s| s.strip } + + parts.each do |part| + mmd = MultiMediaDescription.new + mmd.url = part + item.multi_media_descriptions << mmd + end +end + +def import_single_item(row, index, logger) + succes = true + begin + lookup = get_column_lookup + item = Item.new + item.name = row[lookup[:name_on_ribica]] + item.code = row[lookup[:code]] + item.current_input_price = row[lookup[:current_input_price]] || 11.00 + item.list_price = row[lookup[:list_price]] || 12.00 + item.units_in_pack = row[lookup[:units_in_pack]] + item.description = row[lookup[:description]] || "default description" + item.stock = row[lookup[:stock]] + item.on_display = row[lookup[:on_display]] + item.tags = row[lookup[:tags]] + item.traits = row[lookup[:traits]] + item.weight = row[lookup[:weight]] || 1.0 + item.delivery_time_estimation = lookup_delivery_time_estimation(row, lookup) + + item.sub_category = resolve_subcategory(row[lookup[:name_sub_category]]) + # todo multimedia, item groups + + handle_multimedia(item, row, index, logger, lookup) + + item.unit = lookup_unit(row, lookup) + item.supplier = lookup_supplier(row, lookup) + item.brand = lookup_brand(row, lookup) + item.save(validate: false) + # logger.info "Successfully imported item with on row position #{index}: #{row[lookup[:name_on_ribica]]}" + success = true + rescue Exception => e + logger.error "Could not import item on row number #{index} (#{row[lookup[:name_on_ribica]]}), reason: #{e}" + success = false + end + + success +end + +def do_import(validate_only) + begin + input_file = ENV['INPUT'] + if input_file.to_s == "" + puts "Input file is missing! Please provide input file in form INPUT=somefile.csv" + return + end + + lookup = get_column_lookup + path = Rails.root.join(input_file) + + log_filename = "import.log" + log_filename = "import_validate.log" if validate_only + + logger = Logger.new(log_filename) + + logger.info "Item import starting at #{Time.now}" + logger.info "Will be importing items from #{path}" + + i = 1 + should_rollback = false + Item.transaction do + CSV.foreach(path) do |row| + if i != 1 + if import_single_item(row, i, logger) == false + should_rollback = true + end + + end + i += 1 + end + + if validate_only || should_rollback + if should_rollback + logger.info "Rolling back because of errors" + end + + raise ActiveRecord::Rollback + end + end + rescue Exception => e + logger.error "Error while importing: #{e}" + end + logger.info "Import done" +end + +namespace :ribica do + task copy_sections_to_menu: :environment do + Section.transaction do + Section.all.each do |section| + mi = MenuItem.new + mi.title = section.name + mi.url = "/sekcija/#{section.id}/#{section.name}" + mi.ordinal = section.order + + section.categories.each do |category| + msi = MenuSubItem.new + msi.title = category.name + msi.url = "/sekcija/#{section.name}/kategorija/#{category.id}/#{category.name}" + msi.ordinal = section.order + mi.menu_sub_items << msi + end + mi.save! + end + end + end +end + +namespace :ribica do + task clear_database: :environment do + conn = ActiveRecord::Base.connection + + tables = conn.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';").map { |r| r["table_name"] } + tables.delete "schema_migrations" + tables.each { |t| conn.execute("TRUNCATE TABLE #{t}") } + end +end + +namespace :ribica do + task validate_items: :environment do + do_import true + end +end + +namespace :ribica do + task import_items: :environment do + do_import false + end +end diff --git a/front-api/db/migrate/20150404114006_change_code_column_on_items_table.rb b/front-api/db/migrate/20150404114006_change_code_column_on_items_table.rb new file mode 100644 index 0000000..3ce93a4 --- /dev/null +++ b/front-api/db/migrate/20150404114006_change_code_column_on_items_table.rb @@ -0,0 +1,5 @@ +class ChangeCodeColumnOnItemsTable < ActiveRecord::Migration + def change + change_column :items, :code, :string, limit: 30 + end +end diff --git a/front-api/db/schema.rb b/front-api/db/schema.rb index f4a1995..1343116 100644 --- a/front-api/db/schema.rb +++ b/front-api/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150328132019) do +ActiveRecord::Schema.define(version: 20150404114006) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -113,7 +113,7 @@ ActiveRecord::Schema.define(version: 20150328132019) do create_table "items", force: :cascade do |t| t.string "name" - t.string "code", limit: 10 + t.string "code", limit: 30 t.decimal "current_input_price", precision: 5, scale: 2 t.decimal "list_price", precision: 5, scale: 2 t.integer "unit_id" diff --git a/front-ui/app/components/rootApp.js b/front-ui/app/components/rootApp.js index a75328b..bc94157 100644 --- a/front-ui/app/components/rootApp.js +++ b/front-ui/app/components/rootApp.js @@ -1,5 +1,7 @@ var React = require('react'), MenuItemListComponent = require('./shared/menuItemListComponent'), + SectionListComponent = require('./shared/sectionsListComponent'), + Router = require('react-router'), Link = Router.Link, RouteHandler = Router.RouteHandler, @@ -14,7 +16,7 @@ var RootApp = React.createClass({ // Add change listeners to stores componentDidMount: function() { - InitializationStore.addChangeListener(this._onChange); + InitializationStore.addChangeListener(this._onChange); InitializationActions.initialize(); }, @@ -26,7 +28,7 @@ var RootApp = React.createClass({ _onChange: function () { if (this.isMounted()) { - this.setState(InitializationStore.getState()); + this.setState(InitializationStore.getState()); } }, @@ -44,7 +46,7 @@ var RootApp = React.createClass({