gemgento 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/README.md +9 -0
- data/Rakefile +21 -0
- data/app/adapters/gemgento/magento/address_adapter.rb +37 -0
- data/app/adapters/gemgento/magento/customer_adapter.rb +24 -0
- data/app/adapters/gemgento/magento/line_item_adapter.rb +41 -0
- data/app/adapters/gemgento/magento/order_adapter.rb +97 -0
- data/app/adapters/gemgento/magento/order_status_adapter.rb +24 -0
- data/app/adapters/gemgento/magento/payment_adapter.rb +38 -0
- data/app/adapters/gemgento/magento/product_adapater.rb +24 -0
- data/app/adapters/gemgento/magento/quote_adapter.rb +17 -0
- data/app/adapters/gemgento/magento/shipment_adapter.rb +74 -0
- data/app/adapters/gemgento/magento/shipment_item_adapter.rb +39 -0
- data/app/adapters/gemgento/magento/shipment_track_adapter.rb +31 -0
- data/app/adapters/gemgento/magento_adapter.rb +17 -0
- data/app/assets/fonts/FuturaStd-Bold.otf +0 -0
- data/app/assets/fonts/FuturaStd-Book.otf +0 -0
- data/app/assets/fonts/FuturaStd-Light.otf +0 -0
- data/app/assets/fonts/FuturaStd-Medium.otf +0 -0
- data/app/assets/fonts/Verlag-Black.otf +0 -0
- data/app/assets/fonts/Verlag-BlackItalic.otf +0 -0
- data/app/assets/fonts/Verlag-Bold.otf +0 -0
- data/app/assets/fonts/Verlag-BoldItalic.otf +0 -0
- data/app/assets/fonts/Verlag-Book.otf +0 -0
- data/app/assets/fonts/Verlag-BookItalic-2.otf +0 -0
- data/app/assets/fonts/Verlag-BookItalic-3.otf +0 -0
- data/app/assets/fonts/Verlag-BookItalic.otf +0 -0
- data/app/assets/fonts/Verlag-Light.otf +0 -0
- data/app/assets/fonts/Verlag-LightItalic.otf +0 -0
- data/app/assets/fonts/Verlag-XLight.otf +0 -0
- data/app/assets/fonts/Verlag-XLightItalic.otf +0 -0
- data/app/assets/images/about_store.jpg +0 -0
- data/app/assets/images/gemgento-icon-bolt.png +0 -0
- data/app/assets/javascripts/gemgento/active_admin.js.coffee +42 -0
- data/app/assets/javascripts/gemgento/checkout/address.coffee +23 -0
- data/app/assets/javascripts/gemgento/gemgento.js +1 -0
- data/app/assets/javascripts/gemgento/global.js.coffee +31 -0
- data/app/assets/stylesheets/gemgento/active_admin.css.scss +549 -0
- data/app/assets/stylesheets/gemgento/default-views/_address.scss +11 -0
- data/app/assets/stylesheets/gemgento/default-views/_category.scss +54 -0
- data/app/assets/stylesheets/gemgento/default-views/_checkout.scss +28 -0
- data/app/assets/stylesheets/gemgento/default-views/_footer.scss +13 -0
- data/app/assets/stylesheets/gemgento/default-views/_form.scss +127 -0
- data/app/assets/stylesheets/gemgento/default-views/_header.scss +32 -0
- data/app/assets/stylesheets/gemgento/default-views/_layout.css.scss +157 -0
- data/app/assets/stylesheets/gemgento/default-views/_pages.css.scss +83 -0
- data/app/assets/stylesheets/gemgento/default-views/_product.scss +57 -0
- data/app/assets/stylesheets/gemgento/default-views/_shopping_bag.scss +87 -0
- data/app/assets/stylesheets/gemgento/default-views/_subheader.css.scss +30 -0
- data/app/assets/stylesheets/gemgento/default-views/application.css.scss +1 -0
- data/app/controllers/concerns/gemgento/api/v1/base.rb +15 -0
- data/app/controllers/concerns/gemgento/api/v1/product_filters.rb +33 -0
- data/app/controllers/gemgento/addresses_controller.rb +14 -0
- data/app/controllers/gemgento/api/v1/categories_controller.rb +13 -0
- data/app/controllers/gemgento/api/v1/products_controller.rb +23 -0
- data/app/controllers/gemgento/api/v1/search_controller.rb +14 -0
- data/app/controllers/gemgento/application_controller.rb +5 -0
- data/app/controllers/gemgento/cart_controller.rb +99 -0
- data/app/controllers/gemgento/categories_controller.rb +37 -0
- data/app/controllers/gemgento/checkout/address_controller.rb +48 -0
- data/app/controllers/gemgento/checkout/confirm_controller.rb +72 -0
- data/app/controllers/gemgento/checkout/coupons_controller.rb +42 -0
- data/app/controllers/gemgento/checkout/gift_card_controller.rb +39 -0
- data/app/controllers/gemgento/checkout/gift_controller.rb +32 -0
- data/app/controllers/gemgento/checkout/login_controller.rb +123 -0
- data/app/controllers/gemgento/checkout/payment_controller.rb +70 -0
- data/app/controllers/gemgento/checkout/paypal_controller.rb +23 -0
- data/app/controllers/gemgento/checkout/shipping_controller.rb +46 -0
- data/app/controllers/gemgento/checkout/shipping_payment_controller.rb +58 -0
- data/app/controllers/gemgento/checkout/thank_you_controller.rb +45 -0
- data/app/controllers/gemgento/checkout_controller.rb +120 -0
- data/app/controllers/gemgento/countries_controller.rb +17 -0
- data/app/controllers/gemgento/errors_controller.rb +7 -0
- data/app/controllers/gemgento/footer_items_controller.rb +16 -0
- data/app/controllers/gemgento/home_controller.rb +7 -0
- data/app/controllers/gemgento/magento/addresses_controller.rb +61 -0
- data/app/controllers/gemgento/magento/base_controller.rb +18 -0
- data/app/controllers/gemgento/magento/categories_controller.rb +103 -0
- data/app/controllers/gemgento/magento/email/sales/credit_memos_controller.rb +21 -0
- data/app/controllers/gemgento/magento/email/sales/invoices_controller.rb +21 -0
- data/app/controllers/gemgento/magento/email/sales/orders_controller.rb +20 -0
- data/app/controllers/gemgento/magento/email/sales/shipments_controller.rb +25 -0
- data/app/controllers/gemgento/magento/emails_controller.rb +11 -0
- data/app/controllers/gemgento/magento/inventory_controller.rb +30 -0
- data/app/controllers/gemgento/magento/orders_controller.rb +12 -0
- data/app/controllers/gemgento/magento/price_rules_controller.rb +22 -0
- data/app/controllers/gemgento/magento/product_attribute_sets_controller.rb +37 -0
- data/app/controllers/gemgento/magento/product_attributes_controller.rb +70 -0
- data/app/controllers/gemgento/magento/products_controller.rb +240 -0
- data/app/controllers/gemgento/magento/recurring_profiles_controller.rb +42 -0
- data/app/controllers/gemgento/magento/stores_controller.rb +21 -0
- data/app/controllers/gemgento/magento/user_groups_controller.rb +23 -0
- data/app/controllers/gemgento/magento/users_controller.rb +63 -0
- data/app/controllers/gemgento/order_export_controller.rb +21 -0
- data/app/controllers/gemgento/orders_controller.rb +12 -0
- data/app/controllers/gemgento/products_controller.rb +12 -0
- data/app/controllers/gemgento/regions_controller.rb +18 -0
- data/app/controllers/gemgento/search_controller.rb +16 -0
- data/app/controllers/gemgento/shipments_controller.rb +5 -0
- data/app/controllers/gemgento/stock_notifications_controller.rb +22 -0
- data/app/controllers/gemgento/subscribers_controller.rb +35 -0
- data/app/controllers/gemgento/sync_controller.rb +50 -0
- data/app/controllers/gemgento/user/addresses_controller.rb +79 -0
- data/app/controllers/gemgento/user/base_controller.rb +14 -0
- data/app/controllers/gemgento/user/home_controller.rb +9 -0
- data/app/controllers/gemgento/user/orders_controller.rb +17 -0
- data/app/controllers/gemgento/user/passwords_controller.rb +18 -0
- data/app/controllers/gemgento/user/recurring_profiles_controller.rb +31 -0
- data/app/controllers/gemgento/user/registration_session_controller.rb +103 -0
- data/app/controllers/gemgento/user/registrations_controller.rb +28 -0
- data/app/controllers/gemgento/user/saved_credit_cards_controller.rb +56 -0
- data/app/controllers/gemgento/user/sessions_controller.rb +50 -0
- data/app/controllers/gemgento/user/wishlist_items_controller.rb +55 -0
- data/app/controllers/gemgento/users_controller.rb +36 -0
- data/app/helpers/gemgento/application_helper.rb +125 -0
- data/app/helpers/gemgento/categories_helper.rb +39 -0
- data/app/helpers/gemgento/checkout/payment_helper.rb +5 -0
- data/app/helpers/gemgento/products_helper.rb +23 -0
- data/app/jobs/gemgento/import_job.rb +12 -0
- data/app/mailers/gemgento/sales_mailer.rb +80 -0
- data/app/models/admin_user.rb +7 -0
- data/app/models/concerns/gemgento/product_touches.rb +26 -0
- data/app/models/gemgento.rb +5 -0
- data/app/models/gemgento/adapter/sellect/customer.rb +34 -0
- data/app/models/gemgento/adapter/sellect/order.rb +378 -0
- data/app/models/gemgento/adapter/sellect/product.rb +250 -0
- data/app/models/gemgento/adapter/sellect_adapter.rb +16 -0
- data/app/models/gemgento/adapter/shopify/address.rb +40 -0
- data/app/models/gemgento/adapter/shopify/category.rb +51 -0
- data/app/models/gemgento/adapter/shopify/customer.rb +66 -0
- data/app/models/gemgento/adapter/shopify/order.rb +334 -0
- data/app/models/gemgento/adapter/shopify/product.rb +377 -0
- data/app/models/gemgento/adapter/shopify/product_category_position.rb +58 -0
- data/app/models/gemgento/adapter/shopify_adapter.rb +63 -0
- data/app/models/gemgento/address.rb +206 -0
- data/app/models/gemgento/api_job.rb +68 -0
- data/app/models/gemgento/asset.rb +185 -0
- data/app/models/gemgento/asset_file.rb +57 -0
- data/app/models/gemgento/asset_type.rb +8 -0
- data/app/models/gemgento/bundle.rb +5 -0
- data/app/models/gemgento/bundle/item.rb +17 -0
- data/app/models/gemgento/bundle/option.rb +20 -0
- data/app/models/gemgento/category.rb +235 -0
- data/app/models/gemgento/country.rb +21 -0
- data/app/models/gemgento/gift_message.rb +8 -0
- data/app/models/gemgento/image_import.rb +141 -0
- data/app/models/gemgento/import.rb +125 -0
- data/app/models/gemgento/inventory.rb +60 -0
- data/app/models/gemgento/inventory_import.rb +51 -0
- data/app/models/gemgento/line_item.rb +142 -0
- data/app/models/gemgento/line_item_option.rb +10 -0
- data/app/models/gemgento/magento_db.rb +35 -0
- data/app/models/gemgento/magento_response.rb +10 -0
- data/app/models/gemgento/order.rb +56 -0
- data/app/models/gemgento/order_status.rb +7 -0
- data/app/models/gemgento/payment.rb +50 -0
- data/app/models/gemgento/price_rule.rb +258 -0
- data/app/models/gemgento/price_tier.rb +36 -0
- data/app/models/gemgento/product.rb +607 -0
- data/app/models/gemgento/product_attribute.rb +48 -0
- data/app/models/gemgento/product_attribute_option.rb +39 -0
- data/app/models/gemgento/product_attribute_set.rb +22 -0
- data/app/models/gemgento/product_attribute_value.rb +26 -0
- data/app/models/gemgento/product_category.rb +34 -0
- data/app/models/gemgento/product_import.rb +359 -0
- data/app/models/gemgento/quote.rb +586 -0
- data/app/models/gemgento/recurring_profile.rb +28 -0
- data/app/models/gemgento/region.rb +14 -0
- data/app/models/gemgento/relation.rb +10 -0
- data/app/models/gemgento/relation_type.rb +7 -0
- data/app/models/gemgento/saved_credit_card.rb +45 -0
- data/app/models/gemgento/search.rb +14 -0
- data/app/models/gemgento/session.rb +49 -0
- data/app/models/gemgento/shipment.rb +38 -0
- data/app/models/gemgento/shipment_comment.rb +19 -0
- data/app/models/gemgento/shipment_item.rb +11 -0
- data/app/models/gemgento/shipment_track.rb +21 -0
- data/app/models/gemgento/stock_notification.rb +17 -0
- data/app/models/gemgento/store.rb +31 -0
- data/app/models/gemgento/store_tag.rb +8 -0
- data/app/models/gemgento/subscriber.rb +30 -0
- data/app/models/gemgento/swatch.rb +9 -0
- data/app/models/gemgento/sync.rb +134 -0
- data/app/models/gemgento/tag.rb +64 -0
- data/app/models/gemgento/user.rb +161 -0
- data/app/models/gemgento/user_group.rb +13 -0
- data/app/models/gemgento/wishlist_item.rb +11 -0
- data/app/services/gemgento/magento_api.rb +173 -0
- data/app/services/gemgento/price.rb +67 -0
- data/app/validators/gemgento/credit_card_validator.rb +50 -0
- data/app/validators/gemgento/image_import_validator.rb +100 -0
- data/app/validators/gemgento/inventory_import_validator.rb +73 -0
- data/app/validators/gemgento/inventory_validator.rb +14 -0
- data/app/validators/gemgento/product_import_validator.rb +163 -0
- data/app/views/admin/product_positions/_index.html.haml +14 -0
- data/app/views/devise/registrations/edit.haml +37 -0
- data/app/views/gemgento/addresses/_address.json.jbuilder +6 -0
- data/app/views/gemgento/addresses/region_options.html.haml +1 -0
- data/app/views/gemgento/api/v1/categories/_category.json.jbuilder +52 -0
- data/app/views/gemgento/api/v1/categories/index.json.jbuilder +8 -0
- data/app/views/gemgento/api/v1/categories/show.json.jbuilder +7 -0
- data/app/views/gemgento/api/v1/products/_attributes.json.jbuilder +9 -0
- data/app/views/gemgento/api/v1/products/_product.json.jbuilder +18 -0
- data/app/views/gemgento/api/v1/products/_relationships.json.jbuilder +48 -0
- data/app/views/gemgento/api/v1/products/index.json.jbuilder +8 -0
- data/app/views/gemgento/api/v1/products/show.json.jbuilder +7 -0
- data/app/views/gemgento/api/v1/search/index.json.jbuilder +8 -0
- data/app/views/gemgento/api/v1/search/show.json.jbuilder +8 -0
- data/app/views/gemgento/assets/_asset.json.jbuilder +13 -0
- data/app/views/gemgento/cart/show.html.haml +67 -0
- data/app/views/gemgento/categories/_category.json.jbuilder +9 -0
- data/app/views/gemgento/categories/_product_tile.html.haml +16 -0
- data/app/views/gemgento/categories/index.html.haml +10 -0
- data/app/views/gemgento/categories/index.json.jbuilder +1 -0
- data/app/views/gemgento/categories/show.html.haml +16 -0
- data/app/views/gemgento/categories/show.json.jbuilder +1 -0
- data/app/views/gemgento/checkout/_checkout_header.html.haml +17 -0
- data/app/views/gemgento/checkout/address/error.js.erb +1 -0
- data/app/views/gemgento/checkout/address/show.html.haml +88 -0
- data/app/views/gemgento/checkout/address/success.js.erb +1 -0
- data/app/views/gemgento/checkout/confirm/error.js.erb +0 -0
- data/app/views/gemgento/checkout/confirm/show.html.haml +60 -0
- data/app/views/gemgento/checkout/confirm/success.js.erb +1 -0
- data/app/views/gemgento/checkout/login/login_fail.js.erb +1 -0
- data/app/views/gemgento/checkout/login/login_guest_fail.js.erb +1 -0
- data/app/views/gemgento/checkout/login/login_guest_success.js.erb +1 -0
- data/app/views/gemgento/checkout/login/login_success.js.erb +1 -0
- data/app/views/gemgento/checkout/login/registration_fail.js.erb +1 -0
- data/app/views/gemgento/checkout/login/registration_success.js.erb +1 -0
- data/app/views/gemgento/checkout/login/show.html.haml +50 -0
- data/app/views/gemgento/checkout/payment/error.js.erb +1 -0
- data/app/views/gemgento/checkout/payment/show.html.haml +56 -0
- data/app/views/gemgento/checkout/payment/success.js.erb +1 -0
- data/app/views/gemgento/checkout/shipping/error.js.erb +0 -0
- data/app/views/gemgento/checkout/shipping/show.html.haml +26 -0
- data/app/views/gemgento/checkout/shipping/success.js.erb +1 -0
- data/app/views/gemgento/checkout/shipping_payment/show.haml +52 -0
- data/app/views/gemgento/checkout/thank_you/show.html.haml +6 -0
- data/app/views/gemgento/countries/_country.json.jbuilder +4 -0
- data/app/views/gemgento/countries/index.json.jbuilder +3 -0
- data/app/views/gemgento/countries/show.json.jbuilder +1 -0
- data/app/views/gemgento/home/index.html.haml +1 -0
- data/app/views/gemgento/order/add_item.js.erb +0 -0
- data/app/views/gemgento/order/no_inventory.js.erb +1 -0
- data/app/views/gemgento/order/remove_item.js.erb +0 -0
- data/app/views/gemgento/order/update_item.js.erb +0 -0
- data/app/views/gemgento/order_export/index.html.haml +2 -0
- data/app/views/gemgento/order_items/_order_item.json.jbuilder +7 -0
- data/app/views/gemgento/orders/_order.json.jbuilder +29 -0
- data/app/views/gemgento/products/_product.json.jbuilder +40 -0
- data/app/views/gemgento/products/index.html.haml +19 -0
- data/app/views/gemgento/products/show.html.haml +67 -0
- data/app/views/gemgento/products/show.json.jbuilder +1 -0
- data/app/views/gemgento/sales_mailer/credit_memo_email.html.haml +1 -0
- data/app/views/gemgento/sales_mailer/invoice_email.html.haml +1 -0
- data/app/views/gemgento/sales_mailer/order_email.html.haml +1 -0
- data/app/views/gemgento/sales_mailer/shipment_email.html.haml +1 -0
- data/app/views/gemgento/search/index.html.haml +8 -0
- data/app/views/gemgento/shared/_asset.json.jbuilder +35 -0
- data/app/views/gemgento/shared/_asset_file.json.jbuilder +19 -0
- data/app/views/gemgento/shared/_asset_type.json.jbuilder +13 -0
- data/app/views/gemgento/shared/_error_messages.haml +7 -0
- data/app/views/gemgento/shared/_flash_messages.haml +4 -0
- data/app/views/gemgento/shared/_footer.haml +7 -0
- data/app/views/gemgento/shared/_header.haml +23 -0
- data/app/views/gemgento/shared/_mini_bag.haml +36 -0
- data/app/views/gemgento/shared/_product_image.haml +8 -0
- data/app/views/gemgento/shipments/_shipment.json.jbuilder +12 -0
- data/app/views/gemgento/subscribers/create.js.erb +2 -0
- data/app/views/gemgento/subscribers/errors.js.erb +1 -0
- data/app/views/gemgento/user/_user.json.jbuilder +2 -0
- data/app/views/gemgento/user/addresses/_form.haml +39 -0
- data/app/views/gemgento/user/addresses/errors.js.erb +1 -0
- data/app/views/gemgento/user/addresses/index.haml +33 -0
- data/app/views/gemgento/user/addresses/new.haml +11 -0
- data/app/views/gemgento/user/addresses/success.js.erb +1 -0
- data/app/views/gemgento/user/confirmations/new.haml +9 -0
- data/app/views/gemgento/user/home/index.haml +8 -0
- data/app/views/gemgento/user/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/gemgento/user/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/gemgento/user/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/gemgento/user/orders/index.html.haml +29 -0
- data/app/views/gemgento/user/orders/show.html.haml +87 -0
- data/app/views/gemgento/user/passwords/edit.html.erb +18 -0
- data/app/views/gemgento/user/passwords/new.html.haml +21 -0
- data/app/views/gemgento/user/registration_session/new.haml +67 -0
- data/app/views/gemgento/user/registrations/new.html.haml +28 -0
- data/app/views/gemgento/user/saved_credit_cards/_form.haml +69 -0
- data/app/views/gemgento/user/saved_credit_cards/index.haml +25 -0
- data/app/views/gemgento/user/saved_credit_cards/new.haml +13 -0
- data/app/views/gemgento/user/sessions/new.html.haml +29 -0
- data/app/views/gemgento/user/shared/_links.erb +25 -0
- data/app/views/gemgento/user/shared/_user_header.haml +8 -0
- data/app/views/gemgento/user/wishlist_items/index.html.haml +20 -0
- data/app/views/kaminari/_first_page.html.haml +9 -0
- data/app/views/kaminari/_gap.html.haml +8 -0
- data/app/views/kaminari/_last_page.html.haml +9 -0
- data/app/views/kaminari/_next_page.html.haml +9 -0
- data/app/views/kaminari/_page.html.haml +10 -0
- data/app/views/kaminari/_paginator.html.haml +18 -0
- data/app/views/kaminari/_prev_page.html.haml +9 -0
- data/app/views/layouts/_footer.html.haml +5 -0
- data/app/views/layouts/_header.html.haml +39 -0
- data/app/views/layouts/gemgento.html.haml +22 -0
- data/app/workers/gemgento/apply_price_rule.rb +24 -0
- data/app/workers/gemgento/cart/add_item_worker.rb +23 -0
- data/app/workers/gemgento/cart/update_item_worker.rb +18 -0
- data/bin/rails +12 -0
- data/config/gemgento_config.yml +12 -0
- data/config/initializers/active_admin.rb +14 -0
- data/config/initializers/active_touch.rb +3 -0
- data/config/initializers/assets.rb +1 -0
- data/config/initializers/boolean_typecasting.rb +37 -0
- data/config/initializers/devise.rb +49 -0
- data/config/initializers/gemgento_config.rb +67 -0
- data/config/initializers/mime_types.rb +4 -0
- data/config/initializers/state_machine.rb +7 -0
- data/config/locales/en.yml +24 -0
- data/config/routes.rb +138 -0
- data/db/migrate/20151201231300_add_validation_indexes_to_gemgento_products.rb +22 -0
- data/db/migrate/20160122174450_gemgento2.rb +969 -0
- data/db/migrate/20160217142910_create_gemgento_imports.rb +15 -0
- data/db/migrate/20160219235858_add_timestamps_to_gemgento_asset_files.rb +6 -0
- data/gemgento.gemspec +44 -0
- data/lib/devise/models/validatable.rb +76 -0
- data/lib/gemgento.rb +24 -0
- data/lib/gemgento/admin/admin_user.rb +29 -0
- data/lib/gemgento/admin/api_jobs.rb +30 -0
- data/lib/gemgento/admin/image_import.rb +87 -0
- data/lib/gemgento/admin/inventory_import.rb +81 -0
- data/lib/gemgento/admin/product_import.rb +138 -0
- data/lib/gemgento/admin/product_positions.rb +49 -0
- data/lib/gemgento/admin/stores.rb +33 -0
- data/lib/gemgento/admin/subscribers.rb +31 -0
- data/lib/gemgento/admin/syncs.rb +26 -0
- data/lib/gemgento/api/soap/authnetcim/payment.rb +127 -0
- data/lib/gemgento/api/soap/catalog/category.rb +237 -0
- data/lib/gemgento/api/soap/catalog/product.rb +481 -0
- data/lib/gemgento/api/soap/catalog/product_attribute.rb +147 -0
- data/lib/gemgento/api/soap/catalog/product_attribute_media.rb +202 -0
- data/lib/gemgento/api/soap/catalog/product_attribute_set.rb +80 -0
- data/lib/gemgento/api/soap/catalog/product_tag.rb +146 -0
- data/lib/gemgento/api/soap/catalog/product_tier_price.rb +21 -0
- data/lib/gemgento/api/soap/catalog/search.rb +27 -0
- data/lib/gemgento/api/soap/catalog_inventory/stock_item.rb +87 -0
- data/lib/gemgento/api/soap/catalog_rule/rule.rb +54 -0
- data/lib/gemgento/api/soap/checkout/cart.rb +85 -0
- data/lib/gemgento/api/soap/checkout/coupon.rb +37 -0
- data/lib/gemgento/api/soap/checkout/customer.rb +87 -0
- data/lib/gemgento/api/soap/checkout/payment.rb +59 -0
- data/lib/gemgento/api/soap/checkout/product.rb +138 -0
- data/lib/gemgento/api/soap/checkout/shipping.rb +37 -0
- data/lib/gemgento/api/soap/customer/address.rb +143 -0
- data/lib/gemgento/api/soap/customer/customer.rb +200 -0
- data/lib/gemgento/api/soap/directory/country.rb +49 -0
- data/lib/gemgento/api/soap/directory/region.rb +50 -0
- data/lib/gemgento/api/soap/enterprise_gift_message/gift_message.rb +26 -0
- data/lib/gemgento/api/soap/gift_card.rb +35 -0
- data/lib/gemgento/api/soap/globalcollect/saved_cc.rb +58 -0
- data/lib/gemgento/api/soap/miscellaneous/store.rb +47 -0
- data/lib/gemgento/api/soap/sales/order.rb +249 -0
- data/lib/gemgento/api/soap/sales/order_shipment.rb +122 -0
- data/lib/gemgento/api/soap/sales/recurring_profile.rb +88 -0
- data/lib/gemgento/api/soap/stock_notification.rb +27 -0
- data/lib/gemgento/engine.rb +61 -0
- data/lib/gemgento/version.rb +3 -0
- data/lib/generators/gemgento/assets_generator.rb +11 -0
- data/lib/generators/gemgento/install_generator.rb +34 -0
- data/lib/generators/gemgento/templates/active_admin.css.scss +1 -0
- data/lib/generators/gemgento/templates/active_admin.js.coffee +1 -0
- data/lib/generators/gemgento/templates/active_admin.rb +3 -0
- data/lib/generators/gemgento/views_generator.rb +10 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +20 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/gemgento_config.yml +22 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/gemgento/price_tiers.rb +8 -0
- data/spec/factories/gemgento/product_attribute_sets.rb +22 -0
- data/spec/factories/gemgento/product_attributes.rb +5 -0
- data/spec/factories/gemgento/products.rb +14 -0
- data/spec/factories/gemgento/stores.rb +12 -0
- data/spec/factories/gemgento/user_groups.rb +6 -0
- data/spec/factories/gemgento/users.rb +13 -0
- data/spec/factories/gemgento_imports.rb +12 -0
- data/spec/model/gemgento/price_tier_spec.rb +45 -0
- data/spec/rails_helper.rb +55 -0
- data/spec/services/gemgento/price_spec.rb +152 -0
- data/spec/spec_helper.rb +87 -0
- metadata +919 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Gemgento
|
|
2
|
+
|
|
3
|
+
# @author Gemgento LLC
|
|
4
|
+
class Payment < ActiveRecord::Base
|
|
5
|
+
belongs_to :payable, polymorphic: true
|
|
6
|
+
|
|
7
|
+
attr_accessor :cc_number, :cc_cid, :save_card, :payment_id
|
|
8
|
+
|
|
9
|
+
before_save :set_cc_last4, if: Proc.new { |payment| payment.cc_number.present? || payment.payment_id.present? }
|
|
10
|
+
|
|
11
|
+
validates :method, :payable, presence: true
|
|
12
|
+
|
|
13
|
+
REDIRECTING_PAYMENT_METHODS = {
|
|
14
|
+
paypal_standard: %w[confirm_after],
|
|
15
|
+
paypal_express: %w[payment_after confirm_before]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def is_redirecting_payment_method?(step = nil)
|
|
19
|
+
if REDIRECTING_PAYMENT_METHODS.has_key?(self.method.to_sym)
|
|
20
|
+
if step.blank?
|
|
21
|
+
return true
|
|
22
|
+
else
|
|
23
|
+
return REDIRECTING_PAYMENT_METHODS[self.method.to_sym].include? step
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
return false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Set cc_last4 to the last 4 numbers of cc_number.
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
33
|
+
def set_cc_last4
|
|
34
|
+
if self.cc_number.present?
|
|
35
|
+
self.cc_last4 = cc_number[-4..-1]
|
|
36
|
+
|
|
37
|
+
elsif saved_cc = Gemgento::SavedCreditCard.find_by(user: self.payable.user, token: self.payment_id)
|
|
38
|
+
self.cc_last4 = saved_cc.cc_number.to_s[-4..-1]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def is_new_credit_card_payment?
|
|
43
|
+
payable_type == 'Gemgento::Quote' &&
|
|
44
|
+
payable.converted_at.nil? &&
|
|
45
|
+
!is_redirecting_payment_method? &&
|
|
46
|
+
method != 'free' && payment_id.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
module Gemgento
|
|
2
|
+
|
|
3
|
+
# @author Gemgento LLC
|
|
4
|
+
class PriceRule < ActiveRecord::Base
|
|
5
|
+
serialize :conditions, Hash
|
|
6
|
+
|
|
7
|
+
has_and_belongs_to_many :stores, join_table: 'gemgento_price_rules_stores', class_name: 'Store'
|
|
8
|
+
has_and_belongs_to_many :user_groups, join_table: 'gemgento_price_rules_user_groups', class_name: 'UserGroup'
|
|
9
|
+
|
|
10
|
+
after_save :apply, if: -> { changed? }
|
|
11
|
+
|
|
12
|
+
default_scope ->{ order(sort_order: :asc) }
|
|
13
|
+
scope :active, -> { where(is_active: true) }
|
|
14
|
+
|
|
15
|
+
# Calculate a product price based on PriceRules.
|
|
16
|
+
#
|
|
17
|
+
# @param product [Gemgento::Product]
|
|
18
|
+
# @param user_group [Gemgento::UserGroup]
|
|
19
|
+
# @param store [Gemgento::Store]
|
|
20
|
+
# @return [Float]
|
|
21
|
+
def self.calculate_price(product, user_group = nil, store = nil)
|
|
22
|
+
store ||= Store.current
|
|
23
|
+
price = product.attribute_value('price', store).to_f
|
|
24
|
+
user_group ||= UserGroup.find_by(magento_id: 0)
|
|
25
|
+
|
|
26
|
+
PriceRule.active.each do |price_rule|
|
|
27
|
+
if price_rule.is_valid?(product, user_group, store)
|
|
28
|
+
price = price_rule.calculate(price)
|
|
29
|
+
return price if price_rule.stop_rules_processing?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return price
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Determines if the rule is valid for a product.
|
|
37
|
+
#
|
|
38
|
+
# @param product [Gemgento::Product]
|
|
39
|
+
# @param user_group [Gemgento::UserGroup]
|
|
40
|
+
# @param store [Gemgento::Store]
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def is_valid?(product, user_group, store)
|
|
43
|
+
if !self.is_active?
|
|
44
|
+
return false
|
|
45
|
+
elsif !self.stores.include?(store)
|
|
46
|
+
return false
|
|
47
|
+
elsif !self.user_groups.include?(user_group)
|
|
48
|
+
return false
|
|
49
|
+
elsif !self.from_date.nil? && Date.today < from_date
|
|
50
|
+
return false
|
|
51
|
+
elsif !self.to_date.nil? && Date.today > to_date
|
|
52
|
+
return false
|
|
53
|
+
else
|
|
54
|
+
return PriceRule.meets_condition?(self.conditions, product, store)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Calculate the discount based on rule actions.
|
|
59
|
+
#
|
|
60
|
+
# @param price [Float]
|
|
61
|
+
# @return Float
|
|
62
|
+
def calculate(price)
|
|
63
|
+
case self.simple_action
|
|
64
|
+
when 'to_fixed'
|
|
65
|
+
return [self.discount_amount.to_f, price].min
|
|
66
|
+
when 'to_percent'
|
|
67
|
+
return price * self.discount_amount.to_f / 100
|
|
68
|
+
when 'by_fixed'
|
|
69
|
+
return [0, price - self.discount_amount.to_f].max
|
|
70
|
+
when 'by_percent'
|
|
71
|
+
return price * (1 - self.discount_amount.to_f / 100)
|
|
72
|
+
else
|
|
73
|
+
return price
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Determines if the condition is met.
|
|
78
|
+
#
|
|
79
|
+
# @param condition [Hash]
|
|
80
|
+
# @param product [Gemgento::Product]
|
|
81
|
+
# @param store [Gemgento::Store]
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def self.meets_condition?(condition, product, store)
|
|
84
|
+
if condition['type'] == 'catalogrule/rule_condition_combine'
|
|
85
|
+
return meets_condition_combine?(condition, product, store)
|
|
86
|
+
elsif condition['type'] == 'catalogrule/rule_condition_product'
|
|
87
|
+
return meets_condition_product?(condition, product, store)
|
|
88
|
+
else
|
|
89
|
+
return false # non-determinable condition type
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Determines if the combined child conditions are met.
|
|
94
|
+
#
|
|
95
|
+
# @param condition [Hash]
|
|
96
|
+
# @param product [Gemgento::Product]
|
|
97
|
+
# @param store [Gemgento::Store]
|
|
98
|
+
# @return [Boolean]
|
|
99
|
+
def self.meets_condition_combine?(condition, product, store)
|
|
100
|
+
return true if condition['conditions'].nil?
|
|
101
|
+
|
|
102
|
+
condition['conditions'].each do |child_condition|
|
|
103
|
+
|
|
104
|
+
if meets_condition?(child_condition, product, store) # the condition has been met
|
|
105
|
+
if condition['aggregator'] == 'all' && condition['value'] == '0' # all conditions not met
|
|
106
|
+
return false
|
|
107
|
+
elsif condition['aggregator'] == 'any' && condition['value'] == '1' # any condition met
|
|
108
|
+
return true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
else # the condition has not be met
|
|
112
|
+
if condition['aggregator'] == 'all' && condition['value'] == '1' # all conditions met
|
|
113
|
+
return false
|
|
114
|
+
elsif condition['aggregator'] == 'any' && condition['value'] == '0' # any condition not be met
|
|
115
|
+
return true
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if condition['aggregator'] == 'all' # all conditions evaluated and all met
|
|
121
|
+
return true
|
|
122
|
+
else # all conditions evaluated and none met
|
|
123
|
+
return false
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Determines if the product conditions are met.
|
|
128
|
+
#
|
|
129
|
+
# @param condition [Hash]
|
|
130
|
+
# @param product [Gemgento::Product]
|
|
131
|
+
# @param store [Gemgento::Store]
|
|
132
|
+
# @return [Boolean]
|
|
133
|
+
def self.meets_condition_product?(condition, product, store)
|
|
134
|
+
if condition['attribute'] == 'category_ids'
|
|
135
|
+
return meets_category_condition?(condition, product, store)
|
|
136
|
+
elsif condition['attribute'] == 'attribute_set_id'
|
|
137
|
+
return meets_attribute_set_condition?(condition, product)
|
|
138
|
+
else
|
|
139
|
+
return meets_attribute_condition?(condition, product, store)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Determines if a product meets a category based condition.
|
|
144
|
+
#
|
|
145
|
+
# @param condition [Hash]
|
|
146
|
+
# @param product [Gemgento::Product]
|
|
147
|
+
# @param store [Gemgento::Store]
|
|
148
|
+
# @return [Boolean]
|
|
149
|
+
def self.meets_category_condition?(condition, product, store)
|
|
150
|
+
magento_category_ids = ProductCategory.where(product: product, store: store).includes(:category).map{ |pc| pc.category.magento_id unless pc.category.nil? }.uniq
|
|
151
|
+
condition_category_ids = condition['value'].split(',').map(&:to_i)
|
|
152
|
+
|
|
153
|
+
case condition['operator']
|
|
154
|
+
when *%w[== {} ()]
|
|
155
|
+
return (magento_category_ids & condition_category_ids).any?
|
|
156
|
+
when *%w[!= !{} !()]
|
|
157
|
+
return (magento_category_ids & condition_category_ids).empty?
|
|
158
|
+
else
|
|
159
|
+
return false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Determines if a product meets an attribute set based condition.
|
|
164
|
+
#
|
|
165
|
+
# @param condition [Hash]
|
|
166
|
+
# @param product [Gemgento::Product]
|
|
167
|
+
# @return [Boolean]
|
|
168
|
+
def self.meets_attribute_set_condition?(condition, product)
|
|
169
|
+
magento_attribute_set_id = product.product_attribute_set.magento_id
|
|
170
|
+
condition_attribute_set_id = condition['value'].to_i
|
|
171
|
+
|
|
172
|
+
case condition['operator']
|
|
173
|
+
when *%w[== {} ()]
|
|
174
|
+
return magento_attribute_set_id == condition_attribute_set_id
|
|
175
|
+
when *%w[!= !{} !()]
|
|
176
|
+
return magento_attribute_set_id != condition_attribute_set_id
|
|
177
|
+
else
|
|
178
|
+
return false
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Determines if a product meets an attribute based condition.
|
|
183
|
+
#
|
|
184
|
+
# @param condition [Hash]
|
|
185
|
+
# @param product [Gemgento::Product]
|
|
186
|
+
# @param store [Gemgento::Store]
|
|
187
|
+
# @return [Boolean]
|
|
188
|
+
def self.meets_attribute_condition?(condition, product, store)
|
|
189
|
+
return false unless attribute = ProductAttribute.find_by(code: condition['attribute'])
|
|
190
|
+
|
|
191
|
+
product_value = product.attribute_value(attribute.code, store).to_s.downcase
|
|
192
|
+
|
|
193
|
+
if attribute.frontend_input == 'select'
|
|
194
|
+
return false unless option = attribute.product_attribute_options.find_by(value: condition['value'], store: store)
|
|
195
|
+
condition_value = option.label.downcase
|
|
196
|
+
else
|
|
197
|
+
condition_value = condition['value'].downcase
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
case condition['operator']
|
|
201
|
+
when '==' # is
|
|
202
|
+
return product_value == condition_value
|
|
203
|
+
when '!=' # is not
|
|
204
|
+
return product_value != condition_value
|
|
205
|
+
when '>=' # equals or greater than
|
|
206
|
+
return product_value >= condition_value
|
|
207
|
+
when '<=' # equals or less than
|
|
208
|
+
return product_value <= condition_value
|
|
209
|
+
when '>' # greater than
|
|
210
|
+
return product_value > condition_value
|
|
211
|
+
when '<' # less than
|
|
212
|
+
return product_value < condition_value
|
|
213
|
+
when '{}' # contains
|
|
214
|
+
return product_value.include?(condition_value)
|
|
215
|
+
when '!{}' # does not contain
|
|
216
|
+
return !product_value.include?(condition_value)
|
|
217
|
+
when '()' # is one of
|
|
218
|
+
return condition_value.split(',').map(&:strip).include?(product_value)
|
|
219
|
+
when '!()' # is not one of
|
|
220
|
+
return !condition_value.split(',').map(&:strip).include?(product_value)
|
|
221
|
+
else
|
|
222
|
+
return false
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Get the earliest date and time that a price rule will expire for a given product.
|
|
227
|
+
#
|
|
228
|
+
# @param product [Gemgento::Product]
|
|
229
|
+
# @param user_group [Gemgento::UserGroup]
|
|
230
|
+
# @param store [Gemgento::Store]
|
|
231
|
+
# @return [DateTime, nil]
|
|
232
|
+
def self.first_to_expire(product, user_group, store)
|
|
233
|
+
expires_at = nil
|
|
234
|
+
|
|
235
|
+
PriceRule.active.each do |price_rule|
|
|
236
|
+
next if price_rule.to_date.nil?
|
|
237
|
+
|
|
238
|
+
if price_rule.is_valid?(product, user_group, store)
|
|
239
|
+
if expires_at.nil? || price_rule.to_date.end_of_day < expires_at
|
|
240
|
+
expires_at = price_rule.to_date.end_of_day
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
return expires_at
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
private
|
|
249
|
+
|
|
250
|
+
# Touch products affected by the PriceRule
|
|
251
|
+
#
|
|
252
|
+
# @return [Void]
|
|
253
|
+
def apply
|
|
254
|
+
ApplyPriceRule.perform_async(self.id)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Gemgento
|
|
2
|
+
class PriceTier < ActiveRecord::Base
|
|
3
|
+
include Gemgento::ProductTouches
|
|
4
|
+
|
|
5
|
+
belongs_to :store, class_name: 'Gemgento::Store'
|
|
6
|
+
belongs_to :product, class_name: 'Gemgento::Product'
|
|
7
|
+
belongs_to :user_group, class_name: 'Gemgento::UserGroup'
|
|
8
|
+
|
|
9
|
+
validates :store, :product, :quantity, :price, presence: true
|
|
10
|
+
|
|
11
|
+
# Check if PriceTier is valid for the given quantity and user.
|
|
12
|
+
#
|
|
13
|
+
# @param quantity [Float]
|
|
14
|
+
# @param user_group [Gemgento::UserGroup]
|
|
15
|
+
def is_valid?(quantity, user_group)
|
|
16
|
+
return quantity >= self.quantity && (self.user_group.nil? || user_group == self.user_group)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param product [Gemgento::Product]
|
|
20
|
+
# @param quantity [Float]
|
|
21
|
+
# @param user_group [Gemgento::UserGroup]
|
|
22
|
+
def self.calculate_price(product, quantity = 1.0, user_group = nil, store = nil)
|
|
23
|
+
store ||= Gemgento::Store.current
|
|
24
|
+
price = product.attribute_value('price', store).to_f
|
|
25
|
+
user_group ||= UserGroup.find_by(magento_id: 0)
|
|
26
|
+
|
|
27
|
+
product.price_tiers.where(store: store).where('quantity <= ?', quantity).each do |price_tier|
|
|
28
|
+
next unless price_tier.is_valid? quantity, user_group
|
|
29
|
+
price = price_tier.price if price_tier.price < price
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return price
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
module Gemgento
|
|
2
|
+
|
|
3
|
+
# @author Gemgento LLC
|
|
4
|
+
class Product < ActiveRecord::Base
|
|
5
|
+
|
|
6
|
+
belongs_to :product_attribute_set
|
|
7
|
+
|
|
8
|
+
has_many :assets, dependent: :destroy
|
|
9
|
+
has_many :bundle_items, class_name: 'Gemgento::Bundle::Item', dependent: :destroy
|
|
10
|
+
has_many :bundle_options, class_name: 'Gemgento::Bundle::Option', dependent: :destroy
|
|
11
|
+
has_many :categories, -> { uniq }, through: :product_categories, class_name: 'Gemgento::Category'
|
|
12
|
+
has_many :inventories, class_name: 'Gemgento::Inventory'
|
|
13
|
+
has_many :line_items, class_name: 'Gemgento::LineItem'
|
|
14
|
+
has_many :orders, through: :line_items, source: :itemizable, source_type: 'Gemgento::Order', class_name: 'Gemgento::Order'
|
|
15
|
+
has_many :price_tiers, class_name: 'Gemgento::PriceTier'
|
|
16
|
+
has_many :product_attribute_values, class_name: 'Gemgento::ProductAttributeValue', dependent: :destroy
|
|
17
|
+
has_many :product_attributes, through: :product_attribute_values, class_name: 'Gemgento::ProductAttribute'
|
|
18
|
+
has_many :product_attribute_options, through: :product_attribute_values, class_name: 'Gemgento::ProductAttributeOption'
|
|
19
|
+
has_many :product_categories, class_name: '::Gemgento::ProductCategory', dependent: :destroy
|
|
20
|
+
has_many :quotes, through: :line_items, source: :itemizable, source_type: 'Gemgento::Quote', class_name: 'Gemgento::Quote'
|
|
21
|
+
has_many :relations, as: :relatable, class_name: 'Relation', dependent: :destroy
|
|
22
|
+
has_many :shipment_items
|
|
23
|
+
has_many :wishlist_items
|
|
24
|
+
has_many :users, through: :wishlist_items
|
|
25
|
+
|
|
26
|
+
has_one :shopify_adapter, class_name: 'Adapter::ShopifyAdapter', as: :gemgento_model
|
|
27
|
+
|
|
28
|
+
has_and_belongs_to_many :stores, -> { distinct }, join_table: 'gemgento_stores_products', class_name: 'Store'
|
|
29
|
+
has_and_belongs_to_many :tags, class_name: 'Tag', join_table: 'gemgento_products_tags'
|
|
30
|
+
has_and_belongs_to_many :configurable_attributes, -> { distinct }, join_table: 'gemgento_configurable_attributes', class_name: 'ProductAttribute'
|
|
31
|
+
has_and_belongs_to_many :configurable_products, -> { distinct },
|
|
32
|
+
join_table: 'gemgento_configurable_simple_relations',
|
|
33
|
+
foreign_key: 'simple_product_id',
|
|
34
|
+
association_foreign_key: 'configurable_product_id',
|
|
35
|
+
class_name: 'Product'
|
|
36
|
+
has_and_belongs_to_many :simple_products, -> { distinct },
|
|
37
|
+
join_table: 'gemgento_configurable_simple_relations',
|
|
38
|
+
foreign_key: 'configurable_product_id',
|
|
39
|
+
association_foreign_key: 'simple_product_id',
|
|
40
|
+
class_name: 'Product'
|
|
41
|
+
|
|
42
|
+
scope :eager, -> { includes([{product_attribute_values: :product_attribute}, {assets: [:asset_file, :asset_types]}, :inventories]) }
|
|
43
|
+
scope :configurable, -> { where(magento_type: 'configurable') }
|
|
44
|
+
scope :simple, -> { where(magento_type: 'simple') }
|
|
45
|
+
scope :enabled, -> { where(status: true) }
|
|
46
|
+
scope :disabled, -> { where(status: false) }
|
|
47
|
+
scope :catalog_visible, -> { where(visibility: [2, 4]) }
|
|
48
|
+
scope :search_visible, -> { where(visibility: [3, 4]) }
|
|
49
|
+
scope :visible, -> { where(visibility: [2, 3, 4]) }
|
|
50
|
+
scope :not_deleted, -> { where(deleted_at: nil) }
|
|
51
|
+
scope :active, -> { where(deleted_at: nil, status: true) }
|
|
52
|
+
|
|
53
|
+
touch :categories, after_touch: :after_touch
|
|
54
|
+
touch :configurable_products, after_touch: :after_touch
|
|
55
|
+
touch :bundle_items, after_touch: :after_touch
|
|
56
|
+
|
|
57
|
+
after_find :manage_cache_expires_at
|
|
58
|
+
|
|
59
|
+
before_save :create_magento_product, if: -> { sync_needed? && magento_id.nil? }
|
|
60
|
+
before_save :update_magento_product, if: -> { sync_needed? && !magento_id.nil? }
|
|
61
|
+
|
|
62
|
+
before_destroy :delete_associations
|
|
63
|
+
|
|
64
|
+
after_commit :remove_from_active_quotes, unless: Proc.new { |product| product.status && product.deleted_at.nil? }
|
|
65
|
+
|
|
66
|
+
validates :sku, uniqueness: { scope: :deleted_at }
|
|
67
|
+
validates :magento_id, uniqueness: true
|
|
68
|
+
|
|
69
|
+
attr_accessor :configurable_attribute_ordering, :attribute_values, :sync_needed
|
|
70
|
+
|
|
71
|
+
def sync_needed?
|
|
72
|
+
self.sync_needed.to_bool
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Set an attribute value.
|
|
76
|
+
#
|
|
77
|
+
# @param code [String] attribute code
|
|
78
|
+
# @param value [String, Boolean, Integer, Float, BigDecimal]
|
|
79
|
+
# @param store [Gemgento::Store]
|
|
80
|
+
# @return [Boolean]
|
|
81
|
+
def set_attribute_value(code, value, store = nil)
|
|
82
|
+
store = Store.current if store.nil?
|
|
83
|
+
|
|
84
|
+
product_attribute = ProductAttribute.find_by(code: code)
|
|
85
|
+
|
|
86
|
+
if product_attribute.nil?
|
|
87
|
+
return false
|
|
88
|
+
else
|
|
89
|
+
# enforce a single attribute value per attribute per store per product
|
|
90
|
+
product_attribute_values = ProductAttributeValue.where(product_id: self.id, product_attribute_id: product_attribute.id, store: store)
|
|
91
|
+
|
|
92
|
+
if product_attribute_values.size > 1
|
|
93
|
+
ProductAttributeValue.where(product_id: self.id, product_attribute_id: product_attribute.id, store: store).where('id != ?', product_attribute_values.first.id).destroy_all
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# set the attribute value
|
|
97
|
+
product_attribute_value = ProductAttributeValue.where(product_id: self.id, product_attribute_id: product_attribute.id, store: store).first_or_initialize
|
|
98
|
+
product_attribute_value.product = self
|
|
99
|
+
product_attribute_value.product_attribute = product_attribute
|
|
100
|
+
product_attribute_value.value = value
|
|
101
|
+
product_attribute_value.store = store
|
|
102
|
+
product_attribute_value.save!
|
|
103
|
+
|
|
104
|
+
self.product_attribute_values << product_attribute_value unless self.product_attribute_values.include?(product_attribute_value)
|
|
105
|
+
self.attribute_values = nil # reload cached attributes values
|
|
106
|
+
|
|
107
|
+
return true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get an attribute value.
|
|
112
|
+
#
|
|
113
|
+
# @param code [String] attribute code
|
|
114
|
+
# @param store [Gemgento::Store]
|
|
115
|
+
# @return [String, Boolean, nil]
|
|
116
|
+
def attribute_value(code, store = nil)
|
|
117
|
+
store = Gemgento::Store.current if store.nil?
|
|
118
|
+
product_attribute_value = self.attribute_values.select { |value| !value.product_attribute.nil? && value.product_attribute.code == code.to_s && value.store_id == store.id }.first
|
|
119
|
+
|
|
120
|
+
## if the attribute is not currently associated with the product, check if it exists
|
|
121
|
+
if product_attribute_value.nil?
|
|
122
|
+
product_attribute = Gemgento::ProductAttribute.find_by(code: code)
|
|
123
|
+
|
|
124
|
+
if product_attribute.nil? # throw an error if the code is not recognized
|
|
125
|
+
raise "Unknown product attribute code - #{code}"
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
product_attribute = product_attribute_value.product_attribute
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
value = product_attribute_value.nil? ? product_attribute.default_value : product_attribute_value.value
|
|
132
|
+
return nil if value.nil?
|
|
133
|
+
|
|
134
|
+
if product_attribute.frontend_input == 'boolean' || product_attribute.code == 'is_recurring'
|
|
135
|
+
if value == 'Yes' || value == '1' || value == '1.0'
|
|
136
|
+
value = true
|
|
137
|
+
else
|
|
138
|
+
value = false
|
|
139
|
+
end
|
|
140
|
+
elsif product_attribute.frontend_input == 'select'
|
|
141
|
+
option = product_attribute.product_attribute_options.find_by(value: value, store: store)
|
|
142
|
+
value = option.nil? ? nil : option.label
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
return value
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def attribute_values
|
|
149
|
+
@attribute_values ||= self.product_attribute_values.joins(:product_attribute).includes(:product_attribute)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Attempts to return attribute_value before error.
|
|
153
|
+
def method_missing(method, *args)
|
|
154
|
+
begin
|
|
155
|
+
return self.attribute_value(method)
|
|
156
|
+
rescue
|
|
157
|
+
super
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Determine if product has a specific inventory level.
|
|
162
|
+
#
|
|
163
|
+
# @param quantity [Integer, BigDecimal, Float]
|
|
164
|
+
# @param store [Gemgento::Store]
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
def in_stock?(quantity = 1, store = nil)
|
|
167
|
+
store = Store.current if store.nil?
|
|
168
|
+
|
|
169
|
+
if self.magento_type == 'configurable'
|
|
170
|
+
inventories = Inventory.where(product_id: self.simple_products.active.select(:id), store: store)
|
|
171
|
+
|
|
172
|
+
if inventories.empty? # no inventories means inventory is not tracked
|
|
173
|
+
return true
|
|
174
|
+
else
|
|
175
|
+
inventories.each do |inventory|
|
|
176
|
+
return true if inventory.in_stock?(quantity)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
return false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
else
|
|
183
|
+
if inventory = self.inventories.find_by(store: store)
|
|
184
|
+
return inventory.in_stock?(quantity)
|
|
185
|
+
else
|
|
186
|
+
return true
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Mark a product deleted.
|
|
192
|
+
#
|
|
193
|
+
# @return [Void]
|
|
194
|
+
def mark_deleted
|
|
195
|
+
self.deleted_at = Time.now
|
|
196
|
+
self.shopify_adapter.destroy if self.shopify_adapter
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Mark a product deleted and save.
|
|
200
|
+
#
|
|
201
|
+
# @return [Void]
|
|
202
|
+
def mark_deleted!
|
|
203
|
+
mark_deleted
|
|
204
|
+
self.save
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Filter products based on attribute values.
|
|
208
|
+
#
|
|
209
|
+
# filter example:
|
|
210
|
+
# {attribute: Gemgento::ProductAttribute.find_by(code: 'size'), value: 'large'})
|
|
211
|
+
# or
|
|
212
|
+
# {attribute: Gemgento::ProductAttribute.find_by(code: 'size'), value: %w[large small]})
|
|
213
|
+
# or
|
|
214
|
+
# {attribute: [Gemgento::ProductAttribute.find_by(code: 'size'), Gemgento::ProductAttribute.find_by(code: 'dimension')], value: 'large'})
|
|
215
|
+
# or
|
|
216
|
+
# {attribute: [Gemgento::ProductAttribute.find_by(code: 'size'), Gemgento::ProductAttribute.find_by(code: 'dimension')], value: %w[large small]})
|
|
217
|
+
# or
|
|
218
|
+
# [{attribute: Gemgento::ProductAttribute.find_by(code: 'size'), value: 'large'}), {attribute: Gemgento::ProductAttribute.find_by(code: 'color'), value: 'red'})]
|
|
219
|
+
#
|
|
220
|
+
# Filters can also take an optional operand, the default operand is '=' or 'IN' for an array
|
|
221
|
+
#
|
|
222
|
+
# @param filters [Hash, Array(Hash)]
|
|
223
|
+
# @param store [Gemgento::Store]
|
|
224
|
+
# @return [ActiveRecord::Result]
|
|
225
|
+
def self.filter(filters, store = nil)
|
|
226
|
+
store = Store.current if store.nil?
|
|
227
|
+
|
|
228
|
+
filters = [filters] unless filters.is_a? Array
|
|
229
|
+
products = self
|
|
230
|
+
|
|
231
|
+
filters.each_with_index do |filter, index|
|
|
232
|
+
filter[:attribute] = [filter[:attribute]] unless filter[:attribute].is_a? Array
|
|
233
|
+
operand = filter[:value].is_a?(Array) ? 'IN' : ( filter.has_key?(:operand) ? filter[:operand] : '=' )
|
|
234
|
+
value_placeholder = filter[:value].is_a?(Array) ? '(?)' : '?'
|
|
235
|
+
|
|
236
|
+
unless filter[:attribute][0].frontend_input == 'select'
|
|
237
|
+
products = products.joins(ActiveRecord::Base.escape_sql(
|
|
238
|
+
"INNER JOIN gemgento_product_attribute_values AS value#{index} ON value#{index}.product_id = gemgento_products.id AND value#{index}.value #{operand} #{value_placeholder} AND value#{index}.store_id = ?
|
|
239
|
+
INNER JOIN gemgento_product_attributes AS attribute#{index} ON attribute#{index}.id = value#{index}.product_attribute_id AND attribute#{index}.id IN (?)",
|
|
240
|
+
filter[:value],
|
|
241
|
+
store.id,
|
|
242
|
+
filter[:attribute].map { |a| a.id }
|
|
243
|
+
)).distinct.readonly(false)
|
|
244
|
+
else
|
|
245
|
+
products = products.joins(ActiveRecord::Base.escape_sql(
|
|
246
|
+
"INNER JOIN gemgento_product_attribute_values AS value#{index} ON value#{index}.product_id = gemgento_products.id
|
|
247
|
+
INNER JOIN gemgento_product_attributes AS attribute#{index} ON attribute#{index}.id = value#{index}.product_attribute_id
|
|
248
|
+
AND attribute#{index}.id IN (?)
|
|
249
|
+
INNER JOIN gemgento_product_attribute_options AS option#{index} ON option#{index}.product_attribute_id = attribute#{index}.id
|
|
250
|
+
AND value#{index}.value = option#{index}.value
|
|
251
|
+
AND option#{index}.label #{operand} #{value_placeholder}",
|
|
252
|
+
filter[:attribute].map { |a| a.id },
|
|
253
|
+
filter[:value]
|
|
254
|
+
)).distinct.readonly(false) # does not compare against values
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
return products
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Order ActiveRecord result by attribute values.
|
|
262
|
+
#
|
|
263
|
+
# @param attribute [Gemgento::ProductAttribute]
|
|
264
|
+
# @param direction ['ASC', 'DESC']
|
|
265
|
+
# @param is_numeric [Boolean]
|
|
266
|
+
# @param store [Gemgento::Store]
|
|
267
|
+
# @return [ActiveRecord::Result]
|
|
268
|
+
def self.order_by_attribute(attribute, direction = 'ASC', is_numeric = false, store = nil)
|
|
269
|
+
store = Store.current if store.nil?
|
|
270
|
+
raise 'Direction must be equivalent to ASC or DESC' if direction != 'ASC' and direction != 'DESC'
|
|
271
|
+
|
|
272
|
+
products = self
|
|
273
|
+
|
|
274
|
+
unless attribute.frontend_input == 'select'
|
|
275
|
+
products = products.joins(
|
|
276
|
+
ActiveRecord::Base.escape_sql(
|
|
277
|
+
'INNER JOIN gemgento_product_attribute_values ON gemgento_product_attribute_values.product_id = gemgento_products.id AND gemgento_product_attribute_values.product_attribute_id = ? AND gemgento_product_attribute_values.store_id = ? ' +
|
|
278
|
+
'INNER JOIN gemgento_product_attributes ON gemgento_product_attributes.id = gemgento_product_attribute_values.product_attribute_id ',
|
|
279
|
+
attribute.id,
|
|
280
|
+
store.id
|
|
281
|
+
))
|
|
282
|
+
|
|
283
|
+
if is_numeric
|
|
284
|
+
products = products.reorder("CAST(gemgento_product_attribute_values.value AS SIGNED) #{direction}")
|
|
285
|
+
else
|
|
286
|
+
products = products.reorder("gemgento_product_attribute_values.value #{direction}")
|
|
287
|
+
end
|
|
288
|
+
else
|
|
289
|
+
products = products.joins(
|
|
290
|
+
ActiveRecord::Base.escape_sql(
|
|
291
|
+
'INNER JOIN gemgento_product_attribute_values ON gemgento_product_attribute_values.product_id = gemgento_products.id AND gemgento_product_attribute_values.product_attribute_id = ? ' +
|
|
292
|
+
'INNER JOIN gemgento_product_attributes ON gemgento_product_attributes.id = gemgento_product_attribute_values.product_attribute_id ' +
|
|
293
|
+
'INNER JOIN gemgento_product_attribute_options ON gemgento_product_attribute_options.product_attribute_id = gemgento_product_attributes.id AND gemgento_product_attribute_options.value = gemgento_product_attribute_values.value ' +
|
|
294
|
+
'AND gemgento_product_attribute_options.store_id = ?',
|
|
295
|
+
attribute.id,
|
|
296
|
+
store.id
|
|
297
|
+
))
|
|
298
|
+
|
|
299
|
+
if is_numeric
|
|
300
|
+
products = products.reorder("CAST(gemgento_product_attribute_options.order AS SIGNED) #{direction}")
|
|
301
|
+
else
|
|
302
|
+
products = products.reorder("gemgento_product_attribute_options.order #{direction}")
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
products = products.readonly(false)
|
|
307
|
+
|
|
308
|
+
return products
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Get the product price.
|
|
312
|
+
#
|
|
313
|
+
# @param user_group [Gemgento::UserGroup]
|
|
314
|
+
# @param store [Store]
|
|
315
|
+
# @param quantity [Float]
|
|
316
|
+
# @return Float
|
|
317
|
+
def price(user_group = nil, store = nil, quantity = 1.0)
|
|
318
|
+
Gemgento::Price.new(self, user_group, store, quantity).calculate
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Determine if product is on sale.
|
|
322
|
+
#
|
|
323
|
+
# @param user_group [Gemgento::UserGroup]
|
|
324
|
+
# @param store [Store]
|
|
325
|
+
# @param quantity [Float]
|
|
326
|
+
# @return Boolean
|
|
327
|
+
def on_sale?(user_group = nil, store = nil, quantity = 1.0)
|
|
328
|
+
return self.attribute_value('price', store).to_f != self.price(user_group, store, quantity)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Get the original, non sale, price for a product.
|
|
332
|
+
#
|
|
333
|
+
# @param [Store, nil] store
|
|
334
|
+
# @return [Float]
|
|
335
|
+
def original_price(store = nil)
|
|
336
|
+
return self.attribute_value('price', store).to_f
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Return the ordering of configurable attribute values.
|
|
340
|
+
#
|
|
341
|
+
# @param [Store, nil] store
|
|
342
|
+
# @param [Boolean] active_only
|
|
343
|
+
# @param [Hash(Hash(Array(Integer)))]
|
|
344
|
+
def configurable_attribute_order(store = nil, active_only = true)
|
|
345
|
+
self.configurable_attribute_ordering ||= self.get_configurable_attribute_ordering(store, active_only)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Calculate the ordering of configurable attribute values
|
|
349
|
+
#
|
|
350
|
+
# @param [Store, nil] store
|
|
351
|
+
# @param [Boolean] active_only
|
|
352
|
+
# @return [Hash(Hash(Array(Integer)))]
|
|
353
|
+
def get_configurable_attribute_ordering(store, active_only)
|
|
354
|
+
store = Store.current if store.nil?
|
|
355
|
+
order = {}
|
|
356
|
+
|
|
357
|
+
if self.magento_type != 'configurable' && !self.configurable_products.empty?
|
|
358
|
+
configurable_product = self.configurable_products.first
|
|
359
|
+
else
|
|
360
|
+
configurable_product = self
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
if active_only
|
|
364
|
+
simple_products = configurable_product.simple_products.active.eager
|
|
365
|
+
else
|
|
366
|
+
simple_products = self.simple_products.eager
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
configurable_attributes = self.product_attribute_set.product_attributes.
|
|
370
|
+
where(is_configurable: true, frontend_input: 'select', scope: 'global')
|
|
371
|
+
|
|
372
|
+
configurable_attributes.each do |attribute|
|
|
373
|
+
order[attribute.code] = []
|
|
374
|
+
|
|
375
|
+
simple_products = simple_products.sort_by do |simple_product|
|
|
376
|
+
if o = simple_product.product_attribute_options.find_by(product_attribute: attribute, store: store)
|
|
377
|
+
o.order
|
|
378
|
+
else
|
|
379
|
+
0
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
mapping = {}
|
|
384
|
+
simple_products.each do |simple_product|
|
|
385
|
+
value = simple_product.attribute_value(attribute.code, store)
|
|
386
|
+
mapping[value] = [] if mapping[value].nil?
|
|
387
|
+
mapping[value] << simple_product.id unless mapping[value].include? simple_product.id
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
order[attribute.code] = []
|
|
391
|
+
mapping.each do |k, value|
|
|
392
|
+
order[attribute.code] << { value: k, simple_product_ids: value }
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
return order
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Determine if the product is catalog visible.
|
|
400
|
+
#
|
|
401
|
+
# @return [Boolean]
|
|
402
|
+
def is_catalog_visible?
|
|
403
|
+
return [2, 4].include?(self.visibility)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Set the associated simple products, using an array of Magento product IDs.
|
|
407
|
+
#
|
|
408
|
+
# @param magento_ids [Array(Integer)] Magento IDs of the associated simple products
|
|
409
|
+
# @return [void]
|
|
410
|
+
def set_simple_products_by_magento_ids(magento_ids)
|
|
411
|
+
simple_product_ids = []
|
|
412
|
+
|
|
413
|
+
magento_ids.each do |magento_id|
|
|
414
|
+
simple_product = Product.active.find_by(magento_id: magento_id)
|
|
415
|
+
next if simple_product.nil?
|
|
416
|
+
|
|
417
|
+
self.simple_products << simple_product unless self.simple_products.include? simple_product
|
|
418
|
+
simple_product_ids << simple_product.id
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
self.simple_products.delete(self.simple_products.where('simple_product_id NOT IN (?)', simple_product_ids))
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Set the associated configurable products, using an array of Magento product IDs.
|
|
425
|
+
#
|
|
426
|
+
# @param magento_ids [Array(Integer)] Magento IDs of the associated configurable products
|
|
427
|
+
# @return [void]
|
|
428
|
+
def set_configurable_products_by_magento_ids(magento_ids)
|
|
429
|
+
configurable_product_ids = []
|
|
430
|
+
|
|
431
|
+
magento_ids.each do |magento_id|
|
|
432
|
+
configurable_product = Product.active.find_by(magento_id: magento_id)
|
|
433
|
+
next if configurable_product.nil?
|
|
434
|
+
|
|
435
|
+
self.configurable_products << configurable_product unless self.configurable_products.include? configurable_product
|
|
436
|
+
configurable_product_ids << configurable_product.id
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
self.configurable_products.delete(self.configurable_products.where('configurable_product_id NOT IN (?)', configurable_product_ids))
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# If the product has a cache_expires_at date set, make sure it hasn't expired. If it has, set it again.
|
|
443
|
+
#
|
|
444
|
+
# @return [DateTime, nil]
|
|
445
|
+
def manage_cache_expires_at
|
|
446
|
+
self.set_cache_expires_at if self.cache_expires_at && self.cache_expires_at < Time.now
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Calculate the datetime that the product cache should expire.
|
|
450
|
+
#
|
|
451
|
+
# @return [Void]
|
|
452
|
+
def set_cache_expires_at
|
|
453
|
+
self.cache_expires_at = nil
|
|
454
|
+
|
|
455
|
+
Store.all.each do |store|
|
|
456
|
+
UserGroup.all.each do |user_group|
|
|
457
|
+
if Price.new(self, user_group, store).has_special?
|
|
458
|
+
date = self.attribute_value('special_to_date', store)
|
|
459
|
+
else
|
|
460
|
+
date = PriceRule.first_to_expire(self, user_group, store)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
next if date.nil?
|
|
464
|
+
self.cache_expires_at = date if self.cache_expires_at.nil? || date < self.cache_expires_at
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
self.sync_needed = false
|
|
469
|
+
self.save
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def to_param
|
|
473
|
+
"#{self.id}-#{self.url_key}"
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Determine the current category of a product based on the active navigation categories related to the product.
|
|
477
|
+
# A preferred category id can be specified, if this category is not found in the products navigation categories,
|
|
478
|
+
# then the lowest level navigation category is returned.
|
|
479
|
+
#
|
|
480
|
+
# @param category_id [Integer] id of a preferred category to return
|
|
481
|
+
# @param store [Gemgento::Store]
|
|
482
|
+
# @return [Gemgento::Category]
|
|
483
|
+
def current_category(category_id = nil, store = nil)
|
|
484
|
+
@current_category ||= begin
|
|
485
|
+
self.categories(store || Gemgento::Store.current).active.navigation.find(category_id)
|
|
486
|
+
rescue ActiveRecord::RecordNotFound
|
|
487
|
+
self.categories(store || Gemgento::Store.current).active.navigation.bottom_level.first!
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Check if the product is configurable.
|
|
492
|
+
#
|
|
493
|
+
# @return [Boolean]
|
|
494
|
+
def configurable?
|
|
495
|
+
magento_type == 'configurable'
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Check if the product is simple.
|
|
499
|
+
#
|
|
500
|
+
# @return [Boolean]
|
|
501
|
+
def simple?
|
|
502
|
+
magento_type == 'simple'
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Categories related to the product.
|
|
506
|
+
#
|
|
507
|
+
# @param store [Gemgento::Store]
|
|
508
|
+
# @return []
|
|
509
|
+
def categories(store = nil)
|
|
510
|
+
return super if store.nil?
|
|
511
|
+
Gemgento::Category.where(id: self.product_categories.where(store: store).pluck(:category_id))
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def remove_from_active_quotes
|
|
515
|
+
self.line_items
|
|
516
|
+
.joins('INNER JOIN gemgento_quotes ON gemgento_line_items.itemizable_id = gemgento_quotes.id')
|
|
517
|
+
.joins('LEFT JOIN gemgento_orders ON gemgento_quotes.id = gemgento_orders.quote_id')
|
|
518
|
+
.where(gemgento_line_items: { itemizable_type: 'Gemgento::Quote' })
|
|
519
|
+
.where(gemgento_orders: { id: nil }).where('gemgento_quotes.created_at >= ?', 30.days.ago)
|
|
520
|
+
.destroy_all
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def after_touch
|
|
524
|
+
# do nothing
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
private
|
|
528
|
+
|
|
529
|
+
# Create an attribute option in Magento.
|
|
530
|
+
#
|
|
531
|
+
# @param product_attribute [ProductAttribute]
|
|
532
|
+
# @param option_label [String]
|
|
533
|
+
# @param store [Store]
|
|
534
|
+
# @return [ProductAttributeOption]
|
|
535
|
+
def create_attribute_option(product_attribute, option_label, store)
|
|
536
|
+
attribute_option = ProductAttributeOption.new
|
|
537
|
+
attribute_option.product_attribute = product_attribute
|
|
538
|
+
attribute_option.label = option_label
|
|
539
|
+
attribute_option.store = store
|
|
540
|
+
attribute_option.sync_needed = false
|
|
541
|
+
attribute_option.save
|
|
542
|
+
|
|
543
|
+
attribute_option.sync_local_to_magento
|
|
544
|
+
attribute_option.destroy
|
|
545
|
+
|
|
546
|
+
return ProductAttributeOption.find_by(product_attribute: product_attribute, label: option_label, store: store)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Create an associated magento Product.
|
|
550
|
+
#
|
|
551
|
+
# @return [Boolean]
|
|
552
|
+
def create_magento_product
|
|
553
|
+
response = API::SOAP::Catalog::Product.create(self, self.stores.first)
|
|
554
|
+
|
|
555
|
+
if response.success?
|
|
556
|
+
self.magento_id = response.body[:result]
|
|
557
|
+
self.sync_needed = false
|
|
558
|
+
|
|
559
|
+
stores.each_with_index do |store, index|
|
|
560
|
+
next if index == 0
|
|
561
|
+
response = API::SOAP::Catalog::Product.update(self, store)
|
|
562
|
+
|
|
563
|
+
# If there was a problem updating on of the stores, then make sure the product will be synced on next save.
|
|
564
|
+
# The product needs to be saved regardless, since a Magento product was created and the id must be set. So,
|
|
565
|
+
# this will not return false.
|
|
566
|
+
self.sync_needed = true unless response.success?
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
return true
|
|
570
|
+
else
|
|
571
|
+
errors.add(:base, response.body[:faultstring])
|
|
572
|
+
return false
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# Update associated Magento Product.
|
|
577
|
+
#
|
|
578
|
+
# @return [Boolean]
|
|
579
|
+
def update_magento_product
|
|
580
|
+
self.stores.each do |store|
|
|
581
|
+
response = API::SOAP::Catalog::Product.update(self, store)
|
|
582
|
+
|
|
583
|
+
unless response.success?
|
|
584
|
+
errors.add(:base, response.body[:faultstring])
|
|
585
|
+
return false
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
self.sync_needed = false
|
|
590
|
+
return true
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# Delete associations.
|
|
594
|
+
#
|
|
595
|
+
# @return [Void]
|
|
596
|
+
def delete_associations
|
|
597
|
+
self.configurable_attributes.destroy_all
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def to_ary
|
|
601
|
+
nil
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
alias :to_a :to_ary
|
|
605
|
+
|
|
606
|
+
end
|
|
607
|
+
end
|