workarea-global_e 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.eslintrc.json +37 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/ci.yml +77 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +261 -0
- data/Gemfile +16 -0
- data/README.md +39 -0
- data/Rakefile +59 -0
- data/app/assets/javascripts/workarea/storefront/global_e/modules/checkout_info_validator.js +122 -0
- data/app/assets/javascripts/workarea/storefront/global_e/modules/suppress_price_ranges.js +19 -0
- data/app/controllers/workarea/admin/country_exceptions_controller.rb +67 -0
- data/app/controllers/workarea/admin/fixed_prices_controller.rb +89 -0
- data/app/controllers/workarea/admin/orders_controller.decorator +6 -0
- data/app/controllers/workarea/storefront/application_controller.decorator +30 -0
- data/app/controllers/workarea/storefront/checkouts_controller.decorator +22 -0
- data/app/controllers/workarea/storefront/ge_checkouts_controller.rb +10 -0
- data/app/controllers/workarea/storefront/global_e_controller.rb +42 -0
- data/app/controllers/workarea/storefront/globale/api_controller.rb +108 -0
- data/app/controllers/workarea/storefront/globale/refund_controller.rb +65 -0
- data/app/controllers/workarea/storefront/users/logins_controller.decorator +13 -0
- data/app/helpers/workarea/admin/global_e_helpers.rb +28 -0
- data/app/models/workarea/address.decorator +11 -0
- data/app/models/workarea/catalog/product.decorator +14 -0
- data/app/models/workarea/fulfillment/status/partially_refunded.rb +13 -0
- data/app/models/workarea/fulfillment/status/refunded.rb +15 -0
- data/app/models/workarea/fulfillment.decorator +55 -0
- data/app/models/workarea/global_e/country_exception.rb +37 -0
- data/app/models/workarea/global_e/fixed_price.rb +93 -0
- data/app/models/workarea/global_e/order_api_events.rb +29 -0
- data/app/models/workarea/order/item.decorator +46 -0
- data/app/models/workarea/order/status/pending_global_e_fraud_check.rb +12 -0
- data/app/models/workarea/order.decorator +64 -0
- data/app/models/workarea/payment/status/global_e_appoved.rb +9 -0
- data/app/models/workarea/payment/status/pending_global_e_fraud_check.rb +9 -0
- data/app/models/workarea/payment/tender/global_e_payment.rb +16 -0
- data/app/models/workarea/payment.decorator +10 -0
- data/app/models/workarea/price_adjustment.decorator +13 -0
- data/app/models/workarea/price_adjustment_set.decorator +25 -0
- data/app/models/workarea/pricing/calculators/item_calculator.decorator +48 -0
- data/app/models/workarea/pricing/collection.decorator +90 -0
- data/app/models/workarea/pricing/discount/appliation_group.decorator +16 -0
- data/app/models/workarea/pricing/discount/international_item_proxy.rb +27 -0
- data/app/models/workarea/pricing/discount/international_order.rb +29 -0
- data/app/models/workarea/pricing/discount.decorator +65 -0
- data/app/models/workarea/pricing/price_distributor.decorator +37 -0
- data/app/models/workarea/pricing/request.decorator +17 -0
- data/app/models/workarea/pricing/sku.decorator +17 -0
- data/app/models/workarea/search/admin/order.decorator +11 -0
- data/app/models/workarea/shipping/sku.decorator +23 -0
- data/app/models/workarea/shipping.decorator +11 -0
- data/app/models/workarea/user.decorator +10 -0
- data/app/seeds/workarea/global_e_seeds.rb +66 -0
- data/app/services/workarea/create_fulfillment.decorator +12 -0
- data/app/services/workarea/global_e/address_details.rb +218 -0
- data/app/services/workarea/global_e/api/notify_order_refunded.rb +45 -0
- data/app/services/workarea/global_e/api/perform_order_payment.rb +46 -0
- data/app/services/workarea/global_e/api/remove_restricted_products.rb +28 -0
- data/app/services/workarea/global_e/api/send_order_to_merchant/item_pricer.rb +147 -0
- data/app/services/workarea/global_e/api/send_order_to_merchant/save_user.rb +59 -0
- data/app/services/workarea/global_e/api/send_order_to_merchant.rb +359 -0
- data/app/services/workarea/global_e/api/update_order_shipping_info.rb +38 -0
- data/app/services/workarea/global_e/api/update_order_status/canceled.rb +42 -0
- data/app/services/workarea/global_e/api/update_order_status.rb +28 -0
- data/app/services/workarea/global_e/attribute.rb +44 -0
- data/app/services/workarea/global_e/brand.rb +19 -0
- data/app/services/workarea/global_e/cart_user_details.rb +34 -0
- data/app/services/workarea/global_e/category.rb +19 -0
- data/app/services/workarea/global_e/checkout_cart_info.rb +358 -0
- data/app/services/workarea/global_e/custom_product_attribute.rb +19 -0
- data/app/services/workarea/global_e/discount.rb +215 -0
- data/app/services/workarea/global_e/merchant/brand.rb +21 -0
- data/app/services/workarea/global_e/merchant/category.rb +21 -0
- data/app/services/workarea/global_e/merchant/customer.rb +36 -0
- data/app/services/workarea/global_e/merchant/customer_details.rb +201 -0
- data/app/services/workarea/global_e/merchant/discount.rb +127 -0
- data/app/services/workarea/global_e/merchant/international_details.rb +296 -0
- data/app/services/workarea/global_e/merchant/order.rb +425 -0
- data/app/services/workarea/global_e/merchant/order_refund.rb +103 -0
- data/app/services/workarea/global_e/merchant/original_order.rb +28 -0
- data/app/services/workarea/global_e/merchant/parcel_tracking.rb +31 -0
- data/app/services/workarea/global_e/merchant/payment_details.rb +161 -0
- data/app/services/workarea/global_e/merchant/product.rb +222 -0
- data/app/services/workarea/global_e/merchant/refund_product.rb +69 -0
- data/app/services/workarea/global_e/merchant/response_info.rb +115 -0
- data/app/services/workarea/global_e/merchant_cart_product_attribute.rb +21 -0
- data/app/services/workarea/global_e/order_status.rb +20 -0
- data/app/services/workarea/global_e/order_status_details.rb +79 -0
- data/app/services/workarea/global_e/order_status_reason.rb +20 -0
- data/app/services/workarea/global_e/parcel.rb +53 -0
- data/app/services/workarea/global_e/product.rb +612 -0
- data/app/services/workarea/global_e/product_meta_data.rb +13 -0
- data/app/services/workarea/global_e/tracking_details.rb +23 -0
- data/app/services/workarea/global_e/update_order_dispatch_exception.rb +46 -0
- data/app/services/workarea/global_e/update_order_dispatch_request.rb +108 -0
- data/app/services/workarea/global_e/user_id_number_type.rb +21 -0
- data/app/services/workarea/global_e/users_details.rb +151 -0
- data/app/services/workarea/global_e/vat_category.rb +20 -0
- data/app/services/workarea/global_e/vat_rate_type.rb +27 -0
- data/app/services/workarea/save_order_analytics.decorator +18 -0
- data/app/view_models/workarea/admin/order_view_model.decorator +13 -0
- data/app/view_models/workarea/storefront/cart_view_model.decorator +7 -0
- data/app/view_models/workarea/storefront/order_item_view_model.decorator +16 -0
- data/app/view_models/workarea/storefront/order_view_model.decorator +45 -0
- data/app/view_models/workarea/storefront/product_view_model.decorator +35 -0
- data/app/views/workarea/admin/catalog_products/_country_exceptions_card.html.haml +31 -0
- data/app/views/workarea/admin/catalog_products/_global_e_attributes.html.haml +3 -0
- data/app/views/workarea/admin/catalog_products/_global_e_fields.html.haml +4 -0
- data/app/views/workarea/admin/country_exceptions/edit.html.haml +48 -0
- data/app/views/workarea/admin/country_exceptions/index.html.haml +47 -0
- data/app/views/workarea/admin/country_exceptions/new.html.haml +44 -0
- data/app/views/workarea/admin/fixed_prices/edit.html.haml +73 -0
- data/app/views/workarea/admin/fixed_prices/index.html.haml +55 -0
- data/app/views/workarea/admin/fixed_prices/new.html.haml +70 -0
- data/app/views/workarea/admin/orders/_global_e.html.haml +27 -0
- data/app/views/workarea/admin/orders/attributes.html.haml +131 -0
- data/app/views/workarea/admin/orders/global_e.html.haml +110 -0
- data/app/views/workarea/admin/orders/show.html.haml +27 -0
- data/app/views/workarea/admin/orders/tenders/_global_e_payment.html.haml +3 -0
- data/app/views/workarea/admin/pricing_skus/_cards.html.haml +98 -0
- data/app/views/workarea/storefront/cart_items/create.html.haml +71 -0
- data/app/views/workarea/storefront/carts/_pricing.html.haml +14 -0
- data/app/views/workarea/storefront/carts/show.html.haml +187 -0
- data/app/views/workarea/storefront/ge_checkouts/show.html.haml +1 -0
- data/app/views/workarea/storefront/global_e/_country_picker.html.haml +1 -0
- data/app/views/workarea/storefront/global_e/_head.html.haml +10 -0
- data/app/views/workarea/storefront/orders/_summary.html.haml +275 -0
- data/app/views/workarea/storefront/orders/tenders/_global_e_payment.html.haml +3 -0
- data/app/views/workarea/storefront/products/_pricing.html.haml +49 -0
- data/app/views/workarea/storefront/products/_restricted_item_text.html.haml +1 -0
- data/app/views/workarea/storefront/users/orders/_summary.html.haml +22 -0
- data/app/workers/workarea/global_e/update_order_dispatch.rb +28 -0
- data/app/workers/workarea/save_user_order_details.decorator +9 -0
- data/bin/rails +25 -0
- data/config/initializers/appends.rb +46 -0
- data/config/initializers/seeds.rb +1 -0
- data/config/initializers/workarea.rb +64 -0
- data/config/locales/en.yml +105 -0
- data/config/routes.rb +31 -0
- data/lib/workarea/global_e/engine.rb +10 -0
- data/lib/workarea/global_e/error.rb +8 -0
- data/lib/workarea/global_e/version.rb +5 -0
- data/lib/workarea/global_e.rb +58 -0
- data/script/admin_ci +5 -0
- data/script/ci +8 -0
- data/script/core_ci +5 -0
- data/script/plugins_ci +5 -0
- data/script/storefront_ci +5 -0
- data/test/dummy/.ruby-version +1 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +14 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +15 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +28 -0
- data/test/dummy/bin/update +28 -0
- data/test/dummy/bin/yarn +11 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +52 -0
- data/test/dummy/config/environments/production.rb +83 -0
- data/test/dummy/config/environments/test.rb +45 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/assets.rb +14 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/content_security_policy.rb +25 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/workarea.rb +5 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +34 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/config/secrets.yml +18 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/db/seeds.rb +2 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/factories/workarea/global_e_factories.rb +836 -0
- data/test/integration/workarea/admin/catalog_product_country_exceptions_integration_test.rb +81 -0
- data/test/integration/workarea/admin/global_e_jump_to_integration_test.rb +20 -0
- data/test/integration/workarea/admin/pricing_sku_fixed_prices_integration_test.rb +83 -0
- data/test/integration/workarea/storefront/global_e_api/receive_order_integration_test.rb +278 -0
- data/test/integration/workarea/storefront/global_e_api/receive_order_refund_integration_test.rb +25 -0
- data/test/integration/workarea/storefront/global_e_api/receive_payment_integration_test.rb +34 -0
- data/test/integration/workarea/storefront/global_e_api/receive_shipping_info_integration_test.rb +29 -0
- data/test/integration/workarea/storefront/global_e_api/remove_restricted_products_integration_test.rb +27 -0
- data/test/integration/workarea/storefront/global_e_api/update_order_status_integration_test.rb +29 -0
- data/test/integration/workarea/storefront/global_e_checkout_cart_info/fixed_pricing_test.rb +438 -0
- data/test/integration/workarea/storefront/global_e_checkout_cart_info/gift_card_test.rb +63 -0
- data/test/integration/workarea/storefront/global_e_checkout_cart_info_integration_test.rb +660 -0
- data/test/integration/workarea/storefront/global_e_checkouts_integration_test.rb +71 -0
- data/test/integration/workarea/storefront/users/global_e_logins_integration_test.rb +20 -0
- data/test/models/workarea/catalog/product_global_e_test.rb +13 -0
- data/test/models/workarea/fixed_pricing_test.rb +117 -0
- data/test/models/workarea/global_e/country_exception_test.rb +36 -0
- data/test/models/workarea/payment_test.decorator +31 -0
- data/test/models/workarea/pricing/calculators/item_calculator_fixed_prices_test.rb +83 -0
- data/test/models/workarea/pricing/collection_fixed_prices_test.rb +83 -0
- data/test/models/workarea/pricing/price_distributor_test.decorator +21 -0
- data/test/models/workarea/pricing/sku_fixed_price_test.rb +133 -0
- data/test/support/workarea/global_e_support.rb +18 -0
- data/test/system/workarea/admin/global_e_system_test.rb +21 -0
- data/test/system/workarea/storefront/global_e_cart_system_test.rb +48 -0
- data/test/system/workarea/storefront/global_e_order_history_system_test.rb +43 -0
- data/test/teaspoon_env.rb +6 -0
- data/test/test_helper.rb +9 -0
- data/test/view_models/workarea/admin/global_e_order_view_model_test.rb +22 -0
- data/test/workers/workarea/global_e/update_order_dispatch_test.rb +64 -0
- data/test/workers/workarea/global_e_save_user_order_details_test.rb +41 -0
- data/workarea-global_e.gemspec +16 -0
- metadata +293 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Workarea
|
2
|
+
class Fulfillment
|
3
|
+
module Status
|
4
|
+
class Refunded
|
5
|
+
include StatusCalculator::Status
|
6
|
+
|
7
|
+
def in_status?
|
8
|
+
# Need some items in the fulfillment
|
9
|
+
order.items.any? &&
|
10
|
+
order.items.all? { |i| i.quantity_refunded >= i.quantity }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Fulfillment, with: :global_e do
|
3
|
+
decorated do
|
4
|
+
field :global_e_tracking_url, type: String
|
5
|
+
field :global_e, type: Boolean, default: false
|
6
|
+
end
|
7
|
+
|
8
|
+
def ship_items(tracking_number, shipped_items, send_to_global_e: true)
|
9
|
+
result = super(tracking_number, shipped_items)
|
10
|
+
if result && send_to_global_e && global_e?
|
11
|
+
GlobalE::UpdateOrderDispatch.perform_async(id, tracking_number)
|
12
|
+
end
|
13
|
+
result
|
14
|
+
end
|
15
|
+
|
16
|
+
# Refund items in the fulfillment
|
17
|
+
#
|
18
|
+
# The hashes should contain 'id', the order_item_id and 'quantity' the amount
|
19
|
+
# to be canceled, any other key/values will be stored on the Fulfillment::Event#data
|
20
|
+
#
|
21
|
+
# refund_items([
|
22
|
+
# { 'id' => '1234', 'quantity' => 4, 'replacement_sku' => '9999' },
|
23
|
+
# { 'id' => '4321', 'quantity' => 1 }
|
24
|
+
# ])
|
25
|
+
#
|
26
|
+
# @param [Array<Hash>] refunded_items
|
27
|
+
# @return [Boolean]
|
28
|
+
#
|
29
|
+
def refund_items(refunded_items)
|
30
|
+
return false unless refunded_items.present?
|
31
|
+
|
32
|
+
occured_at = Time.current
|
33
|
+
|
34
|
+
refunded_items = refunded_items.map do |refunded_item|
|
35
|
+
refunded_item = refunded_item.with_indifferent_access
|
36
|
+
next unless refunded_item['quantity'].to_i > 0
|
37
|
+
|
38
|
+
item = items.detect { |i| i.order_item_id == refunded_item['id'].to_s }
|
39
|
+
next unless item.present?
|
40
|
+
|
41
|
+
item.events.build(
|
42
|
+
status: 'refunded',
|
43
|
+
quantity: refunded_item['quantity'],
|
44
|
+
created_at: occured_at,
|
45
|
+
updated_at: occured_at,
|
46
|
+
data: refunded_item.except('id', 'quantity')
|
47
|
+
)
|
48
|
+
end.compact
|
49
|
+
|
50
|
+
return false unless refunded_items.present?
|
51
|
+
|
52
|
+
save
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Workarea
|
2
|
+
module GlobalE
|
3
|
+
class CountryException
|
4
|
+
include ApplicationDocument
|
5
|
+
|
6
|
+
field :country, type: Country
|
7
|
+
field :restricted, type: Boolean, default: false
|
8
|
+
field :vat_rate, type: Float
|
9
|
+
|
10
|
+
embedded_in :product, class_name: "Workarea::Catalog::Product"
|
11
|
+
|
12
|
+
validates_presence_of :country
|
13
|
+
validate :unique_country
|
14
|
+
validate :restricted_or_vat_rate_presence
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def unique_country
|
19
|
+
return unless product.present? && country.present?
|
20
|
+
|
21
|
+
country_exists = product.country_exceptions.any? do |country_exception|
|
22
|
+
country_exception != self && country_exception.country == country
|
23
|
+
end
|
24
|
+
|
25
|
+
if country_exists
|
26
|
+
errors.add(:base, I18n.t('workarea.country_exception.errors.country_must_be_unique'))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def restricted_or_vat_rate_presence
|
31
|
+
return if restricted.present? || vat_rate.present?
|
32
|
+
|
33
|
+
errors.add(:base, I18n.t('workarea.country_exception.errors.restricted_or_vat_rate_required'))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Workarea
|
2
|
+
module GlobalE
|
3
|
+
class FixedPrice
|
4
|
+
include ApplicationDocument
|
5
|
+
include Releasable
|
6
|
+
|
7
|
+
field :country, type: Country
|
8
|
+
field :currency_code, type: String
|
9
|
+
field :regular, type: Money
|
10
|
+
field :sale, type: Money
|
11
|
+
field :msrp, type: Money
|
12
|
+
|
13
|
+
embedded_in :pricing_sku, inverse_of: :fixed_prices
|
14
|
+
|
15
|
+
validates_presence_of :regular, :currency
|
16
|
+
validate :validate_foreign_currency
|
17
|
+
validate :valid_currency
|
18
|
+
validate :currencies_match
|
19
|
+
validate :unique_currency
|
20
|
+
validate :unique_country
|
21
|
+
|
22
|
+
delegate :on_sale?, to: :pricing_sku
|
23
|
+
|
24
|
+
def currency
|
25
|
+
Money::Currency.find currency_code
|
26
|
+
end
|
27
|
+
|
28
|
+
def sell
|
29
|
+
if on_sale? && sale.present?
|
30
|
+
sale
|
31
|
+
else
|
32
|
+
regular
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def validate_foreign_currency
|
39
|
+
if regular.present? && regular.currency == Money.default_currency
|
40
|
+
errors.add(:regular, I18n.t('workarea.fixed_price.errors.invalid_fixed_price_currency'))
|
41
|
+
end
|
42
|
+
|
43
|
+
if sale.present? && sale.currency == Money.default_currency
|
44
|
+
errors.add(:sale, I18n.t('workarea.fixed_price.errors.invalid_fixed_price_currency'))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid_currency
|
49
|
+
return unless currency_code.present?
|
50
|
+
|
51
|
+
if Money::Currency.find(currency_code).blank?
|
52
|
+
errors.add(:currency_code, I18n.t('workarea.fixed_price.errors.invalid_currency'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def currencies_match
|
57
|
+
if currency.present? && regular.present? && regular.currency != currency
|
58
|
+
errors.add(:base, I18n.t('workarea.fixed_price.errors.currency_and_regular_mistmatch'))
|
59
|
+
end
|
60
|
+
|
61
|
+
if sale.present? && regular.present? && regular.currency != sale.currency
|
62
|
+
errors.add(:base, I18n.t('workarea.fixed_price.errors.sale_and_regular_currency_mismatch'))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def unique_currency
|
67
|
+
return unless pricing_sku.present? && currency_code.present?
|
68
|
+
|
69
|
+
currency_exists = pricing_sku.fixed_prices.any? do |fixed_price|
|
70
|
+
fixed_price != self &&
|
71
|
+
fixed_price.currency_code == currency_code &&
|
72
|
+
fixed_price.country == country
|
73
|
+
end
|
74
|
+
|
75
|
+
if currency_exists
|
76
|
+
errors.add(:base, I18n.t('workarea.fixed_price.errors.currency_must_be_unique'))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def unique_country
|
81
|
+
return unless pricing_sku.present? && country.present?
|
82
|
+
|
83
|
+
country_exists = pricing_sku.fixed_prices.any? do |fixed_price|
|
84
|
+
fixed_price != self && fixed_price.country == country
|
85
|
+
end
|
86
|
+
|
87
|
+
if country_exists
|
88
|
+
errors.add(:base, I18n.t('workarea.fixed_price.errors.country_must_be_unique'))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Workarea
|
2
|
+
module GlobalE
|
3
|
+
class OrderApiEvents
|
4
|
+
include ApplicationDocument
|
5
|
+
|
6
|
+
field :receive_order, type: Hash
|
7
|
+
field :receive_order_response, type: Hash
|
8
|
+
field :receive_payment, type: Hash
|
9
|
+
field :receive_payment_response, type: Hash
|
10
|
+
field :update_order_status, type: Hash
|
11
|
+
field :update_order_status_response, type: Hash
|
12
|
+
field :receive_shipping_info, type: Hash
|
13
|
+
field :receive_shipping_info_response, type: Hash
|
14
|
+
|
15
|
+
def self.upsert_one(id, set: {})
|
16
|
+
timestamp = Time.current
|
17
|
+
|
18
|
+
collection.update_one(
|
19
|
+
{ _id: id.to_s },
|
20
|
+
{
|
21
|
+
'$setOnInsert' => { created_at: timestamp },
|
22
|
+
'$set' => { updated_at: timestamp }.merge(set),
|
23
|
+
},
|
24
|
+
upsert: true
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Order::Item, with: :global_e do
|
3
|
+
decorated do
|
4
|
+
field :international_total_value, type: Money
|
5
|
+
field :international_total_price, type: Money
|
6
|
+
field :discounted_price_for_customs, type: Money
|
7
|
+
field :generic_hs_code, type: String
|
8
|
+
|
9
|
+
embeds_many :international_price_adjustments,
|
10
|
+
class_name: 'Workarea::PriceAdjustment',
|
11
|
+
extend: PriceAdjustmentExtension
|
12
|
+
end
|
13
|
+
|
14
|
+
def international_discounted_price
|
15
|
+
international_total_value
|
16
|
+
end
|
17
|
+
|
18
|
+
# The original item calculator price adjustment amount before GlobalE
|
19
|
+
# adjusting
|
20
|
+
#
|
21
|
+
# @return [nil, Money]
|
22
|
+
#
|
23
|
+
def nonadjusted_amount
|
24
|
+
return if price_adjustments.first.data["nonadjusted_amount"].nil?
|
25
|
+
|
26
|
+
Money.demongoize(price_adjustments.first.data["nonadjusted_amount"])
|
27
|
+
end
|
28
|
+
|
29
|
+
# The original item calculator price adjustment price before GlobalE
|
30
|
+
# adjusting
|
31
|
+
#
|
32
|
+
# @return [nil, Float]
|
33
|
+
#
|
34
|
+
def nonadjusted_price
|
35
|
+
price_adjustments.first.data["nonadjusted_price"]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds an international price adjustment to the item. Does not persist.
|
39
|
+
#
|
40
|
+
# @return [self]
|
41
|
+
#
|
42
|
+
def adjust_international_pricing(options = {})
|
43
|
+
international_price_adjustments.build(options)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Order, with: :global_e do
|
3
|
+
decorated do
|
4
|
+
field :currency, type: String, default: -> { Money.default_currency.iso_code }
|
5
|
+
field :shipping_country, type: Country
|
6
|
+
field :fixed_pricing, type: Boolean, default: false
|
7
|
+
|
8
|
+
field :global_e, type: Boolean, default: false
|
9
|
+
field :global_e_id, type: String
|
10
|
+
field :global_e_token, type: String, default: -> { SecureRandom.hex(5).upcase }
|
11
|
+
field :global_e_approved_at, type: Time
|
12
|
+
|
13
|
+
field :duties_guaranteed, type: Boolean, default: false
|
14
|
+
|
15
|
+
field :international_subtotal_price, type: Money
|
16
|
+
field :international_shipping_total, type: Money
|
17
|
+
field :discounted_international_shipping_total, type: Money
|
18
|
+
field :total_duties_price, type: Money
|
19
|
+
field :international_total_price, type: Money
|
20
|
+
field :contains_clearance_fees_price, type: Money
|
21
|
+
|
22
|
+
embeds_many :discount_adjustments,
|
23
|
+
class_name: 'Workarea::PriceAdjustment',
|
24
|
+
extend: PriceAdjustmentExtension
|
25
|
+
|
26
|
+
embeds_many :international_discount_adjustments,
|
27
|
+
class_name: 'Workarea::PriceAdjustment',
|
28
|
+
extend: PriceAdjustmentExtension
|
29
|
+
|
30
|
+
index({ global_e_token: 1 })
|
31
|
+
index({ global_e_id: 1 })
|
32
|
+
end
|
33
|
+
|
34
|
+
# All international price adjustments on this order.
|
35
|
+
#
|
36
|
+
# @return [PriceAdjustmentSet]
|
37
|
+
#
|
38
|
+
def international_price_adjustments
|
39
|
+
PriceAdjustmentSet.new(items.flat_map(&:international_price_adjustments))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Do not run callbacks if global_e order, they will be run after
|
43
|
+
# PerformOrderPayment
|
44
|
+
def place
|
45
|
+
return super unless purchasable? && global_e?
|
46
|
+
|
47
|
+
self.placed_at = Time.current
|
48
|
+
with(write: { w: "majority", j: true }) { save }
|
49
|
+
end
|
50
|
+
|
51
|
+
def global_e_approve!
|
52
|
+
run_callbacks :place do
|
53
|
+
update_attributes(global_e_approved_at: Time.current)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def discount_ids
|
58
|
+
super + discount_adjustments
|
59
|
+
.map { |pa| pa.data['discount_id'] }
|
60
|
+
.compact
|
61
|
+
.uniq
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Workarea
|
2
|
+
class Payment
|
3
|
+
class Tender
|
4
|
+
class GlobalEPayment < Tender
|
5
|
+
field :name, type: String
|
6
|
+
field :payment_method_code, type: String
|
7
|
+
field :last_four, type: String
|
8
|
+
field :expiration_date, type: String
|
9
|
+
|
10
|
+
def slug
|
11
|
+
:global_e_payment
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate PriceAdjustmentSet, with: :global_e do
|
3
|
+
def group_discounts_by_id
|
4
|
+
reduce({}) do |memo, adjustment|
|
5
|
+
if adjustment.discount?
|
6
|
+
memo[adjustment.data["discount_id"]] ||= PriceAdjustment.new(
|
7
|
+
quantity: adjustment.quantity,
|
8
|
+
data: adjustment.data.merge("discount_value" => 0.0),
|
9
|
+
price: adjustment.price,
|
10
|
+
description: adjustment.description,
|
11
|
+
calculator: adjustment.calculator,
|
12
|
+
amount: 0.to_m(adjustment.amount.currency)
|
13
|
+
)
|
14
|
+
|
15
|
+
memo[adjustment.data["discount_id"]].amount += adjustment.amount
|
16
|
+
memo[adjustment.data["discount_id"]].data["discount_value"] += adjustment.data["discount_value"]
|
17
|
+
else
|
18
|
+
memo[adjustment.id] = adjustment
|
19
|
+
end
|
20
|
+
|
21
|
+
memo
|
22
|
+
end.values
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Pricing::Calculators::ItemCalculator, with: :global_e do
|
3
|
+
def adjust
|
4
|
+
order.fixed_pricing = is_fixed_price?
|
5
|
+
|
6
|
+
return super unless is_fixed_price?
|
7
|
+
|
8
|
+
order.items.each do |item|
|
9
|
+
price = pricing.for_sku(item.sku, quantity: item.quantity)
|
10
|
+
|
11
|
+
item.adjust_pricing(
|
12
|
+
price: 'item',
|
13
|
+
quantity: item.quantity,
|
14
|
+
calculator: self.class.name,
|
15
|
+
amount: price.sell * item.quantity,
|
16
|
+
description: 'Item Subtotal',
|
17
|
+
data: {
|
18
|
+
'on_sale' => price.on_sale?,
|
19
|
+
'original_price' => price.regular.to_f,
|
20
|
+
'tax_code' => price.tax_code
|
21
|
+
}
|
22
|
+
)
|
23
|
+
|
24
|
+
fixed_price = pricing.fixed_price_for_sku(item.sku, currency_code: order.currency, country: order.shipping_country)
|
25
|
+
|
26
|
+
item.adjust_international_pricing(
|
27
|
+
price: 'item',
|
28
|
+
quantity: item.quantity,
|
29
|
+
calculator: self.class.name,
|
30
|
+
amount: fixed_price.sell * item.quantity,
|
31
|
+
description: 'Item Subtotal',
|
32
|
+
data: {
|
33
|
+
'on_sale' => fixed_price.on_sale?,
|
34
|
+
'original_price' => fixed_price.regular.to_f,
|
35
|
+
'tax_code' => price.tax_code
|
36
|
+
}
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def is_fixed_price?
|
44
|
+
pricing.records.present? &&
|
45
|
+
pricing.records.all? { |sku| sku.fixed_price_for(currency_code: order.currency, country: order.shipping_country) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Pricing::Collection, with: :global_e do
|
3
|
+
# GlobalE sell min fixed prices
|
4
|
+
# Returns a hash of:
|
5
|
+
# { defualts: { Currency: Price }, Country: Price }
|
6
|
+
#
|
7
|
+
# @return [::Hash]
|
8
|
+
#
|
9
|
+
def sell_min_fixed_prices
|
10
|
+
sell_min_defaults = default_fixed_prices.map do |currency, prices|
|
11
|
+
[currency, prices.map { |p| p.sell.to_f }.sort.first]
|
12
|
+
end.to_h.presence
|
13
|
+
|
14
|
+
sell_min_country_prices = country_fixed_prices.map do |country, prices|
|
15
|
+
[country.alpha2, prices.map { |p| p.sell.to_f }.sort.first]
|
16
|
+
end.to_h
|
17
|
+
|
18
|
+
|
19
|
+
{ defaults: sell_min_defaults }.merge(sell_min_country_prices).compact
|
20
|
+
end
|
21
|
+
|
22
|
+
# GlobalE sell max fixed prices
|
23
|
+
# Returns a hash of:
|
24
|
+
# { defualts: { Currency: Price }, Country: Price }
|
25
|
+
#
|
26
|
+
# @return [::Hash]
|
27
|
+
#
|
28
|
+
def sell_max_fixed_prices
|
29
|
+
sell_min_defaults = default_fixed_prices.map do |currency, prices|
|
30
|
+
[currency, prices.map { |p| p.sell.to_f }.sort.last]
|
31
|
+
end.to_h.presence
|
32
|
+
|
33
|
+
sell_min_country_prices = country_fixed_prices.map do |country, prices|
|
34
|
+
[country.alpha2, prices.map { |p| p.sell.to_f }.sort.last]
|
35
|
+
end.to_h
|
36
|
+
|
37
|
+
|
38
|
+
{ defaults: sell_min_defaults }.merge(sell_min_country_prices).compact
|
39
|
+
end
|
40
|
+
|
41
|
+
def original_min_fixed_prices
|
42
|
+
original_min_defaults = default_fixed_prices.map do |currency, prices|
|
43
|
+
[currency, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.first]
|
44
|
+
end.to_h.presence
|
45
|
+
|
46
|
+
original_min_country_prices = country_fixed_prices.map do |country, prices|
|
47
|
+
[country.alpha2, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.first]
|
48
|
+
end.to_h
|
49
|
+
|
50
|
+
{ defaults: original_min_defaults }.merge(original_min_country_prices).compact
|
51
|
+
end
|
52
|
+
|
53
|
+
def original_max_fixed_prices
|
54
|
+
original_max_defaults = default_fixed_prices.map do |currency, prices|
|
55
|
+
[currency, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.last]
|
56
|
+
end.to_h.presence
|
57
|
+
|
58
|
+
original_max_country_prices = country_fixed_prices.map do |country, prices|
|
59
|
+
[country.alpha2, prices.map { |p| [p.msrp&.to_f, p.regular.to_f].compact.max }.sort.last]
|
60
|
+
end.to_h
|
61
|
+
|
62
|
+
{ defaults: original_max_defaults }.merge(original_max_country_prices).compact
|
63
|
+
end
|
64
|
+
|
65
|
+
# @ return [Workarea::GlobalE::FixedPrice, nil]
|
66
|
+
#
|
67
|
+
def fixed_price_for_sku(sku, currency_code:, country:)
|
68
|
+
return unless sku = records.detect { |r| r.id == sku }
|
69
|
+
sku.fixed_price_for(currency_code: currency_code, country: country)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def fixed_prices
|
75
|
+
@fixed_prices ||= records.flat_map(&:fixed_prices)
|
76
|
+
end
|
77
|
+
|
78
|
+
def default_fixed_prices
|
79
|
+
@default_fixed_prices ||= fixed_prices.select do |fixed_price|
|
80
|
+
fixed_price.country.blank?
|
81
|
+
end.group_by(&:currency_code)
|
82
|
+
end
|
83
|
+
|
84
|
+
def country_fixed_prices
|
85
|
+
@country_fixed_prices ||= fixed_prices.select do |fixed_price|
|
86
|
+
fixed_price.country.present?
|
87
|
+
end.group_by(&:country)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Workarea
|
2
|
+
decorate Pricing::Discount::ApplicationGroup, with: :global_e do
|
3
|
+
def apply
|
4
|
+
return super unless order.fixed_pricing?
|
5
|
+
|
6
|
+
discounts.each do |discount|
|
7
|
+
discount_order = Pricing::Discount::Order.new(order, shippings, discount)
|
8
|
+
next unless discount.qualifies?(discount_order)
|
9
|
+
|
10
|
+
discount.apply(discount_order)
|
11
|
+
international_discount_order = Pricing::Discount::InternationalOrder.new(order, shippings, discount)
|
12
|
+
discount.apply(international_discount_order)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Pricing
|
3
|
+
class Discount
|
4
|
+
class InternationalItemProxy
|
5
|
+
attr_reader :order_item
|
6
|
+
delegate_missing_to :order_item
|
7
|
+
|
8
|
+
def initialize(order_item)
|
9
|
+
@order_item = order_item
|
10
|
+
end
|
11
|
+
|
12
|
+
def current_unit_price
|
13
|
+
return 0.to_m(order.currency) if international_price_adjustments.blank?
|
14
|
+
international_price_adjustments.adjusting('item').map(&:unit).sum.to_m
|
15
|
+
end
|
16
|
+
|
17
|
+
# Adds an international price adjustment to the item. Does not persist.
|
18
|
+
#
|
19
|
+
# @return [self]
|
20
|
+
#
|
21
|
+
def adjust_pricing(options = {})
|
22
|
+
international_price_adjustments.build(options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Workarea
|
2
|
+
module Pricing
|
3
|
+
class Discount
|
4
|
+
class InternationalOrder < Discount::Order
|
5
|
+
# Only return items that are discountable.
|
6
|
+
#
|
7
|
+
# TODO this needs update in 3.4
|
8
|
+
#
|
9
|
+
# @return [Array<Workarea::Order::Item>]
|
10
|
+
#
|
11
|
+
def items
|
12
|
+
@order.items.select(&:discountable?).select do |item|
|
13
|
+
allow_sale_items? || !item.on_sale?
|
14
|
+
end.map { |item| InternationalItemProxy.new(item) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# The subtotal, not including items that cannot be discounted.
|
18
|
+
#
|
19
|
+
# @return [Money]
|
20
|
+
#
|
21
|
+
def subtotal_price
|
22
|
+
items.reduce(0.to_m(@order.currency)) do |memo, item|
|
23
|
+
memo + item.international_price_adjustments.sum
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|