Skip to content

Instantly share code, notes, and snippets.

@nsave
Created November 21, 2017 19:36
Show Gist options
  • Select an option

  • Save nsave/524810c50651d510d5b0fd983ff5a0bd to your computer and use it in GitHub Desktop.

Select an option

Save nsave/524810c50651d510d5b0fd983ff5a0bd to your computer and use it in GitHub Desktop.
Rails code example
# == Schema Information
#
# Table name: orders
#
# id :integer not null, primary key
# eid :string(255)
# location_id :integer
# customer_id :integer
# created_at :datetime
# creator_client_id :integer
# created_by :string(255)
# service_type :string(255)
# expected_time :datetime
# confirmed_time :datetime
# status :string(255)
# customer_notes :text(65535)
# total :string(255)
# total_discrepancy :string(255)
# c_email :string(255)
# c_first_name :string(255)
# c_last_name :string(255)
# c_company_name :string(255)
# c_phone :string(255)
# c_address_1 :string(255)
# c_address_2 :string(255)
# c_postal_code :string(255)
# c_city :string(255)
# c_state :string(255)
# c_country :string(255)
# c_latitude :decimal(9, 6)
# c_longitude :decimal(9, 6)
# c_delivery_notes :text(65535)
# payment_discrepancy :string(255)
#
# Indexes
#
# index_orders_on_customer_id (customer_id)
# index_orders_on_location_id (location_id)
#
class HrOrder < HrModel
self.table_name = 'orders'
STATUSES = [
NEW_STATUS = 'new',
RECEIVED_STATUS = 'received',
ACCEPTED_STATUS = 'accepted',
IN_PREPARATION_STATUS = 'in_preparation',
AWAITING_SHIPMENT_STATUS = 'awaiting_shipment',
AWAITING_COLLECTION_STATUS = 'awaiting_collection',
IN_DELIVERY_STATUS = 'in_delivery',
COMPLETED_STATUS = 'completed',
REJECTED_STATUS = 'rejected',
CANCELLED_STATUS = 'cancelled',
DELIVERY_FAILED_STATUS = 'delivery_failed',
]
belongs_to :hr_location, foreign_key: :location_id
belongs_to :hr_customer, foreign_key: :customer_id
has_one :hr_account, through: :hr_location
with_options foreign_key: :order_id, dependent: :destroy, inverse_of: :hr_order do
has_many :hr_order_deals
has_many :hr_order_items
has_many :hr_order_charges
has_many :hr_order_discounts
has_many :hr_order_payments
has_many :hr_loyalty_operations
has_many :hr_private_refs, class_name: HrOrderPrivateRef
end
has_many :hr_connection_logs, as: :resource
belongs_to :hr_client, foreign_key: :creator_client_id
accepts_nested_attributes_for :hr_order_items,
:hr_order_charges,
:hr_order_deals,
:hr_order_discounts,
:hr_order_payments,
:hr_private_refs,
:hr_loyalty_operations
money_accessor :total
money_accessor :total_discrepancy
money_accessor :payment_discrepancy
validates :total, money: true
validates_presence_of :hr_location
validates_inclusion_of :status, in: STATUSES
validates_length_of :c_email,
:c_first_name,
:c_last_name,
:c_company_name,
:c_phone,
:c_address_1,
:c_address_2,
:c_postal_code,
:c_city,
:c_state,
:c_country, maximum: 255
validates_length_of :customer_notes,
:c_delivery_notes, maximum: 65355
include PrivateReferable
scope :with_private_ref, ->(hr_client, hr_location, private_ref) {
joins(:hr_private_refs).where(
'order_private_refs.client_id = ? && order_private_refs.location_id = ? && order_private_refs.private_ref = ?',
hr_client.id, hr_location.id, private_ref
)
}
delegate :hr_observing_callbacks, to: :hr_location
delegate :currency, to: :hr_account
def recalculate_total
items_total = hr_order_items.map(&:subtotal_m).inject(hr_location.hr_account.null_money, &:+)
charges_total = hr_order_charges.map(&:charge_price_m).inject(hr_location.hr_account.null_money, &:+)
total = items_total + charges_total
final_total = hr_order_discounts.inject(total) do |total, hr_order_discount|
hr_order_discount.discount.apply(total)
end
self.total = final_total.to_s
end
end
require 'rails_helper'
describe Parsers::HrOrder::ParserCreate do
let(:hr_account) { create(:hr_account, :with_resources, owner: create(:hr_user), locations_count: 2) }
let(:hr_account_2) { create(:hr_account, :with_resources, owner: create(:hr_user), locations_count: 2) }
let(:hr_location_1) { hr_account.hr_locations.first }
let(:hr_location_2) { hr_account.hr_locations.second }
let(:hr_catalog_1) { hr_account.hr_all_catalogs.first }
let(:hr_product_1_1) { hr_catalog_1.hr_products.first }
let(:hr_sku_1_1_1) { hr_product_1_1.hr_skus.first }
let(:hr_sku_1_1_2) { hr_product_1_1.hr_skus.last }
let(:hr_charge_1) { hr_catalog_1.hr_charges.first }
let(:hr_catalog_2) { hr_account_2.hr_all_catalogs.first }
let(:hr_product_2_1) { hr_catalog_2.hr_products.first }
let(:hr_sku_2_1_1) { hr_product_2_1.hr_skus.first }
let(:hr_charge_2) { hr_catalog_2.hr_charges.first }
let(:hr_customer_list_1) { hr_account.hr_customer_lists.first }
let(:hr_customer_list_2) { hr_account_2.hr_customer_lists.first }
let(:hr_customer_1) { create(:hr_customer, hr_customer_list: hr_customer_list_1) }
let(:hr_customer_2) { create(:hr_customer, hr_customer_list: hr_customer_list_2) }
let(:hr_client) { create(:hr_client) }
let(:catalogs_repository) { HrCatalog::CatalogsRepository.new(hr_account.hr_all_catalogs) }
let(:customers_repository) { HrCustomer::CustomersRepository.new(hr_account.hr_all_customer_lists) }
def service
Parsers::HrOrder::ParserCreate.new(
hr_location_1, hr_client, catalogs_repository, customers_repository
)
end
it 'builds order' do
new_hr_order = service.build(
status: 'new'
)
expect(new_hr_order).to be_valid
end
describe 'validation' do
it 'fails if private_ref is not uniq in scope of client and location' do
hr_order_1 = create(:hr_order, hr_location: hr_location_1, hr_customer: hr_customer_1)
ref_1 = generate_ref
hr_order_1.hr_private_refs.create(hr_client: hr_client, hr_location: hr_order_1.hr_location, private_ref: ref_1)
new_hr_order = service.build(
status: 'new',
private_ref: ref_1
)
expect(new_hr_order).to_not be_valid
end
end
describe 'total field' do
it 'assigns total from request' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
total: '100 EUR'
)
expect(new_hr_order.total_m.cents).to eq(100_00)
end
it 'records dif of totals in total_discrepancy' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
total: '100 EUR'
)
expect(new_hr_order.total_discrepancy_m.cents).to eq(-100_00)
end
it 'fails if total is wrong currency' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
total: '100 USD'
)
expect(new_hr_order).to be_invalid
end
end
describe 'with customer' do
it 'fails if customer is out of scope' do
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_2.encoded_id
)
expect(new_hr_order).to be_invalid
end
it 'assigns customer by id' do
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_1.encoded_id
)
expect(new_hr_order.customer_id).to eq(hr_customer_1.id)
end
it 'assigns customer by customer_list_id and private_ref' do
private_ref = generate_ref
hr_customer_1.hr_private_refs.create!(private_ref: private_ref, hr_customer_list: hr_customer_1.hr_customer_list, hr_client: hr_client)
new_hr_order = service.build(
status: 'new',
customer_list_id: hr_customer_1.hr_customer_list.encoded_id,
customer_private_ref: private_ref
)
expect(new_hr_order.customer_id).to eq(hr_customer_1.id)
end
it 'fails if customer_id passed with customer_list_id and customer_private_ref' do
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_1.encoded_id,
customer_list_id: hr_customer_1.hr_customer_list.encoded_id,
customer_private_ref: generate_ref
)
expect(new_hr_order).to be_invalid
end
it 'fails if customer_private_ref passed without customer_list_id' do
new_hr_order = service.build(
status: 'new',
customer_private_ref: generate_ref
)
expect(new_hr_order).to be_invalid
end
it 'copies customer fileds to new order' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
customer_id: hr_customer_1.encoded_id
)
expect(new_hr_order.c_first_name).to eq(hr_customer_1.first_name)
expect(new_hr_order.c_last_name).to eq(hr_customer_1.last_name)
end
end
describe 'with items' do
subject(:new_hr_order) do
service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 2,
points_earned: 10
},
{
product_name: 'New product',
price: '5.00 EUR',
quantity: 1,
points_used: 15
}
]
)
end
it 'builds order with items' do
aggregate_failures do
expect(new_hr_order).to be_valid
expect(new_hr_order.hr_order_items.size).to eq(2)
expect(new_hr_order.total).to eq('25.00 EUR')
expect(new_hr_order.hr_order_items.first.points_earned).to eq(10)
expect(new_hr_order.hr_order_items.last.points_used).to eq(15)
end
end
it 'fails with invalid item price currency' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 GBP',
quantity: 2,
points_earned: 10
}
]
)
expect(new_hr_order).to be_invalid
end
it 'ignores order item subtotal' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: hr_sku_1_1_1.price,
quantity: 1,
subtotal: (hr_sku_1_1_1.price_m * 2).to_s
}
]
)
expect(new_hr_order.hr_order_items.first.subtotal_m.cents).to eq(hr_sku_1_1_1.price_m.cents)
end
it 'builds order items with options' do
hr_option = hr_sku_1_1_1.hr_option_lists.take.hr_options.take
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10.00 EUR',
quantity: 1,
options: [
{
ref: hr_option.ref,
name: hr_option.name,
option_list_name: hr_option.hr_option_list.name,
price: hr_option.price,
removed: 'true'
}
]
}
]
)
hr_order_item_option = new_hr_order.hr_order_items.first.hr_order_item_options.first
expect(hr_order_item_option.price).to eq(hr_option.price)
expect(hr_order_item_option.option_list_name).to eq(hr_option.hr_option_list.name)
end
it 'fails with invalid options currency' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10.00 EUR',
quantity: 1,
options: [
{
option_list_name: 'Colors',
name: 'Black',
price: '1 GBP'
}
]
}
]
)
expect(new_hr_order).to be_invalid
end
it 'adds option price to order total' do
price = '1 EUR'
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: hr_sku_1_1_1.price,
quantity: 1,
options: [
{
option_list_name: 'Colors',
name: 'Black',
price: price
}
]
}
]
)
expect(new_hr_order.total_m).to eq(hr_sku_1_1_1.price_m + Util::Money.from_string(price))
end
it 'fails if item is invalid' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
product_ref: generate_ref,
quantity: 2,
}
]
)
expect(new_hr_order).to be_invalid
end
end
describe 'with charges' do
it 'fails without charge name' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
charges: [
{
type: HrCharge::DELIVERY_TYPE,
price: '10 EUR'
}
]
)
expect(new_hr_order).to be_invalid
expect(new_hr_order.hr_order_charges.first).to be_invalid
end
it 'fails without charge type' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
charges: [
{
name: "Carge #{generate_ref}",
price: '10 EUR'
}
]
)
expect(new_hr_order).to be_invalid
expect(new_hr_order.hr_order_charges.first).to be_invalid
end
it 'fails without charge price' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
charges: [
{
type: HrCharge::DELIVERY_TYPE,
name: "Charge #{generate_ref}"
}
]
)
expect(new_hr_order).to be_invalid
expect(new_hr_order.hr_order_charges.first).to be_invalid
end
it 'failes with invalid charge currency' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
charges: [
{
name: "Charge #{generate_ref}",
type: HrCharge::DELIVERY_TYPE,
price: '10 GBP'
}
]
)
expect(new_hr_order).to be_invalid
end
it 'builds order charge' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
charges: [
{
name: "Charge #{generate_ref}",
type: HrCharge::DELIVERY_TYPE,
price: '10 EUR'
}
]
)
expect(new_hr_order).to_not be_invalid
expect(new_hr_order.hr_order_charges.first).to_not be_invalid
end
end
describe 'with discounts' do
it 'fails with invalid discount' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
discounts: [
{
pricing_effect: HrDiscount::Discount::PRICING_EFFECT_PERCENTAGE_OFF,
pricing_value: '10'
}
]
)
expect(new_hr_order).to be_invalid
expect(new_hr_order.hr_order_discounts.first).to be_invalid
end
it 'builds order discount' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
discounts: [
{
name: 'DISCOUNT',
pricing_effect: HrDiscount::Discount::PRICING_EFFECT_PERCENTAGE_OFF,
pricing_value: '10'
}
]
)
expect(new_hr_order).to_not be_invalid
expect(new_hr_order.hr_order_discounts.first).to_not be_invalid
end
it 'applies percentage discount' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 2
}
],
discounts: [
{
name: 'DISCOUNT',
pricing_effect: HrDiscount::Discount::PRICING_EFFECT_PERCENTAGE_OFF,
pricing_value: 10
}
]
)
expect(new_hr_order.total_m.cents).to eq(1800)
end
it 'applies fixed discount' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 2
}
],
discounts: [
{
name: 'DISCOUNT',
pricing_effect: HrDiscount::Discount::PRICING_EFFECT_PRICE_OFF,
pricing_value: '3 EUR'
}
]
)
expect(new_hr_order.total_m.cents).to eq(1700)
end
it 'fails with invalid discount currency' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 2
}
],
discounts: [
{
name: 'DISCOUNT',
pricing_effect: HrDiscount::Discount::PRICING_EFFECT_PRICE_OFF,
pricing_value: '3 GBP'
}
]
)
expect(new_hr_order).to be_invalid
end
end
describe 'with deals' do
it 'builds order deals' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
deals: {
'some_deal_key' => {
name: 'Some deal',
ref: 'Some ref'
}
}
)
expect(new_hr_order).to be_valid
expect(new_hr_order.hr_order_deals.size).to eq(1)
end
it 'builds order deal lines' do
deal_key = 'some_deal_key'
# call #run in this example to save to db for testing relations after saving
new_hr_order = service.run(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 1,
deal_line: {
deal_key: deal_key,
pricing_effect: HrDealLine::DealLine::PRICING_EFFECT_PRICE_OFF,
pricing_value: '1 EUR'
}
}
],
deals: {
deal_key => {
name: 'Some deal',
ref: 'Some ref'
}
}
)
new_hr_order.reload
expect(new_hr_order).to be_valid
expect(new_hr_order.hr_order_deals.size).to eq(1)
expect(new_hr_order.hr_order_items.first.hr_order_deal).to_not be_nil
end
it 'applies order deals to total' do
deal_key = 'some_deal_key'
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
items: [
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 1,
deal_line: {
deal_key: deal_key,
pricing_effect: HrDealLine::DealLine::PRICING_EFFECT_UNCHANGED
}
},
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 1,
deal_line: {
deal_key: deal_key,
pricing_effect: HrDealLine::DealLine::PRICING_EFFECT_FIXED_PRICE,
pricing_value: '8 EUR'
}
},
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 1,
deal_line: {
deal_key: deal_key,
pricing_effect: HrDealLine::DealLine::PRICING_EFFECT_PRICE_OFF,
pricing_value: '1 EUR'
}
},
{
product_name: 'Margarita',
price: '10 EUR',
quantity: 1,
deal_line: {
deal_key: deal_key,
pricing_effect: HrDealLine::DealLine::PRICING_EFFECT_PERCENTAGE_OFF,
pricing_value: '20'
}
}
],
deals: {
deal_key => {
name: 'Some deal',
ref: 'Some ref'
}
}
)
expect(new_hr_order.total_m.cents).to eq(3500)
end
end
describe 'with payments' do
it 'builds nested payments' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
payments: [
{
type: HrOrderPayment::PAYMENT_TYPES.sample,
amount: '10 EUR'
},
{
type: HrOrderPayment::PAYMENT_TYPES.sample,
amount: '20 EUR'
}
]
)
expect(new_hr_order).to be_valid
expect(new_hr_order.hr_order_payments.size).to eq(2)
end
it 'sets payment_discrepancy if payments total is different from order total' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
payments: [
{
type: HrOrderPayment::PAYMENT_TYPES.sample,
amount: '10 EUR'
}
]
)
expect(new_hr_order.payment_discrepancy_m).to eq(Util::Money.from_string('-10 EUR'))
end
it 'sets payment_discrepancy to zero if there are no discrepancy' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref
)
expect(new_hr_order.payment_discrepancy_m).to eq(Util::Money.from_string('0 EUR'))
end
it 'fails if payment is invalid' do
new_hr_order = service.build(
status: 'new',
private_ref: generate_ref,
payments: [
{
type: HrOrderPayment::PAYMENT_TYPES.sample,
amount: '10 USD'
}
]
)
expect(new_hr_order).to be_invalid
end
end
describe 'with loyalty operations' do
it 'builds loyalty operation' do
hr_loyalty_card = hr_customer_1.hr_loyalty_cards.take
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_1.encoded_id,
loyalty_operations: [
{
name: hr_loyalty_card.name,
delta: 10,
reason: 'Order'
}
]
)
expect(new_hr_order.hr_loyalty_operations.size).to be(1)
expect(new_hr_order.hr_loyalty_operations.first.loyalty_card_id).to eq(hr_loyalty_card.id)
end
it 'updates loyalty card balance' do
hr_loyalty_card = hr_customer_1.hr_loyalty_cards.take
delta = 10
expect {
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_1.encoded_id,
loyalty_operations: [
{
name: hr_loyalty_card.name,
delta: delta,
reason: 'Order'
}
]
)
}.to change{hr_loyalty_card.reload.balance}.by(delta)
end
it 'builds new loyalty card with operation' do
delta = 10
expect {
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_1.encoded_id,
loyalty_operations: [
{
name: "NewCard#{generate_ref}",
delta: delta,
reason: 'Order'
}
]
)
hr_loyalty_card = new_hr_order.hr_loyalty_operations.first.hr_loyalty_card
expect(hr_loyalty_card.balance).to eq(delta)
}.to change{hr_customer_1.hr_loyalty_cards.size}.by(1)
end
it 'fails if loyalty operation is invalid' do
hr_loyalty_card = hr_customer_1.hr_loyalty_cards.take
new_hr_order = service.build(
status: 'new',
customer_id: hr_customer_1.encoded_id,
loyalty_operations: [
{
name: hr_loyalty_card.name,
reason: 'Order'
}
]
)
expect(new_hr_order).to be_invalid
end
end
end
module V1
class OrdersController < BaseController
before_action :authorize_access_level!
before_action :ensure_hr_location_found!, except: [:account_orders_index]
before_action :ensure_hr_account_found!, only: [:account_orders_index]
before_action :ensure_hr_order_found!, except: [:index, :account_orders_index, :create]
before_action :ensure_filter_params_valid!, only: [:index, :account_orders_index]
def index
if policy(current_hr_location).read_orders?
respond_with_resources!(
filter_orders(current_hr_location.hr_orders)
)
else
respond_with_error!(:forbidden, message: "A 'orders' read scope is required")
end
end
def account_orders_index
if policy(current_hr_account).read_orders?
respond_with_resources!(
filter_orders(current_hr_account.hr_orders)
)
else
respond_with_error!(:forbidden, message: "A 'orders' read scope is required")
end
end
def show
if policy(current_hr_order).read?
respond_with_resource!(current_hr_order)
else
respond_with_error!(:forbidden, message: "A 'orders' read scope is required")
end
end
def create
if policy(current_hr_location).create_order?
hr_order = HrOrder.create_from_json(
current_hr_location,
current_hr_client,
curent_catalogs_repository,
curent_customers_repository,
params
)
if hr_order.valid?
events_manager.detect_resource_create_events(current_hr_client, hr_order)
HrApiCounter.find_or_create_for(current_hr_location, HrApiCounter::NAME_CREATE_ORDER).count!
if hr_customer = hr_order.hr_customer
events_manager.detect_resource_update_events(current_hr_client, hr_customer) do
hr_customer.handle_new_order!(hr_order)
end
end
end
respond_with_modified_resource!(hr_order)
else
respond_with_error!(:forbidden, message: "A 'orders' write scope is required")
end
end
def update
if policy(current_hr_order).write?
events_manager.detect_resource_update_events(current_hr_client, current_hr_order) do
current_hr_order.update_from_json(current_hr_client, params)
end
respond_with_modified_resource!(current_hr_order)
else
respond_with_error!(:forbidden, message: "A 'orders' write scope is required")
end
end
protected
def filter_orders(hr_orders_relation)
OrdersFilter.process(hr_orders_relation, orders_filter_params,
hr_client: current_hr_client,
hr_location: current_hr_location
)
end
def orders_filter_params
@orders_filter_params ||= OrdersFilter::Params.from_h(params)
end
def ensure_filter_params_valid!
if orders_filter_params.invalid?
respond_with_error!(:unprocessable_entity, message: orders_filter_params.errors.full_messages.first)
end
end
def curent_catalogs_repository
HrCatalog::CatalogsRepository.new(
policy_scope(current_hr_location.hr_all_catalogs)
)
end
def curent_customers_repository
HrCustomer::CustomersRepository.new(
policy_scope(current_hr_location.hr_all_customer_lists)
)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment