module Comee module Core class CustomerOrderService def create(params) order = nil params = params.with_indifferent_access ActiveRecord::Base.transaction do order_params = params.except(:items) order = Comee::Core::CustomerOrder.create(**order_params) items = params.slice(:items)[:items] items.each do |item| item[:customer_order_id] = order.id item[:total_price] = item[:quantity] * item[:price] unless item.key?(:total_price) && !item[:total_price].nil? end Comee::Core::CustomerOrderItem.insert_all!(items) end order end ## # 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_quantity, 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(new_quantity, item.unit_id, new_unit_id) client = Client.find(item.customer_order.client_id) client_id = client.parent_id || client.id client_price = ClientPrice.find_by(product_id: item.product_id, client_id: 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, total_price: quantity * 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, accepted_by = nil) 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, order_date: order.order_date, customer_order: order, status: SalesOrder.statuses[:draft], client: order.client, order_terms: order.order_terms, shipment_address: order.shipment_address, invoice_address: order.invoice_address, delivery_address: order.delivery_address, destination: order.final_destination, handover_date: order.handover_date, shipping_date: order.shipping_date, delivery_date: order.final_delivery_date, consignee: order.consignee, created_by: accepted_by || "" ) order_items = order.customer_order_items.includes(:product, :unit) client_id = order.client.parent_id || order.client_id client_prices = ClientPrice.where( client_id: client_id, product_id: order_items.map(&:product_id), status: ClientPrice.statuses[:current] ) 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.customer_item_no, customer_item_description: item.customer_item_description, quantity: quantity, price: price, total_price: quantity * 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