spree_core 5.1.8 → 5.2.0.rc1
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 +4 -4
- data/app/assets/images/payment_icons/ach.svg +1 -0
- data/app/assets/images/payment_icons/achdirectdebit.svg +1 -0
- data/app/assets/images/payment_icons/ada.svg +1 -0
- data/app/assets/images/payment_icons/adyen.svg +1 -0
- data/app/assets/images/payment_icons/affinbank.svg +1 -0
- data/app/assets/images/payment_icons/affirm.svg +1 -0
- data/app/assets/images/payment_icons/afterpay.svg +1 -0
- data/app/assets/images/payment_icons/airtel_money.svg +1 -0
- data/app/assets/images/payment_icons/airteltigo_mobile_money.svg +1 -0
- data/app/assets/images/payment_icons/alandsbanken.svg +1 -0
- data/app/assets/images/payment_icons/alipay.svg +1 -0
- data/app/assets/images/payment_icons/alipay_hk.svg +1 -0
- data/app/assets/images/payment_icons/alliancebank.svg +1 -0
- data/app/assets/images/payment_icons/alma.svg +1 -0
- data/app/assets/images/payment_icons/amazon.svg +1 -0
- data/app/assets/images/payment_icons/ambank.svg +1 -0
- data/app/assets/images/payment_icons/american_express.svg +1 -0
- data/app/assets/images/payment_icons/apecoin.svg +1 -0
- data/app/assets/images/payment_icons/apple_pay.svg +1 -0
- data/app/assets/images/payment_icons/arbitrum.svg +1 -0
- data/app/assets/images/payment_icons/atome.svg +1 -0
- data/app/assets/images/payment_icons/authorizenet.svg +1 -0
- data/app/assets/images/payment_icons/avalanche.svg +1 -0
- data/app/assets/images/payment_icons/bacs.svg +1 -0
- data/app/assets/images/payment_icons/bancontact.svg +1 -0
- data/app/assets/images/payment_icons/bangkokbank.svg +1 -0
- data/app/assets/images/payment_icons/bankislam.svg +1 -0
- data/app/assets/images/payment_icons/bankmuamalat.svg +1 -0
- data/app/assets/images/payment_icons/bankofamerica.svg +1 -0
- data/app/assets/images/payment_icons/bankrakyat.svg +1 -0
- data/app/assets/images/payment_icons/base.svg +1 -0
- data/app/assets/images/payment_icons/becs.svg +1 -0
- data/app/assets/images/payment_icons/benefit.svg +1 -0
- data/app/assets/images/payment_icons/billie.svg +1 -0
- data/app/assets/images/payment_icons/bitcoin.svg +1 -0
- data/app/assets/images/payment_icons/bitcoin_cash.svg +1 -0
- data/app/assets/images/payment_icons/bizum.svg +1 -0
- data/app/assets/images/payment_icons/blik.svg +1 -0
- data/app/assets/images/payment_icons/bnbchain.svg +1 -0
- data/app/assets/images/payment_icons/bogus.svg +1 -0
- data/app/assets/images/payment_icons/bogus_app_coin.svg +1 -0
- data/app/assets/images/payment_icons/boleto.svg +1 -0
- data/app/assets/images/payment_icons/boost.svg +1 -0
- data/app/assets/images/payment_icons/braintree.svg +1 -0
- data/app/assets/images/payment_icons/buckaroopaybybank.svg +1 -0
- data/app/assets/images/payment_icons/busd.svg +1 -0
- data/app/assets/images/payment_icons/cartes_bancaires.svg +1 -0
- data/app/assets/images/payment_icons/cash.svg +1 -0
- data/app/assets/images/payment_icons/cashappafterpay.svg +1 -0
- data/app/assets/images/payment_icons/cashapppay.svg +1 -0
- data/app/assets/images/payment_icons/checkout_finance.svg +1 -0
- data/app/assets/images/payment_icons/chinabank.svg +1 -0
- data/app/assets/images/payment_icons/clearpay.svg +1 -0
- data/app/assets/images/payment_icons/collector_bank.svg +1 -0
- data/app/assets/images/payment_icons/dai.svg +1 -0
- data/app/assets/images/payment_icons/dana.svg +1 -0
- data/app/assets/images/payment_icons/dankort.svg +1 -0
- data/app/assets/images/payment_icons/danske_bank.svg +1 -0
- data/app/assets/images/payment_icons/deutschebank.svg +1 -0
- data/app/assets/images/payment_icons/diners_club.svg +1 -0
- data/app/assets/images/payment_icons/directbanktransferlatinamerica.svg +1 -0
- data/app/assets/images/payment_icons/discover.svg +1 -0
- data/app/assets/images/payment_icons/dogecoin.svg +1 -0
- data/app/assets/images/payment_icons/duitnow.svg +1 -0
- data/app/assets/images/payment_icons/eft_secure.svg +1 -0
- data/app/assets/images/payment_icons/eftpos_au.svg +1 -0
- data/app/assets/images/payment_icons/eftposau.svg +1 -0
- data/app/assets/images/payment_icons/elo.svg +1 -0
- data/app/assets/images/payment_icons/eos.svg +1 -0
- data/app/assets/images/payment_icons/eps.svg +1 -0
- data/app/assets/images/payment_icons/ethereum.svg +1 -0
- data/app/assets/images/payment_icons/eurobonuscheckout.svg +1 -0
- data/app/assets/images/payment_icons/ezcash.svg +1 -0
- data/app/assets/images/payment_icons/fantom.svg +1 -0
- data/app/assets/images/payment_icons/fpx.svg +1 -0
- data/app/assets/images/payment_icons/gcash.svg +1 -0
- data/app/assets/images/payment_icons/generic.svg +1 -0
- data/app/assets/images/payment_icons/genericbank.svg +1 -0
- data/app/assets/images/payment_icons/gift-card.svg +1 -0
- data/app/assets/images/payment_icons/giropay.svg +1 -0
- data/app/assets/images/payment_icons/glbepaypal.svg +1 -0
- data/app/assets/images/payment_icons/gmobanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/gnosis.svg +1 -0
- data/app/assets/images/payment_icons/google_pay.svg +1 -0
- data/app/assets/images/payment_icons/google_wallet.svg +1 -0
- data/app/assets/images/payment_icons/gopay.svg +1 -0
- data/app/assets/images/payment_icons/grabpay.svg +1 -0
- data/app/assets/images/payment_icons/gusd.svg +1 -0
- data/app/assets/images/payment_icons/handelsbanken.svg +1 -0
- data/app/assets/images/payment_icons/hongleongbank.svg +1 -0
- data/app/assets/images/payment_icons/hypercard.svg +1 -0
- data/app/assets/images/payment_icons/hypercash.svg +1 -0
- data/app/assets/images/payment_icons/ideal.svg +1 -0
- data/app/assets/images/payment_icons/in3viaideal.svg +1 -0
- data/app/assets/images/payment_icons/inbank.svg +1 -0
- data/app/assets/images/payment_icons/jcb.svg +1 -0
- data/app/assets/images/payment_icons/kakao_pay.svg +1 -0
- data/app/assets/images/payment_icons/kasikornbank.svg +1 -0
- data/app/assets/images/payment_icons/klarna-pay-later.svg +1 -0
- data/app/assets/images/payment_icons/klarna-pay-now.svg +1 -0
- data/app/assets/images/payment_icons/klarna-slice-it.svg +1 -0
- data/app/assets/images/payment_icons/klarna.svg +1 -0
- data/app/assets/images/payment_icons/knet.svg +1 -0
- data/app/assets/images/payment_icons/krungthaibank.svg +1 -0
- data/app/assets/images/payment_icons/landbank.svg +1 -0
- data/app/assets/images/payment_icons/laybuybyklarna.svg +1 -0
- data/app/assets/images/payment_icons/litecoin.svg +1 -0
- data/app/assets/images/payment_icons/lunchcheck.svg +1 -0
- data/app/assets/images/payment_icons/mach.svg +1 -0
- data/app/assets/images/payment_icons/mada.svg +1 -0
- data/app/assets/images/payment_icons/maestro.svg +1 -0
- data/app/assets/images/payment_icons/master.svg +1 -0
- data/app/assets/images/payment_icons/mayabank.svg +1 -0
- data/app/assets/images/payment_icons/maybank.svg +1 -0
- data/app/assets/images/payment_icons/maybankm2u.svg +1 -0
- data/app/assets/images/payment_icons/maybankqrpay.svg +1 -0
- data/app/assets/images/payment_icons/mbway.svg +1 -0
- data/app/assets/images/payment_icons/mcash.svg +1 -0
- data/app/assets/images/payment_icons/medicinosbankas.svg +1 -0
- data/app/assets/images/payment_icons/mercadopago.svg +1 -0
- data/app/assets/images/payment_icons/metrobank.svg +1 -0
- data/app/assets/images/payment_icons/mobilepay.svg +1 -0
- data/app/assets/images/payment_icons/momopay.svg +1 -0
- data/app/assets/images/payment_icons/monero.svg +1 -0
- data/app/assets/images/payment_icons/mtn_mobile_money.svg +1 -0
- data/app/assets/images/payment_icons/mybank.svg +1 -0
- data/app/assets/images/payment_icons/naver_pay.svg +1 -0
- data/app/assets/images/payment_icons/nelo.svg +1 -0
- data/app/assets/images/payment_icons/netbanking.svg +1 -0
- data/app/assets/images/payment_icons/novalnetdirectdebitach.svg +1 -0
- data/app/assets/images/payment_icons/nubank.svg +1 -0
- data/app/assets/images/payment_icons/ocbcbank.svg +1 -0
- data/app/assets/images/payment_icons/offlinebanktransferlatinamerica.svg +1 -0
- data/app/assets/images/payment_icons/ola_money.svg +1 -0
- data/app/assets/images/payment_icons/oney.svg +1 -0
- data/app/assets/images/payment_icons/onlinebanking.svg +1 -0
- data/app/assets/images/payment_icons/onlinebanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/orangemobilemoney.svg +1 -0
- data/app/assets/images/payment_icons/otpbank.svg +1 -0
- data/app/assets/images/payment_icons/oxxo.svg +1 -0
- data/app/assets/images/payment_icons/paybybank.svg +1 -0
- data/app/assets/images/payment_icons/paybybankmollie.svg +1 -0
- data/app/assets/images/payment_icons/paybybankus.svg +1 -0
- data/app/assets/images/payment_icons/paycash.svg +1 -0
- data/app/assets/images/payment_icons/payco.svg +1 -0
- data/app/assets/images/payment_icons/payeverpaybybank.svg +1 -0
- data/app/assets/images/payment_icons/payme.svg +1 -0
- data/app/assets/images/payment_icons/paynow.svg +1 -0
- data/app/assets/images/payment_icons/paynowmbank.svg +1 -0
- data/app/assets/images/payment_icons/paypal.svg +1 -0
- data/app/assets/images/payment_icons/paypo.svg +1 -0
- data/app/assets/images/payment_icons/payrexxbanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/payrexxpaybybank.svg +1 -0
- data/app/assets/images/payment_icons/paysafecash.svg +1 -0
- data/app/assets/images/payment_icons/pix.svg +1 -0
- data/app/assets/images/payment_icons/polygon.svg +1 -0
- data/app/assets/images/payment_icons/przelewy24.svg +1 -0
- data/app/assets/images/payment_icons/publicbank.svg +1 -0
- data/app/assets/images/payment_icons/publicbank_pbe.svg +1 -0
- data/app/assets/images/payment_icons/qr_promptpay.svg +1 -0
- data/app/assets/images/payment_icons/rabobank.svg +1 -0
- data/app/assets/images/payment_icons/revolut.svg +1 -0
- data/app/assets/images/payment_icons/rhbbank.svg +1 -0
- data/app/assets/images/payment_icons/riverty.svg +1 -0
- data/app/assets/images/payment_icons/safetypaybanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/samsung_pay.svg +1 -0
- data/app/assets/images/payment_icons/satispay.svg +1 -0
- data/app/assets/images/payment_icons/scalapay.svg +1 -0
- data/app/assets/images/payment_icons/scotiabank.svg +1 -0
- data/app/assets/images/payment_icons/seabankid.svg +1 -0
- data/app/assets/images/payment_icons/seabankph.svg +1 -0
- data/app/assets/images/payment_icons/sepa_bank_transfer.svg +1 -0
- data/app/assets/images/payment_icons/sepadirectdebit.svg +1 -0
- data/app/assets/images/payment_icons/seveneleven.svg +1 -0
- data/app/assets/images/payment_icons/shib.svg +1 -0
- data/app/assets/images/payment_icons/shopcash.svg +1 -0
- data/app/assets/images/payment_icons/siauliubankas.svg +1 -0
- data/app/assets/images/payment_icons/skeps.svg +1 -0
- data/app/assets/images/payment_icons/snap_checkout.svg +1 -0
- data/app/assets/images/payment_icons/sofort.svg +1 -0
- data/app/assets/images/payment_icons/softbank.svg +1 -0
- data/app/assets/images/payment_icons/spei.svg +1 -0
- data/app/assets/images/payment_icons/storecredit.svg +1 -0
- data/app/assets/images/payment_icons/stripe.svg +1 -0
- data/app/assets/images/payment_icons/sveacheckout.svg +1 -0
- data/app/assets/images/payment_icons/swedbank.svg +1 -0
- data/app/assets/images/payment_icons/swish.svg +1 -0
- data/app/assets/images/payment_icons/tbibank.svg +1 -0
- data/app/assets/images/payment_icons/thanachartbank.svg +1 -0
- data/app/assets/images/payment_icons/tnmmoney.svg +1 -0
- data/app/assets/images/payment_icons/touchngo.svg +1 -0
- data/app/assets/images/payment_icons/trustly.svg +1 -0
- data/app/assets/images/payment_icons/twint.svg +1 -0
- data/app/assets/images/payment_icons/uaevisa.svg +1 -0
- data/app/assets/images/payment_icons/unionpay.svg +1 -0
- data/app/assets/images/payment_icons/unzerbanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/upi.svg +1 -0
- data/app/assets/images/payment_icons/usbank.svg +1 -0
- data/app/assets/images/payment_icons/usdc.svg +1 -0
- data/app/assets/images/payment_icons/usdt.svg +1 -0
- data/app/assets/images/payment_icons/venmo.svg +1 -0
- data/app/assets/images/payment_icons/vipps.svg +1 -0
- data/app/assets/images/payment_icons/visa.svg +1 -0
- data/app/assets/images/payment_icons/volksbank.svg +1 -0
- data/app/assets/images/payment_icons/waavepaybybank.svg +1 -0
- data/app/assets/images/payment_icons/walley.svg +1 -0
- data/app/assets/images/payment_icons/wbtc.svg +1 -0
- data/app/assets/images/payment_icons/wechatpay.svg +1 -0
- data/app/assets/images/payment_icons/whishcheckout.svg +1 -0
- data/app/assets/images/payment_icons/wingbank.svg +1 -0
- data/app/assets/images/payment_icons/xrp.svg +1 -0
- data/app/assets/images/payment_icons/zip.svg +1 -0
- data/app/finders/spree/posts/find.rb +137 -0
- data/app/helpers/spree/addresses_helper.rb +9 -3
- data/app/helpers/spree/base_helper.rb +8 -4
- data/app/jobs/spree/images/save_from_url_job.rb +70 -0
- data/app/jobs/spree/imports/create_rows_job.rb +72 -0
- data/app/jobs/spree/imports/process_rows_job.rb +19 -0
- data/app/models/concerns/spree/adjustment_source.rb +3 -3
- data/app/models/concerns/spree/admin_user_methods.rb +52 -0
- data/app/models/concerns/spree/display_on.rb +7 -1
- data/app/models/concerns/spree/has_image_alt_text.rb +18 -0
- data/app/models/concerns/spree/has_one_link.rb +1 -1
- data/app/models/concerns/spree/has_page_links.rb +1 -1
- data/app/models/concerns/spree/linkable.rb +9 -0
- data/app/models/concerns/spree/metadata.rb +2 -0
- data/app/models/concerns/spree/metafields.rb +94 -0
- data/app/models/concerns/spree/named_type.rb +1 -1
- data/app/models/concerns/spree/user_methods.rb +20 -7
- data/app/models/concerns/spree/user_payment_source.rb +1 -1
- data/app/models/concerns/spree/user_roles.rb +3 -2
- data/app/models/spree/ability.rb +4 -0
- data/app/models/spree/address.rb +3 -1
- data/app/models/spree/adjustable/adjuster/tax.rb +2 -2
- data/app/models/spree/adjustable/adjustments_updater.rb +26 -3
- data/app/models/spree/adjustment.rb +4 -1
- data/app/models/spree/asset.rb +16 -0
- data/app/models/spree/base.rb +1 -1
- data/app/models/spree/credit_card.rb +1 -0
- data/app/models/spree/current.rb +5 -1
- data/app/models/spree/custom_domain.rb +5 -0
- data/app/models/spree/customer_return.rb +1 -0
- data/app/models/spree/export.rb +39 -2
- data/app/models/spree/exports/customers.rb +17 -0
- data/app/models/spree/exports/gift_cards.rb +13 -0
- data/app/models/spree/exports/newsletter_subscribers.rb +13 -0
- data/app/models/spree/exports/orders.rb +6 -4
- data/app/models/spree/exports/products.rb +9 -3
- data/app/models/spree/gift_card.rb +7 -1
- data/app/models/spree/gift_card_batch.rb +0 -1
- data/app/models/spree/import.rb +279 -0
- data/app/models/spree/import_mapping.rb +46 -0
- data/app/models/spree/import_row.rb +108 -0
- data/app/models/spree/import_schema.rb +25 -0
- data/app/models/spree/import_schemas/products.rb +45 -0
- data/app/models/spree/imports/products.rb +9 -0
- data/app/models/spree/invitation.rb +1 -1
- data/app/models/spree/legacy_user.rb +0 -1
- data/app/models/spree/line_item.rb +2 -7
- data/app/models/spree/metafield.rb +53 -0
- data/app/models/spree/metafield_definition.rb +91 -0
- data/app/models/spree/metafields/boolean.rb +15 -0
- data/app/models/spree/metafields/json.rb +27 -0
- data/app/models/spree/metafields/long_text.rb +7 -0
- data/app/models/spree/metafields/number.rb +15 -0
- data/app/models/spree/metafields/rich_text.rb +18 -0
- data/app/models/spree/metafields/short_text.rb +7 -0
- data/app/models/spree/newsletter_subscriber/emails.rb +12 -0
- data/app/models/spree/newsletter_subscriber.rb +64 -0
- data/app/models/spree/option_type.rb +2 -0
- data/app/models/spree/option_value.rb +1 -0
- data/app/models/spree/order/checkout.rb +7 -0
- data/app/models/spree/order.rb +8 -3
- data/app/models/spree/order_merger.rb +2 -4
- data/app/models/spree/order_promotion.rb +3 -0
- data/app/models/spree/order_updater.rb +2 -2
- data/app/models/spree/page.rb +5 -1
- data/app/models/spree/page_block.rb +4 -0
- data/app/models/spree/page_blocks/image.rb +3 -0
- data/app/models/spree/page_blocks/metafields.rb +18 -0
- data/app/models/spree/page_blocks/products/brand.rb +15 -0
- data/app/models/spree/page_blocks/products/buy_buttons.rb +5 -1
- data/app/models/spree/page_blocks/products/description.rb +18 -0
- data/app/models/spree/page_blocks/products/price.rb +1 -1
- data/app/models/spree/page_blocks/products/quantity_selector.rb +6 -2
- data/app/models/spree/page_blocks/products/title.rb +5 -1
- data/app/models/spree/page_blocks/products/variant_picker.rb +1 -1
- data/app/models/spree/page_section.rb +2 -2
- data/app/models/spree/page_sections/breadcrumbs.rb +12 -0
- data/app/models/spree/page_sections/footer.rb +20 -13
- data/app/models/spree/page_sections/image_banner.rb +6 -1
- data/app/models/spree/page_sections/image_with_text.rb +5 -0
- data/app/models/spree/page_sections/newsletter.rb +7 -0
- data/app/models/spree/page_sections/product_details.rb +38 -1
- data/app/models/spree/pages/account.rb +4 -0
- data/app/models/spree/pages/cart.rb +10 -0
- data/app/models/spree/pages/checkout.rb +6 -0
- data/app/models/spree/pages/custom.rb +4 -0
- data/app/models/spree/pages/homepage.rb +4 -0
- data/app/models/spree/pages/login.rb +4 -0
- data/app/models/spree/pages/password.rb +2 -0
- data/app/models/spree/pages/post_list.rb +4 -0
- data/app/models/spree/pages/product_details.rb +1 -0
- data/app/models/spree/pages/shop_all.rb +4 -0
- data/app/models/spree/pages/wishlist.rb +4 -0
- data/app/models/spree/payment.rb +1 -0
- data/app/models/spree/payment_method.rb +4 -5
- data/app/models/spree/payment_source.rb +1 -0
- data/app/models/spree/policy.rb +70 -0
- data/app/models/spree/post.rb +4 -2
- data/app/models/spree/post_category.rb +1 -0
- data/app/models/spree/product.rb +16 -9
- data/app/models/spree/promotion/actions/free_shipping.rb +3 -3
- data/app/models/spree/promotion.rb +24 -8
- data/app/models/spree/promotion_action.rb +1 -1
- data/app/models/spree/promotion_handler/cart.rb +1 -1
- data/app/models/spree/promotion_handler/coupon.rb +1 -1
- data/app/models/spree/promotion_rule.rb +1 -1
- data/app/models/spree/property.rb +18 -0
- data/app/models/spree/refund.rb +2 -1
- data/app/models/spree/role_user.rb +1 -1
- data/app/models/spree/shipment.rb +1 -0
- data/app/models/spree/shipping_method.rb +1 -0
- data/app/models/spree/stock_item.rb +1 -0
- data/app/models/spree/stock_transfer.rb +1 -0
- data/app/models/spree/store.rb +45 -3
- data/app/models/spree/store_credit.rb +2 -1
- data/app/models/spree/tax_category.rb +1 -1
- data/app/models/spree/tax_rate.rb +18 -3
- data/app/models/spree/taxon.rb +12 -2
- data/app/models/spree/taxonomy.rb +11 -0
- data/app/models/spree/theme.rb +1 -1
- data/app/models/spree/variant.rb +17 -0
- data/app/presenters/spree/csv/customer_presenter.rb +59 -0
- data/app/presenters/spree/csv/gift_card_presenter.rb +49 -0
- data/app/presenters/spree/csv/metafields_helper.rb +17 -0
- data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +39 -0
- data/app/presenters/spree/csv/order_line_item_presenter.rb +12 -5
- data/app/presenters/spree/csv/product_variant_presenter.rb +9 -5
- data/app/services/spree/cart/recalculate.rb +6 -4
- data/app/services/spree/cart/remove_line_item.rb +1 -2
- data/app/services/spree/imports/row_processors/base.rb +19 -0
- data/app/services/spree/imports/row_processors/product_variant.rb +228 -0
- data/app/services/spree/line_items/destroy.rb +1 -1
- data/app/services/spree/newsletter/subscribe.rb +43 -0
- data/app/services/spree/newsletter/verify.rb +30 -0
- data/app/sorters/spree/posts/sort.rb +40 -0
- data/app/views/spree/shared/_payment.html.erb +3 -3
- data/config/locales/en.yml +56 -2
- data/db/migrate/20250605131334_add_missing_fields_to_users.rb +23 -13
- data/db/migrate/20250728095629_create_spree_metafields.rb +27 -0
- data/db/migrate/20250730102644_add_missing_unique_indexes_to_spree_option_types_and_values.rb +40 -0
- data/db/migrate/20250730154601_add_unique_index_on_spree_properties_name.rb +24 -0
- data/db/migrate/20250811112056_create_spree_policies.rb +22 -0
- data/db/migrate/20250821211705_fix_unique_index_on_spree_order_promotions.rb +20 -0
- data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +7 -0
- data/db/migrate/20250826093602_create_spree_newsletter_subscribers.rb +12 -0
- data/db/migrate/20250902143122_fix_policies_store_association.rb +18 -0
- data/db/migrate/20250911205659_add_missing_index_on_spree_products_promotionable.rb +5 -0
- data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +10 -0
- data/db/migrate/20250914101955_add_missing_indexes_on_spree_adjustments.rb +6 -0
- data/db/migrate/20250915093930_add_metadata_to_spree_newsletter_subscribers.rb +20 -0
- data/db/migrate/20250923141845_create_spree_imports.rb +47 -0
- data/lib/generators/spree/cursor_rules/cursor_rules_generator.rb +19 -0
- data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +387 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +0 -1
- data/lib/generators/spree/install/templates/config/initializers/spree.rb +5 -0
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/dependencies.rb +3 -0
- data/lib/spree/core/engine.rb +57 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +3 -1
- data/lib/spree/permitted_attributes.rb +18 -3
- data/lib/spree/testing_support/capybara_config.rb +9 -3
- data/lib/spree/testing_support/factories/export_factory.rb +5 -17
- data/lib/spree/testing_support/factories/import_factory.rb +20 -0
- data/lib/spree/testing_support/factories/import_mapping_factory.rb +7 -0
- data/lib/spree/testing_support/factories/import_row_factory.rb +7 -0
- data/lib/spree/testing_support/factories/metafield_definition_factory.rb +72 -0
- data/lib/spree/testing_support/factories/metafield_factory.rb +68 -0
- data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +18 -0
- data/lib/spree/testing_support/factories/policy_factory.rb +8 -0
- data/lib/spree/testing_support/factories/post_factory.rb +8 -0
- data/lib/spree/testing_support/store.rb +11 -0
- data/lib/tasks/core.rake +128 -0
- metadata +279 -6
- data/app/models/action_text/rich_text_decorator.rb +0 -11
- data/app/presenters/spree/csv/product_presenter.rb +0 -63
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
class Import < Spree.base_class
|
|
5
|
+
include Spree::NumberIdentifier
|
|
6
|
+
include Spree::NumberAsParam
|
|
7
|
+
|
|
8
|
+
include Spree::Core::NumberGenerator.new(prefix: 'IM')
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Associations
|
|
12
|
+
#
|
|
13
|
+
belongs_to :owner, polymorphic: true # Store, Vendor, etc.
|
|
14
|
+
belongs_to :user, class_name: Spree.admin_user_class.to_s, optional: true
|
|
15
|
+
has_many :mappings, class_name: 'Spree::ImportMapping', dependent: :destroy, inverse_of: :import
|
|
16
|
+
alias import_mappings mappings
|
|
17
|
+
has_many :rows, class_name: 'Spree::ImportRow', dependent: :destroy_async, inverse_of: :import
|
|
18
|
+
alias import_rows rows
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Validations
|
|
22
|
+
#
|
|
23
|
+
validates :owner, :user, :type, presence: true
|
|
24
|
+
validates :attachment, presence: true, unless: -> { Rails.env.test? }
|
|
25
|
+
validate :ensure_whitelisted_type
|
|
26
|
+
validate :ensure_attachment_content_type
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Ransack configuration
|
|
30
|
+
#
|
|
31
|
+
self.whitelisted_ransackable_attributes = %w[number type]
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Attachments
|
|
35
|
+
#
|
|
36
|
+
has_one_attached :attachment, service: Spree.private_storage_service_name
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# State machine
|
|
40
|
+
#
|
|
41
|
+
state_machine initial: :pending, attribute: :status do
|
|
42
|
+
event :start_mapping do
|
|
43
|
+
transition to: :mapping
|
|
44
|
+
end
|
|
45
|
+
before_transition to: :mapping, do: :create_mappings
|
|
46
|
+
|
|
47
|
+
event :complete_mapping do
|
|
48
|
+
transition from: :mapping, to: :completed_mapping
|
|
49
|
+
end
|
|
50
|
+
after_transition to: :completed_mapping, do: :create_rows_async
|
|
51
|
+
|
|
52
|
+
event :start_processing do
|
|
53
|
+
transition from: :completed_mapping, to: :processing
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
event :complete do
|
|
57
|
+
transition from: :processing, to: :completed
|
|
58
|
+
end
|
|
59
|
+
after_transition to: :completed, do: :send_import_completed_email
|
|
60
|
+
after_transition to: :completed, do: :update_loader_in_import_view
|
|
61
|
+
|
|
62
|
+
event :fail do
|
|
63
|
+
transition to: :failed
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#
|
|
68
|
+
# Preferences
|
|
69
|
+
#
|
|
70
|
+
preference :delimiter, :string, default: ','
|
|
71
|
+
|
|
72
|
+
# Returns true if the import is in mapping state
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
def mapping?
|
|
75
|
+
status == 'mapping'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns true if the import is processing or completed mapping
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def processing?
|
|
81
|
+
['processing', 'completed_mapping'].include?(status)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns true if the import is complete
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
def complete?
|
|
87
|
+
status == 'completed'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns the model class for the import
|
|
91
|
+
# @return [Class]
|
|
92
|
+
def model_class
|
|
93
|
+
if type == 'Spree::Imports::Customers'
|
|
94
|
+
Spree.user_class
|
|
95
|
+
else
|
|
96
|
+
"Spree::#{type.demodulize.singularize}".safe_constantize
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns the import schema for the import
|
|
101
|
+
# @return [Spree::ImportSchema]
|
|
102
|
+
def import_schema
|
|
103
|
+
"Spree::ImportSchemas::#{type.demodulize}".safe_constantize.new
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the row processor class for the import
|
|
107
|
+
# @return [Class]
|
|
108
|
+
def row_processor_class
|
|
109
|
+
"Spree::ImportRowProcessors::#{type.demodulize.singularize}".safe_constantize
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns the fields for the import schema
|
|
113
|
+
# If model supports metafields, it will include the metafield definitions for this model
|
|
114
|
+
# @return [Array<Hash>]
|
|
115
|
+
def schema_fields
|
|
116
|
+
base_fields = import_schema.fields
|
|
117
|
+
|
|
118
|
+
# Dynamically add metafield definitions if the model supports metafields
|
|
119
|
+
if model_class_supports_metafields?
|
|
120
|
+
metafield_fields = metafield_definitions_for_model.map do |definition|
|
|
121
|
+
{
|
|
122
|
+
name: definition.csv_header_name,
|
|
123
|
+
label: definition.name
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
base_fields + metafield_fields
|
|
127
|
+
else
|
|
128
|
+
base_fields
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Returns the file columns that are not mapped
|
|
133
|
+
# @return [Array<String>]
|
|
134
|
+
def unmapped_file_columns
|
|
135
|
+
csv_headers.reject { |header| mappings.mapped.exists?(file_column: header) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns the required fields for the import schema
|
|
139
|
+
# @return [Array<String>]
|
|
140
|
+
def required_fields
|
|
141
|
+
import_schema.required_fields
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Returns the mapped fields for the import schema
|
|
145
|
+
# @return [Array<String>]
|
|
146
|
+
def mapped_fields
|
|
147
|
+
@mapped_fields ||= mappings.mapped.where(schema_field: required_fields)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Returns true if the mapping is done
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
def mapping_done?
|
|
153
|
+
mapped_fields.count == required_fields.count
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Returns the display name for the import
|
|
157
|
+
# @return [String]
|
|
158
|
+
def display_name
|
|
159
|
+
"#{Spree.t(type.demodulize.pluralize.downcase)} #{number}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def send_import_completed_email
|
|
163
|
+
# Spree::ImportMailer.import_done(self).deliver_later
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the headers of the csv file
|
|
167
|
+
# @return [Array<String>]
|
|
168
|
+
def csv_headers
|
|
169
|
+
return [] if attachment_file_content.blank?
|
|
170
|
+
|
|
171
|
+
@csv_headers ||= ::CSV.parse_line(
|
|
172
|
+
attachment_file_content,
|
|
173
|
+
col_sep: preferred_delimiter
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Returns the content of the attachment file
|
|
178
|
+
# @return [String]
|
|
179
|
+
def attachment_file_content
|
|
180
|
+
@attachment_file_content ||= attachment.attached? ? attachment.blob.download&.force_encoding('UTF-8') : nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Creates mappings from the schema fields
|
|
184
|
+
# TODO: get mappings from the previous import if it exists, so user won't have to map the same columns again
|
|
185
|
+
def create_mappings
|
|
186
|
+
schema_fields.each do |schema_field|
|
|
187
|
+
mapping = mappings.find_or_create_by!(schema_field: schema_field[:name])
|
|
188
|
+
mapping.try_to_auto_assign_file_column(csv_headers)
|
|
189
|
+
mapping.save!
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Creates rows asynchronously
|
|
194
|
+
# @return [void]
|
|
195
|
+
def create_rows_async
|
|
196
|
+
Spree::Imports::CreateRowsJob.set(wait: 2.seconds).perform_later(id)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Processes rows asynchronously
|
|
200
|
+
# @return [void]
|
|
201
|
+
def process_rows_async
|
|
202
|
+
Spree::Imports::ProcessRowsJob.perform_later(id)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Returns the store for the import
|
|
206
|
+
# @return [Spree::Store]
|
|
207
|
+
def store
|
|
208
|
+
owner.is_a?(Spree::Store) ? owner : owner.respond_to?(:store) ? owner.store : Spree::Store.default
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def update_loader_in_import_view
|
|
212
|
+
return unless defined?(broadcast_update_to)
|
|
213
|
+
|
|
214
|
+
broadcast_update_to "import_#{id}_loader", target: 'loader', partial: 'spree/admin/imports/loader', locals: { import: self }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Returns the current ability for the import
|
|
218
|
+
# @return [Spree::Ability]
|
|
219
|
+
def current_ability
|
|
220
|
+
@current_ability ||= Spree::Dependencies.ability_class.constantize.new(user, { store: store })
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class << self
|
|
224
|
+
# Returns the available types for the import
|
|
225
|
+
# @return [Array<Class>]
|
|
226
|
+
def available_types
|
|
227
|
+
Rails.application.config.spree.import_types
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Returns the available models for the import
|
|
231
|
+
# @return [Array<Class>]
|
|
232
|
+
def available_models
|
|
233
|
+
available_types.map(&:model_class)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Returns the type for the model
|
|
237
|
+
# @return [Class]
|
|
238
|
+
def type_for_model(model)
|
|
239
|
+
available_types.find { |type| type.model_class.to_s == model.to_s }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# eg. Spree::Imports::Orders => Spree::Order
|
|
243
|
+
def model_class
|
|
244
|
+
klass = "Spree::#{to_s.demodulize.singularize}".safe_constantize
|
|
245
|
+
|
|
246
|
+
raise NameError, "Missing model class for #{to_s}" unless klass
|
|
247
|
+
|
|
248
|
+
klass
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
def ensure_whitelisted_type
|
|
255
|
+
return if type.blank?
|
|
256
|
+
|
|
257
|
+
allowed = self.class.available_types.map(&:to_s)
|
|
258
|
+
errors.add(:type, :inclusion) unless allowed.include?(type)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def ensure_attachment_content_type
|
|
262
|
+
return if attachment.blank?
|
|
263
|
+
|
|
264
|
+
errors.add(:attachment, :content_type) unless attachment.content_type.in?(%w[text/csv])
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def model_class_supports_metafields?
|
|
268
|
+
return false unless model_class.present?
|
|
269
|
+
|
|
270
|
+
model_class.included_modules.include?(Spree::Metafields)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def metafield_definitions_for_model
|
|
274
|
+
return [] unless model_class.present?
|
|
275
|
+
|
|
276
|
+
Spree::MetafieldDefinition.for_resource_type(model_class.name)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class ImportMapping < Spree.base_class
|
|
3
|
+
#
|
|
4
|
+
# Associations
|
|
5
|
+
#
|
|
6
|
+
belongs_to :import, class_name: 'Spree::Import', inverse_of: :mappings, required: true
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# Validations
|
|
10
|
+
#
|
|
11
|
+
validates :import, :schema_field, presence: true
|
|
12
|
+
validates :schema_field, uniqueness: { scope: [:import_id] }
|
|
13
|
+
validates :file_column, uniqueness: { scope: [:import_id] }, allow_blank: true
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Scopes
|
|
17
|
+
#
|
|
18
|
+
scope :required, -> { where(schema_field: import.required_fields) }
|
|
19
|
+
scope :mapped, -> { where.not(file_column: [nil, '']) }
|
|
20
|
+
|
|
21
|
+
# Returns true if the mapping is required
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def required?
|
|
24
|
+
import.required_fields.include?(schema_field)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns true if the mapping has a file column
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def mapped?
|
|
30
|
+
file_column.present?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def try_to_auto_assign_file_column(csv_headers)
|
|
34
|
+
self.file_column = csv_headers.find { |header| header.parameterize.underscore.downcase.strip == schema_field.parameterize.underscore.downcase.strip }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the label for the schema field
|
|
38
|
+
# @return [String]
|
|
39
|
+
def schema_field_label
|
|
40
|
+
@schema_field_label ||= begin
|
|
41
|
+
field = import.schema_fields.find { |field| field[:name] == schema_field }
|
|
42
|
+
field[:label] if field.present?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class ImportRow < Spree.base_class
|
|
3
|
+
#
|
|
4
|
+
# Associations
|
|
5
|
+
#
|
|
6
|
+
belongs_to :import, class_name: 'Spree::Import', counter_cache: :rows_count
|
|
7
|
+
belongs_to :item, polymorphic: true, optional: true # eg. Spree::Variant, Spree::Order, etc.
|
|
8
|
+
delegate :store, to: :import
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Validations
|
|
12
|
+
#
|
|
13
|
+
validates :import, :data, presence: true
|
|
14
|
+
validates :row_number, uniqueness: { scope: :import_id }, numericality: { only_integer: true, greater_than: 0 }, presence: true
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# State machine
|
|
18
|
+
#
|
|
19
|
+
state_machine initial: :pending, attribute: :status do
|
|
20
|
+
event :start_processing do
|
|
21
|
+
transition to: :processing
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
event :fail do
|
|
25
|
+
transition to: :failed
|
|
26
|
+
end
|
|
27
|
+
after_transition to: :failed, do: :add_row_to_import_view
|
|
28
|
+
after_transition to: :failed, do: :update_footer_in_import_view
|
|
29
|
+
|
|
30
|
+
event :complete do
|
|
31
|
+
transition to: :completed
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
after_transition to: :completed, do: :add_row_to_import_view
|
|
35
|
+
after_transition to: :completed, do: :update_footer_in_import_view
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Scopes
|
|
40
|
+
#
|
|
41
|
+
scope :pending_and_failed, -> { where(status: %i[pending failed]) }
|
|
42
|
+
scope :completed, -> { where(status: :completed) }
|
|
43
|
+
scope :failed, -> { where(status: :failed) }
|
|
44
|
+
scope :processed, -> { where(status: %i[completed failed]) }
|
|
45
|
+
|
|
46
|
+
def data_json
|
|
47
|
+
@data_json ||= JSON.parse(data)
|
|
48
|
+
rescue JSON::ParserError
|
|
49
|
+
{}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_schema_hash
|
|
53
|
+
@to_schema_hash ||= begin
|
|
54
|
+
mappings = import.mappings.mapped
|
|
55
|
+
schema_fields = import.schema_fields
|
|
56
|
+
|
|
57
|
+
attributes = {}
|
|
58
|
+
schema_fields.each do |field|
|
|
59
|
+
attributes[field[:name]] = attribute_by_schema_field(field[:name], mappings)
|
|
60
|
+
end
|
|
61
|
+
attributes
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def attribute_by_schema_field(schema_field, mappings = nil)
|
|
66
|
+
mappings ||= import.mappings.mapped
|
|
67
|
+
|
|
68
|
+
mapping = mappings.find { |m| m.schema_field == schema_field }
|
|
69
|
+
return unless mapping&.mapped?
|
|
70
|
+
|
|
71
|
+
data_json[mapping.file_column]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def process!
|
|
75
|
+
start_processing!
|
|
76
|
+
self.item = import.row_processor_class.new(self).process!
|
|
77
|
+
complete!
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
self.validation_errors = e.message
|
|
80
|
+
fail!
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def add_row_to_import_view
|
|
84
|
+
return unless defined?(broadcast_append_to)
|
|
85
|
+
|
|
86
|
+
# we need to render this partial with store context to properly generate image URLs
|
|
87
|
+
with_store_context do
|
|
88
|
+
broadcast_append_to "import_#{import.id}_rows", target: 'rows', partial: 'spree/admin/imports/row', locals: { row: self, import: import }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def update_footer_in_import_view
|
|
93
|
+
return unless defined?(broadcast_replace_to)
|
|
94
|
+
|
|
95
|
+
broadcast_replace_to "import_#{import_id}_footer", target: 'footer', partial: 'spree/admin/imports/footer', locals: { import: import }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def with_store_context
|
|
101
|
+
Spree::Current.store = store
|
|
102
|
+
Rails.application.routes.default_url_options[:host] = store.url_or_custom_domain
|
|
103
|
+
yield
|
|
104
|
+
ensure
|
|
105
|
+
Spree::Current.reset if defined?(Spree::Current)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class ImportSchema
|
|
3
|
+
FIELDS = []
|
|
4
|
+
|
|
5
|
+
def fields
|
|
6
|
+
self.class::FIELDS
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def label_for_field(field_name)
|
|
10
|
+
fields.find { |f| f[:name] == field_name }[:label]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def required_fields
|
|
14
|
+
self.class::FIELDS.select { |f| f[:required] }.map { |f| f[:name] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def optional_fields
|
|
18
|
+
self.class::FIELDS.reject { |f| f[:required] }.map { |f| f[:name] }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def headers
|
|
22
|
+
self.class::FIELDS.map { |f| f[:name] }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module ImportSchemas
|
|
3
|
+
class Products < Spree::ImportSchema
|
|
4
|
+
FIELDS = [
|
|
5
|
+
{ name: 'slug', label: 'Slug', required: true },
|
|
6
|
+
{ name: 'sku', label: 'SKU', required: true },
|
|
7
|
+
{ name: 'name', label: 'Name', required: true },
|
|
8
|
+
{ name: 'price', label: 'Price', required: true },
|
|
9
|
+
{ name: 'status', label: 'Status' },
|
|
10
|
+
{ name: 'description', label: 'Description' },
|
|
11
|
+
{ name: 'meta_title', label: 'Meta Title' },
|
|
12
|
+
{ name: 'meta_description', label: 'Meta Description' },
|
|
13
|
+
{ name: 'meta_keywords', label: 'Meta Keywords' },
|
|
14
|
+
{ name: 'tags', label: 'Tags' },
|
|
15
|
+
{ name: 'compare_at_price', label: 'Compare at Price' },
|
|
16
|
+
{ name: 'currency', label: 'Currency' },
|
|
17
|
+
{ name: 'width', label: 'Width' },
|
|
18
|
+
{ name: 'height', label: 'Height' },
|
|
19
|
+
{ name: 'depth', label: 'Depth' },
|
|
20
|
+
{ name: 'dimensions_unit', label: 'Dimensions Unit' },
|
|
21
|
+
{ name: 'weight', label: 'Weight' },
|
|
22
|
+
{ name: 'weight_unit', label: 'Weight Unit' },
|
|
23
|
+
{ name: 'available_on', label: 'Available On' },
|
|
24
|
+
{ name: 'discontinue_on', label: 'Discontinue On' },
|
|
25
|
+
{ name: 'track_inventory', label: 'Track Inventory' },
|
|
26
|
+
{ name: 'inventory_count', label: 'Inventory Count' },
|
|
27
|
+
{ name: 'inventory_backorderable', label: 'Inventory Backorderable' },
|
|
28
|
+
{ name: 'tax_category', label: 'Tax Category' },
|
|
29
|
+
{ name: 'digital', label: 'Digital' },
|
|
30
|
+
{ name: 'image1_src', label: 'Image 1 URL' },
|
|
31
|
+
{ name: 'image2_src', label: 'Image 2 URL' },
|
|
32
|
+
{ name: 'image3_src', label: 'Image 3 URL' },
|
|
33
|
+
{ name: 'option1_name', label: 'Option 1 Name' },
|
|
34
|
+
{ name: 'option1_value', label: 'Option 1 Value' },
|
|
35
|
+
{ name: 'option2_name', label: 'Option 2 Name' },
|
|
36
|
+
{ name: 'option2_value', label: 'Option 2 Value' },
|
|
37
|
+
{ name: 'option3_name', label: 'Option 3 Name' },
|
|
38
|
+
{ name: 'option3_value', label: 'Option 3 Value' },
|
|
39
|
+
{ name: 'category1', label: 'Category 1' },
|
|
40
|
+
{ name: 'category2', label: 'Category 2' },
|
|
41
|
+
{ name: 'category3', label: 'Category 3' }
|
|
42
|
+
].freeze
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -15,7 +15,7 @@ module Spree
|
|
|
15
15
|
belongs_to :inviter, polymorphic: true # User or AdminUser
|
|
16
16
|
belongs_to :invitee, polymorphic: true, optional: true # User or AdminUser
|
|
17
17
|
belongs_to :role, class_name: 'Spree::Role'
|
|
18
|
-
has_one :role_user, dependent: :
|
|
18
|
+
has_one :role_user, dependent: :nullify, class_name: 'Spree::RoleUser', inverse_of: :invitation
|
|
19
19
|
|
|
20
20
|
#
|
|
21
21
|
# Validations
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class LineItem < Spree.base_class
|
|
3
|
+
include Spree::Metafields
|
|
3
4
|
include Spree::Metadata
|
|
4
5
|
if defined?(Spree::Webhooks::HasWebhooks)
|
|
5
6
|
include Spree::Webhooks::HasWebhooks
|
|
@@ -18,7 +19,7 @@ module Spree
|
|
|
18
19
|
has_one :product, -> { with_deleted }, class_name: 'Spree::Product', through: :variant
|
|
19
20
|
|
|
20
21
|
has_many :adjustments, as: :adjustable, dependent: :destroy
|
|
21
|
-
has_many :inventory_units, inverse_of: :line_item
|
|
22
|
+
has_many :inventory_units, class_name: 'Spree::InventoryUnit', inverse_of: :line_item, dependent: :destroy
|
|
22
23
|
has_many :shipments, through: :inventory_units, source: :shipment
|
|
23
24
|
has_many :digital_links, dependent: :destroy
|
|
24
25
|
|
|
@@ -41,8 +42,6 @@ module Spree
|
|
|
41
42
|
|
|
42
43
|
before_destroy :verify_order_inventory_before_destroy, if: -> { order.has_checkout_step?('delivery') }
|
|
43
44
|
|
|
44
|
-
before_destroy :destroy_inventory_units
|
|
45
|
-
|
|
46
45
|
after_save :update_inventory
|
|
47
46
|
after_save :update_adjustments
|
|
48
47
|
|
|
@@ -226,10 +225,6 @@ module Spree
|
|
|
226
225
|
Spree::OrderInventory.new(order, self).verify(target_shipment)
|
|
227
226
|
end
|
|
228
227
|
|
|
229
|
-
def destroy_inventory_units
|
|
230
|
-
throw(:abort) unless inventory_units.destroy_all
|
|
231
|
-
end
|
|
232
|
-
|
|
233
228
|
def update_adjustments
|
|
234
229
|
if saved_change_to_quantity?
|
|
235
230
|
recalculate_adjustments
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class Metafield < Spree.base_class
|
|
3
|
+
#
|
|
4
|
+
# Associations
|
|
5
|
+
#
|
|
6
|
+
belongs_to :resource, polymorphic: true, touch: true
|
|
7
|
+
belongs_to :metafield_definition, class_name: 'Spree::MetafieldDefinition'
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Delegations
|
|
11
|
+
#
|
|
12
|
+
delegate :key, :full_key, :name, :display_on, to: :metafield_definition, allow_nil: true
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Callbacks
|
|
16
|
+
#
|
|
17
|
+
before_validation :set_type_from_metafield_definition, on: :create
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# Validations
|
|
21
|
+
#
|
|
22
|
+
validates :metafield_definition, :type, :resource, :value, presence: true
|
|
23
|
+
validates :metafield_definition_id, uniqueness: { scope: [:resource_type, :resource_id] }
|
|
24
|
+
validate :type_must_match_metafield_definition
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Scopes
|
|
28
|
+
#
|
|
29
|
+
scope :available_on_front_end, -> { joins(:metafield_definition).merge(Spree::MetafieldDefinition.available_on_front_end) }
|
|
30
|
+
scope :available_on_back_end, -> { joins(:metafield_definition).merge(Spree::MetafieldDefinition.available_on_back_end) }
|
|
31
|
+
scope :with_key, ->(namespace, key) { joins(:metafield_definition).where(spree_metafield_definitions: { namespace: namespace, key: key }) }
|
|
32
|
+
|
|
33
|
+
def serialize_value
|
|
34
|
+
value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def csv_value
|
|
38
|
+
value.to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def set_type_from_metafield_definition
|
|
44
|
+
self.type ||= metafield_definition.metafield_type
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def type_must_match_metafield_definition
|
|
48
|
+
return if metafield_definition.blank?
|
|
49
|
+
|
|
50
|
+
errors.add(:type, 'must match metafield definition') unless type == metafield_definition.metafield_type
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|