Skip to content

Instantly share code, notes, and snippets.

@bigos
Created March 3, 2017 16:40
Show Gist options
  • Save bigos/9916c086e60827c8e5abf54861b1252b to your computer and use it in GitHub Desktop.
Save bigos/9916c086e60827c8e5abf54861b1252b to your computer and use it in GitHub Desktop.
Sample of my Ruby code written 2 years ago
require 'csv'
# helper code for importing csv orders
class ReceiptImporter
attr_reader :found_errors, :invalid_receipts, :skipped_receipts, :imported_receipts, :total_receipts
def initialize(filepath, mode)
@depot = Depot.find Depot.current
@mode = mode
@record_types = { 'FH' => :file_header,
'RH' => :receipt_header,
'RI' => :receipt_item,
'RT' => :receipt_total,
'FT' => :file_total }
# creating hashes translating column values to names,
# eg. order header CSV row[2] will become row[@oh[:order_date]]
@fh = {}
fh_columns = [:record_type, :creation_date, :creation_time, :senders_reference]
fh_columns.each_with_index { |item, index| @fh[item] = index }
@rh = {}
rh_columns = [:record_type, :additional_ref1, :supplier, :owner, :marshalling_zone, :receipt_date, :notes]
rh_columns.each_with_index { |item, index| @rh[item] = index }
@ri = {}
ri_columns = [:record_type, :additional_ref1, :product, :quantity, :secondary_quantity,
:split_stock, :putaway_done, :location_code, :pallet_ref, :rotation_date, :stock_status, :batch_no ]
ri_columns.each_with_index { |item, index| @ri[item] = index }
@rt = {}
rt_columns = [:record_type, :additional_ref1, :total_records, :total_quantity_ordered]
rt_columns.each_with_index { |item, index| @rt[item] = index }
@ft = {}
ft_columns = [:record_type, :total_receipts, :total_records, :total_quantity]
ft_columns.each_with_index { |item, index| @ft[item] = index }
@found_errors = []
@line_count = 0
@row_count = 0
@skipped_count = 0
@total_receipts = 0
@total_records = 0
@total_quantity = 0
@invalid_receipts = [] # order numbers of invalid order headers
@correct_receipts = [] # valid order (including all the items) numbers
@skipped_receipts = [] # order numbers of skipped order headers
@imported_receipts = []
process_file filepath
end
def process_file(filepath)
CSV.foreach(filepath, headers: false) do |row|
# puts '>>> ' + row.inspect
@current_row = row
@line_count += 1
header = row[0]
if header.blank? # should it be all blank?
@skipped_count += 1
elsif correct? header
@row_count += 1
process_row row
end
end
end
# select a method for row processing depending on record type
def process_row(row)
self.send(@record_types[row[0]], row)
end
# processing of different record types FH RH RI RT FT
def file_header(row)
row_hash = {}
@fh.each_pair { |key, val| row_hash[key] = row[@fh[key]] }
p row_hash
end
# what about headers failing validation check?
def receipt_header(row)
@last_receipt_total_qty = 0
@last_receipt_records = 0
@total_receipts += 1
@total_records += 1
@invalid_items = []
row_hash = {}
@rh.each_pair { |key, val| row_hash[key] = row[@rh[key]] }
@last_receipt_header = row_hash
@last_receipt_header_line = @line_count
puts '==========================='
p row_hash
import_receipt_header row_hash if @mode == :import
end
def receipt_item(row)
if @last_receipt_header
@last_receipt_records += 1
@total_records += 1
row_hash = {}
@ri.each_pair do |key, val|
if key == :quantity
qty = Integer(row[@ri[key]])
row_hash[key] = qty
@last_receipt_total_qty += qty
@total_quantity += qty
else
row_hash[key] = row[@ri[key]]
end
end
puts 'receipt item hash'
p row_hash
if @mode == :import && @receipt_header_obj
import_receipt_item row_hash
end
end
end
def receipt_total(row)
if @last_receipt_header
@total_records += 1
row_hash = {}
@rt.each_pair do |key, val|
if [:total_records, :total_quantity_ordered].include? key
num = Integer(row[@rt[key]])
row_hash[key] = num
else
row_hash[key] = row[@rt[key]]
end
end
p row_hash
receipt_total_check row_hash
receipt_total_imports(row[@rt[:additional_ref1]])
receipt_items_cleanup if @mode == :import
end
end
def receipt_total_imports(additional_ref1)
return if @invalid_items.include?(additional_ref1) || @skipped_receipts.include?(additional_ref1)
puts "\n=====> import header #{@last_receipt_header}"
@correct_receipts << additional_ref1
end
def receipt_total_check(row_hash)
if @last_receipt_total_qty == row_hash[:total_quantity_ordered]
puts 'total receipt quantity is ok'
else
add_to_errors 'receipt total quantity ordered is not valid'
end
if (@last_receipt_records + 2) == row_hash[:total_records]
puts 'number of records is ok'
else
add_to_errors 'receipt total number of records is not valid'
end
end
def file_total(row)
@total_records += 1
row_hash = {}
@ft.each_pair do |key, val|
if key == :record_type
row_hash[key] = row[@ft[key]]
else
num = Integer(row[@ft[key]])
row_hash[key] = num
end
end
p row_hash
if row_hash[:total_receipts] != @total_receipts
add_to_errors "total receipt quantity is incorrect - expected #{@total_receipts} - found #{row_hash[:total_receipts]}", true
end
if row_hash[:total_records] != @total_records
add_to_errors "total line number is incorrect - expected #{@total_records} - found #{row_hash[:total_records]}", true
end
if row_hash[:total_quantity] != @total_quantity
add_to_errors "total quantity is incorrect - expected #{@total_quantity} - found #{row_hash[:total_quantity]}",true
end
puts '++++++++++++++++++'
if @found_errors.size > 0
puts "errors found #{@found_errors.inspect}"
else
puts "\nno errors"
end
puts 'skipped receipts'
puts @skipped_receipts.inspect
puts "\n\nthe end\n\n\n"
end
# importing data from parsed CSV file
def import_receipt_header(row_hash)
puts "\n********** processing header " + row_hash.inspect
receipt = Receipt.new
receipt.additional_ref1 = row_hash[:additional_ref1]
receipt.passed_supplier = row_hash[:supplier]
receipt.supplier_id = @depot.company.supplier_details.where(supplier_code: receipt.passed_supplier).try(:first).try(:id)
receipt.passed_owner_detail = row_hash[:owner]
receipt.owner_detail_id = @depot.company.owner_details.where(owner_code: receipt.passed_owner_detail).try(:first).try(:id)
receipt.passed_marshalling_zone = row_hash[:marshalling_zone]
receipt.marshalling_zone_id = @depot.zones.marshalling.where(name: receipt.passed_marshalling_zone).try(:first).try(:id)
begin
receipt.passed_date = row_hash[:receipt_date]
receipt.date_of_receipt = receipt.passed_date
rescue
puts 'invalid date detected'
end
receipt.notes = row_hash[:notes]
receipt.depot_id = @depot.id
receipt.user_id = User.current
already_imported = @depot.receipts.where(additional_ref1: row_hash[:additional_ref1])
if already_imported.count > 0
@skipped_receipts << row_hash[:additional_ref1]
elsif receipt.valid?
receipt.save
@receipt_header_obj = receipt
@correct_receipts << row_hash[:additional_ref1]
else
@receipt_header_obj = nil
puts '$$$$$$$$$ failed receipt header validation'
puts receipt.errors.messages.inspect
add_to_errors human_errors(receipt)
@invalid_receipts << row_hash[:additional_ref1]
end
end
def import_receipt_item(row_hash)
puts "\n********** processing item " + row_hash.inspect
try_to_import = true
if @skipped_receipts.include?(row_hash[:additional_ref1])
try_to_import = false
end
puts '@receipt_header_obj is nil' if @receipt_header_obj.nil?
if @receipt_header_obj
unless row_hash[:additional_ref1].strip == @receipt_header_obj.additional_ref1.strip
# inconsistent additional_ref1
# uncomment this if you want to stop processing upon discovery of inconsitent additional_ref1
# try_to_import = false
puts "inconsistent additional_ref1 row: #{row_hash[:additional_ref1]} - receipt header: #{@receipt_header_obj.additional_ref1}"
add_to_errors "Additional ref #{ row_hash[:additional_ref1] } is not consistent with the header in line #{@last_receipt_header_line}, expected #{@receipt_header_obj.additional_ref1}"
@invalid_receipts << @receipt_header_obj.additional_ref1
end
end
if try_to_import && @receipt_header_obj
rcil = ReceiptItemLine.new
if @receipt_header_obj.owner_required?
begin
rel = @depot.products.where(code1: row_hash[:product], owner_detail_id: @receipt_header_obj.owner_detail_id)
rcil.product_id = rel.try(:first).try(:id)
rescue
add_to_errors "problem with owner #{ @receipt_header_obj.try(:owner_detail).try(:owner_code) }"
end
else
rcil.product_id = @depot.products.where(code1: row_hash[:product]).first.try(:id)
end
rcil.storage_quantity = row_hash[:quantity]
rcil.location_code = row_hash[:location_code]
rcil.putaway_done = row_hash[:putaway_done]
rcil.pallet_ref = row_hash[:pallet_ref]
begin
rcil.passed_rotate_by_date = row_hash[:rotation_date]
rcil.rotate_by_date = rcil.passed_rotate_by_date
rescue
puts 'invalid date detected'
end
rcil.receipt_id = @receipt_header_obj.id
rcil.secondary_quantity = row_hash[:secondary_quantity]
rcil.product_code = row_hash[:product]
rcil.batch_no = row_hash[:batch_no]
rcil.split_stock = row_hash[:split_stock]
rcil.passed_customer_stock_status = row_hash[:stock_status]
stock_status = @depot.company.customer_stock_statuses.where(name: row_hash[:stock_status].to_s.capitalize).try(:first)
# This will ignore misspelled stock statuses
# TODO: ask James or David
rcil.customer_stock_status_id = stock_status.try(:id)
rcil.current_step = 'confirmation'
# byebug if rcil.all_valid? == false
if rcil.all_valid? && rcil.valid_putaway?
rcil.save
else
puts "\n\n %%%%%%%%%%%%%%% failed item line validation " + rcil.errors.try(:messages).to_s
add_to_errors human_errors(rcil)
@invalid_receipts << @receipt_header_obj.additional_ref1
@invalid_items << row_hash[:additional_ref1]
end
end
end
def human_errors(obj)
error_string = []
obj.errors.messages.each do |err_msg|
error_string << err_msg.second.collect{ |em| "#{err_msg.first.to_s.humanize} #{translate_error(obj, err_msg, em) }" }.join(' and ')
end
error_string.join(' - ')
end
def translate_error(obj, err_msg, em)
if err_msg.first == :product_code
"#{em} - #{obj[:product_code]}"
else
em
end
end
def receipt_items_cleanup
if @receipt_header_obj
if @invalid_receipts.include? @receipt_header_obj.additional_ref1
# delete receipt header if it was marked as invalid
# after checking it's receipt items
@receipt_header_obj.destroy
else
@imported_receipts << @receipt_header_obj.additional_ref1
end
end
@receipt_header_obj = nil
end
def correct?(header)
if @record_types.keys.include?(header)
true
else
add_to_errors "Header #{header} is not valid"
false
end
end
def errors
@found_errors if @found_errors.size > 0
end
def add_to_errors(message, totals = false)
if totals
@found_errors << [@line_count, 'totals', message]
else
@found_errors << [@line_count, @current_row[1], message]
end
end
##################### except report, the other methods should go elsewhere and be reused if Rails do not provide better alternatives
def humanize_number_all_no(number, all, singular_string = '')
if number == 0
'no ' + singular_string.pluralize(number)
elsif number == all
if number == 1
'one ' + singular_string
else
'all ' + singular_string.pluralize(number)
end
elsif number > 0 && number < all
number.to_s + ' ' + singular_string.pluralize(number)
else
raise 'number out of expected range'
end
end
def was_were(number)
number == 1 ? 'was' : 'were'
end
def my_to_sentence(array) # rails version inserts comma before and
if array.length > 1
initials = array[0..(array.length - 2)]
[initials.join(', '), array.last].join(' and ')
else
array.first
end
end
def report
all_num = self.total_receipts
err_num = self.invalid_receipts.uniq.size
imp_num = self.imported_receipts.size
skip_num = self.skipped_receipts.size
invalid_string = "There #{was_were(err_num)}#{err_num == 1 ? ' an ' : ' '}#{'error'.pluralize(err_num)} in #{humanize_number_all_no(err_num, all_num, 'receipt')}"
imported_string = "#{humanize_number_all_no imp_num, all_num, 'receipt'} #{was_were(imp_num)} imported successfully"
skipped_string = "#{humanize_number_all_no skip_num, all_num, 'receipt'} #{was_were(skip_num)} skipped"
report_list = []
report_list << invalid_string unless self.invalid_receipts.size.zero?
report_list << imported_string unless self.skipped_receipts.size == self.total_receipts
report_list << skipped_string unless self.skipped_receipts.size.zero?
if report_list.blank?
imported_string
else
my_to_sentence report_list
end.capitalize + '.'
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment