module Comee module Core class CustomerOrderService def create(params) ActiveRecord::Base.transaction do prev_order = Comee::Core::CustomerOrder.find_by(order_number: params[:order_number]) if prev_order && Comee::Core::CustomerOrderItem.exists?(customer_order_id: prev_order.id) Comee::Core::CustomerOrderItem.where(customer_order_id: prev_order.id).delete_all prev_order.delete end end order = nil params = params.to_h.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, from_id, to_id, quantity, old_price = nil) item = SalesOrderItem.includes(:product, sales_order: :client) .find_by(id: item_id) raise(StandardError, "Order item with id `#{item_id}` not found.") unless item client = item.sales_order.client client_id = client.parent_id || client.id client_price = old_price unless client_price client_price = ClientPrice.find_by(product_id: item.product_id, client_id: client_id) if client_price client_price = convert_price(client_price.price, client_price.unit_id, from_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 client_price = convert_price(master_price.selling_price, master_price.unit_id, from_id) end end price = convert_price(client_price, from_id, to_id) new_quantity = convert_quantity(quantity, from_id, to_id) {quantity: new_quantity, price: price, total_price: (new_quantity * price).round(2)} 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_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 || "", consolidator_date: order.consolidator_date, voyage_no: order.voyage_no, shipping_arrangement: order.shipping_arrangement ) order_items = order.customer_order_items.includes(:product, :unit) client_id = order.client.parent_id || order.client_id product_ids = order_items.map(&:product_id).uniq client_prices = ClientPrice.where( client_id: client_id, product_id: product_ids, status: ClientPrice.statuses[:current] ) master_prices = MasterPrice.includes(:supplier).where(primary: true, status: Price.statuses[:current], product_id: product_ids) 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 } raise(StandardError, "No primary supplier found for product #{item.customer_item_no}") unless master_price product_lookup = master_price.product_lookup order_price = convert_price(item.price, item.unit_id, unit_id) price = if client_price convert_price(client_price.price, client_price.unit_id, unit_id) else 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, serial_no: item.serial_no, customer_item_no: item.customer_item_no, customer_item_alias: item.customer_item_alias, customer_item_description: item.customer_item_description, quantity: quantity, price: price, price_diff: price - order_price, total_price: quantity * price, delivery_date: item.delivery_date, handover_date: order.handover_date, lead_time: master_price&.lead_time, use_alias: item.use_alias, additional_details: { supplier_id: master_price.supplier_id, supplier_name: master_price.supplier.name, supplier_item_no: product_lookup&.code, supplier_description: product_lookup&.item_description, country_of_origin: master_price&.country_of_origin&.name } } end SalesOrderItem.insert_all!(items) order.update!(status: CustomerOrder.statuses[:accepted]) sales_order.calculate_total_price sales_order.calculate_vat sales_order.save! 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