module Comee module Core class CustomerOrderService ## # This method converts a price value per a unit of measure to a price value per another # unit of measure. # # @param price [Numeric] the price value # @param unit [Numeric] the unit id of measure for the price # @param new_unit [Numeric] the new unit id of measure # # @return [Numeric] the converted price for the new unit # def convert_price(price, unit_id, new_unit_id) return price if unit_id == new_unit_id conversions = UnitConversion.where("from_id = ? AND to_id = ?", unit_id, new_unit_id) .or(UnitConversion.where("from_id = ? AND to_id = ?", new_unit_id, unit_id)) unless conversions.count.positive? units = Unit.where(id: [unit_id, new_unit_id]).select(:code) raise(StandardError, "There is no conversion factor between #{units[0].code} and #{units[1].code}.") end conversion = conversions.first return (price / conversion.factor).round(2) if unit_id == conversion.from_id && new_unit_id == conversion.to_id (price * conversion.factor).round(2) end ## # This method converts a price value per a unit of measure to a price value per another # unit of measure. # # @param quantity [Numeric] the price value # @param unit_id [Numeric] the unit id of measure for the price # @param new_unit_id [Numeric] the new unit id of measure # # @return [Numeric] the converted price for the new unit # def convert_quantity(quantity, unit_id, new_unit_id) return quantity if unit_id == new_unit_id conversions = UnitConversion.where("from_id = ? AND to_id = ?", unit_id, new_unit_id) .or(UnitConversion.where("from_id = ? AND to_id = ?", new_unit_id, unit_id)) unless conversions.count.positive? units = Unit.where(id: [unit_id, new_unit_id]) raise(StandardError, "There is no conversion factor between #{units[0].code} and #{units[1].code}.") end conversion = conversions.first return (quantity * conversion.factor).round if unit_id == conversion.from_id && new_unit_id == conversion.to_id (quantity / conversion.factor).round end def suggest_values(item_id, new_unit_id) item = CustomerOrderItem.includes(:customer_order).find_by(id: item_id) raise(StandardError, "Order item with id `#{item_id}` not found.") unless item quantity = convert_quantity(item.quantity, item.unit_id, new_unit_id) client_price = ClientPrice.find_by(product_id: item.product_id, client_id: item.customer_order.client_id) if client_price price = convert_price(client_price.price, client_price.unit_id, new_unit_id) else master_price = MasterPrice.find_by(product_id: item.product_id, primary: true) raise(StandardError, "No price entry could be found for product `#{item.product.code}`.") unless master_price price = convert_price(master_price.selling_price, master_price.unit_id, new_unit_id) end {quantity: quantity, price: price} end def submit(id) order = CustomerOrder.find_by(id: id) raise(StandardError, "Customer order with id `#{id}` not found.") unless order unless CustomerOrder.statuses[order.status] == CustomerOrder.statuses[:draft] raise(StandardError, "Customer order should be in draft state.") end raise(StandardError, "Customer order should have at least one item.") unless order.customer_order_items.count.positive? order.update!(status: CustomerOrder.statuses[:submitted]) order end def submit_for_confirmation(id) order = CustomerOrder.find_by(id: id) raise(StandardError, "Customer order with id `#{id}` not found.") unless order unless CustomerOrder.statuses[order.status] == CustomerOrder.statuses[:submitted] raise(StandardError, "Customer order should be in submitted state.") end raise(StandardError, "Customer order does not have any items.") if order.customer_order_items.count.zero? order.update!(status: CustomerOrder.statuses[:awaiting_confirmation]) order end def accept(id) order = CustomerOrder.find_by(id: id) raise(StandardError, "Customer order with id `#{id}` not found.") unless order if CustomerOrder.statuses[order.status] == CustomerOrder.statuses[:accepted] raise(StandardError, "Customer order is already in accepted state.") end allowed_statuses = [CustomerOrder.statuses[:submitted], CustomerOrder.statuses[:awaiting_confirmation]] unless allowed_statuses.include?(CustomerOrder.statuses[order.status]) raise(StandardError, "Customer order should be in submitted or awaiting confirmation state.") end if order.customer_order_items.count.zero? || order.customer_order_items.all?(&:canceled?) raise(StandardError, "Customer order does not have any items.") end sales_order = nil CustomerOrder.transaction do sales_order = SalesOrder.create!( order_number: order.order_number, customer_order: order, status: SalesOrder.statuses[:draft] ) order_items = order.customer_order_items.includes(:product, :unit) client_prices = ClientPrice.where(client: order.client, product_id: order_items.map(&:product_id)) master_prices = MasterPrice.where(primary: true, status: Price.statuses[:current], product: order_items.map(&:product)) items = order_items.each_with_object([]) do |item, res| next if item.canceled? default_unit = item.product.default_unit unit_id = default_unit ? default_unit["id"] : item.unit_id quantity = convert_quantity(item.quantity, item.unit_id, unit_id) client_price = client_prices.find { |price| price.product_id == item.product_id } master_price = master_prices.find { |price| price.product_id == item.product_id } if client_price price = convert_price(client_price.price, client_price.unit_id, unit_id) else raise(StandardError, "No price entry could be found for product `#{item.product.code}`.") unless master_price price = convert_price(master_price.selling_price, master_price.unit_id, unit_id) end res << { sales_order_id: sales_order.id, customer_order_item_id: item.id, product_id: item.product_id, unit_id: unit_id, customer_item_no: item.product.code, quantity: quantity, price: price, delivery_date: item.delivery_date, lead_time: master_price&.lead_time } end SalesOrderItem.insert_all!(items) order.update!(status: CustomerOrder.statuses[:accepted]) end sales_order end def cancel(id) order = CustomerOrder.find_by(id: id) raise(StandardError, "Customer order with id `#{id}` not found.") unless order if CustomerOrder.statuses[order.status] == CustomerOrder.statuses[:canceled] raise(StandardError, "Customer order is already canceled.") end order.update!(status: CustomerOrder.statuses[:canceled]) order end end end end