Created
March 3, 2017 16:40
-
-
Save bigos/9916c086e60827c8e5abf54861b1252b to your computer and use it in GitHub Desktop.
Sample of my Ruby code written 2 years ago
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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