module Comee module Core class SalesOrderService ## # This method returns { success: true} if all back orders in the +params+ created successfully. # The +params+ contain array of back orders with the corresponding products. # The exact structure of +params+ looks like the following # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '', # back_order_items: [{product_id: '', requested_quantity: ''},...] }...] def create_back_orders(params) check_product_prices(params) check_duplicate_order_number(params) bulk_create_back_order(params) end ## # This method returns true if all products under each back order in +params+ have active price in the master price table. # The +params+ contain array of back orders with the corresponding products. # The exact structure of +params+ looks like the following # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '', # back_order_items: [{product_id: '', requested_quantity: ''},...] }...] def check_product_prices(params) queries = [] params.each do |param| param[:back_order_items].map do |bo_item| queries << {supplier_id: param[:supplier_id], product_id: bo_item[:product_id]} end end master_prices = queries.inject(Comee::Core::MasterPrice.none) do |conditions, condition| conditions.or(Comee::Core::MasterPrice.where(condition)) end raise(StandardError, compose_error_messge(queries, master_prices)) unless queries.count == master_prices.count true end ## # This method composes an error message when called from check_product_prices. # It recieves +queries+ and +master_prices+ as a parameter # The +queries+ is an array containing list of conditions that will be joined using or connector # The +queries+ param looks like [{supplier_id: '', product_id: '', active: true},...] # The +master_prices+ param contains master prices fetched by the queries param given above # The message is composed by computing difference b/n the queries and master prices params def compose_error_messge(queries, master_prices) result = master_prices.map { |mp| {supplier_id: mp[:supplier_id], product_id: mp[:product_id]} } diff = queries - result product_names = Comee::Core::Product.where(id: diff.map { |dif| dif[:product_id] }).pluck("name").join(", ") supplier_names = Comee::Core::Supplier.where(id: diff.map { |dif| dif[:supplier_id] }).pluck("name").join(", ") "The following products #{product_names} do not have price entry for the following suppliers" \ " #{supplier_names} in the master price table or the prices are not valid." end ## # This method returns false if there is no duplicate order number, otherwise it raises an error. # The +params+ contain array of back orders with the corresponding products. # The exact structure of +params+ looks like the following # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '', # back_order_items: [{product_id: '', requested_quantity: ''},...] }...] # The method first checks if there is duplicate order number within in the +params+ array, and if not then it checks # for duplicate order number from back order table. def check_duplicate_order_number(params) order_numbers = params.map { |param| param[:order_number] } unless order_numbers.uniq.count == order_numbers.count raise(StandardError, "Duplicate order number(s) #{order_numbers.select do |e| order_numbers.count(e) > 1 end.uniq.join(', ')} exist in the payload.") end duplicate_order_numbers = Comee::Core::BackOrder.where(order_number: order_numbers) unless duplicate_order_numbers.count.zero? raise(StandardError, "Duplicate order number(s) #{duplicate_order_numbers.map(&:order_number).join(', ')} exist in the back order table.") end false end ## # This method returns items aggregated by product for a supplier. # It recieves +items+, +supplier_id+ and +back_order_id+ as a parameter # The +items+ is an array containing list of back order items # The +items+ param looks like [{product_id: '', requested_quantity: ''},...] # The +supplier_id+ is the id of the supplier under which the items belong to # The +back_order_id+ is the id of the backer order under which the items belong to def aggregate_back_order_item(items, supplier_id, back_order_id, delivery_date) items.group_by { |item| item[:product_id] }.map do |product_id, aggregated_items| total_quantity = aggregated_items.reduce(0) { |sum, agg_item| agg_item[:requested_quantity] + sum } {product_id: product_id, requested_quantity: total_quantity, supplier_quantity: total_quantity, supplier_id: supplier_id, back_order_id: back_order_id, delivery_date: delivery_date} end end ## # This method assigns current purchase price for a product under a given supplier. # It recieves +items+ containing array of products along with the corresponding suppliers # The +items+ param looks like [ # {product_id: '', requested_quantity: '', supplier_quantity: '', supplier_id: '', back_order_id: '' }, # ... # ] # It assigns price by first fetching the price of products in the items array and then iterate through the # items array and find the corresponding price from the fetched price def assign_price_to_items(items) queries = [] items.map { |bo_item| queries << {supplier_id: bo_item[:supplier_id], product_id: bo_item[:product_id]} } master_prices = queries.inject(Comee::Core::MasterPrice.none) do |conditions, condition| conditions.or(Comee::Core::MasterPrice.where(condition)) end items.each do |item| mp = master_prices.find { |price| price.supplier_id == item[:supplier_id] && price.product_id == item[:product_id] } item[:requested_unit_price] = mp.purchase_price item[:supplier_unit_price] = mp.purchase_price item[:from] = mp.valid_from item[:to] = mp.valid_from item.delete(:supplier_id) end end ## # This method returns { success: true} if all back orders in the +params+ created successfully. # The +params+ contain array of back orders with the corresponding products. # The exact structure of +params+ looks like the following # [{supplier_id: '', order_number: '', order_date: '', delivery_date: '', delivery_address: '', invoice_address: '', # back_order_items: [{product_id: '', requested_quantity: ''},...] }...] # This method uses insert_all method to create back order and its items in bulk def bulk_create_back_order(params) back_orders = params.map { |p| {**p.except(:back_order_items), created_at: Time.now, updated_at: Time.now} } result = Comee::Core::BackOrder.insert_all!(back_orders, returning: %w[id supplier_id delivery_date]) aggregated_items = [] result.pluck("id", "supplier_id", "delivery_date").each do |r| supplier_detail = params.find { |p| p[:supplier_id].to_i == r[1] } aggregated_items << aggregate_back_order_item(supplier_detail[:back_order_items], r[1], r[0], r[2]) end Comee::Core::BackOrderItem.insert_all!(assign_price_to_items(aggregated_items.flatten)) {success: true} rescue StandardError => e {success: false, error: e.message} end end end end